Skip to content

Commit b4e1094

Browse files
Copilotfarfromrefug
andcommitted
Update documentation for streaming download behavior
Agent-Logs-Url: https://github.com/nativescript-community/https/sessions/07e4da87-be33-46f4-872f-e397b5e6c049 Co-authored-by: farfromrefug <655344+farfromrefug@users.noreply.github.com>
1 parent 98bb864 commit b4e1094

File tree

1 file changed

+144
-30
lines changed

1 file changed

+144
-30
lines changed

docs/IOS_ANDROID_BEHAVIOR_PARITY.md

Lines changed: 144 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,40 @@
1-
# iOS and Android Behavior Parity
1+
# iOS and Android Streaming Behavior
22

33
## Overview
44

5-
The iOS implementation has been updated to match Android's response handling behavior, providing consistent cross-platform functionality.
5+
Both iOS and Android now implement true streaming downloads where response bodies are NOT loaded into memory until explicitly accessed. This provides memory-efficient handling of large files.
66

7-
## Response Handling Behavior
7+
## How It Works
8+
9+
### Android (OkHttp)
10+
11+
Android uses OkHttp's `ResponseBody` which provides a stream:
12+
13+
1. **Request completes** - Response returned with `ResponseBody` (unopened stream)
14+
2. **Inspect response** - User can check status code and headers
15+
3. **Process data** - When `.toFile()`, `.toArrayBuffer()`, etc. is called:
16+
- Stream is opened and consumed
17+
- For `toFile()`: Data streams directly to disk
18+
- For `toArrayBuffer()`: Data streams into memory
19+
- For `toJSON()`: Data streams, parsed, returned
20+
21+
**Memory Usage**: Only buffered data in memory during streaming (typically ~8KB at a time)
22+
23+
### iOS (Alamofire)
824

9-
### How It Works
25+
iOS now uses Alamofire's `DownloadRequest` which downloads to a temp file:
26+
27+
1. **Request completes** - Response body downloaded to temp file
28+
2. **Inspect response** - User can check status code and headers
29+
3. **Process data** - When `.toFile()`, `.toArrayBuffer()`, etc. is called:
30+
- For `toFile()`: Temp file is moved to destination (no copy, no memory)
31+
- For `toArrayBuffer()`: Temp file loaded into memory
32+
- For `toJSON()`: Temp file loaded and parsed
33+
- For `toString()`: Temp file loaded as string
34+
35+
**Memory Usage**: Temp file on disk during download, loaded into memory only when explicitly accessed
36+
37+
## Response Handling Behavior
1038

1139
Both iOS and Android now follow the same pattern:
1240

@@ -55,58 +83,144 @@ if (response.statusCode === 200) {
5583

5684
On Android, the response includes a `ResponseBody` that provides an input stream:
5785

58-
- Request completes and returns response
59-
- ResponseBody is available with the data
60-
- When `toFile()` is called, it reads from the ResponseBody stream and writes to disk
61-
- When `toArrayBuffer()` is called, it reads from the ResponseBody stream into memory
86+
- Request completes and returns response with ResponseBody (stream not yet consumed)
87+
- ResponseBody stream is available but not opened
88+
- When `toFile()` is called, it opens the stream and writes to disk chunk by chunk
89+
- When `toArrayBuffer()` is called, it opens the stream and reads into memory
90+
- Stream is consumed only once - subsequent calls use cached data
6291

6392
**Native Code Flow:**
6493
```java
65-
// Response is returned with ResponseBody
94+
// Response is returned with ResponseBody (stream)
6695
ResponseBody responseBody = response.body();
6796

6897
// Later, when toFile() is called:
6998
InputStream inputStream = responseBody.byteStream();
7099
FileOutputStream output = new FileOutputStream(file);
71-
// Stream data from input to output
100+
byte[] buffer = new byte[1024];
101+
while ((count = inputStream.read(buffer)) != -1) {
102+
output.write(buffer, 0, count); // Streaming write
103+
}
72104
```
73105

106+
**Memory Characteristics:**
107+
- Only buffer size (~1KB) in memory during streaming
108+
- Large files: ~1-2MB RAM overhead maximum
109+
- File writes happen progressively as data arrives
110+
74111
### iOS (Alamofire)
75112

76-
On iOS, the response includes the data as NSData:
113+
On iOS, the response downloads to a temporary file automatically:
77114

78-
- Request completes and returns response
79-
- Data is loaded into memory as NSData
80-
- When `toFile()` is called, it writes the NSData to disk
81-
- When `toArrayBuffer()` is called, it converts NSData to ArrayBuffer
115+
- Request completes and downloads body to temp file
116+
- Temp file path stored in response object
117+
- When `toFile()` is called, it moves the temp file to destination (fast file system operation)
118+
- When `toArrayBuffer()` is called, it loads the temp file into memory
119+
- When `toJSON()` is called, it loads and parses the temp file
82120

83121
**Native Code Flow:**
84122
```swift
85-
// Response is returned with NSData
86-
let data: NSData = responseData
123+
// Response downloads to temp file during request
124+
let tempFileURL = FileManager.default.temporaryDirectory
125+
.appendingPathComponent(UUID().uuidString)
126+
// Download happens here, saved to tempFileURL
87127

88128
// Later, when toFile() is called:
89-
data.writeToFile(filePath, atomically: true)
129+
try FileManager.default.moveItem(at: tempFileURL, to: destinationURL)
130+
// Fast move operation, no data copying
131+
132+
// Or when toArrayBuffer() is called:
133+
let data = try Data(contentsOf: tempFileURL)
134+
// File loaded into memory at this point
90135
```
91136

137+
**Memory Characteristics:**
138+
- Temp file written to disk during download
139+
- No memory overhead during download (except small buffer)
140+
- Memory used only when explicitly loading via toArrayBuffer()/toJSON()
141+
- toFile() uses file move (no memory overhead)
142+
92143
## Memory Considerations
93144

94-
### Android
95-
- ResponseBody provides a stream, so data isn't necessarily all in memory
96-
- OkHttp may buffer data internally
97-
- Large files will consume memory proportional to the buffering strategy
145+
### Comparison
98146

99-
### iOS
100-
- Response data is loaded into memory as NSData
101-
- Large files will consume memory equal to the file size
102-
- This matches Android's effective behavior for most use cases
147+
| Operation | Android Memory | iOS Memory |
148+
|-----------|----------------|------------|
149+
| **During download** | ~1-2MB buffer | ~1-2MB buffer + temp file on disk |
150+
| **After download** | ResponseBody (minimal) | Temp file on disk (0 RAM) |
151+
| **toFile()** | Stream to disk (~1MB buffer) | File move (0 RAM) |
152+
| **toArrayBuffer()** | Load into memory | Load from temp file into memory |
153+
| **toJSON()** | Stream and parse | Load from temp file and parse |
103154

104-
### Recommendation
155+
### Benefits
156+
157+
Both platforms now provide true memory-efficient streaming:
158+
159+
1. **Large File Downloads**: Won't cause OOM errors
160+
2. **Flexible Processing**: Inspect headers before committing to download
161+
3. **Efficient File Saving**: Direct streaming (Android) or file move (iOS)
162+
4. **On-Demand Loading**: Data loaded into memory only when explicitly requested
163+
164+
### Recommendations
105165

106166
For both platforms:
107-
- Small files (<10MB): No concern, data handling is efficient
108-
- Medium files (10-50MB): Monitor memory usage, should work on most devices
109-
- Large files (>50MB): Test on low-memory devices, consider chunked downloads if needed
167+
- **Small files (<10MB)**: Any method works efficiently
168+
- **Medium files (10-100MB)**: Use `toFile()` for best memory efficiency
169+
- **Large files (>100MB)**: Always use `toFile()` to avoid memory issues
170+
- **JSON APIs**: `toJSON()` works well for responses up to ~50MB
171+
172+
## Example Usage
173+
174+
```typescript
175+
import { request } from '@nativescript-community/https';
176+
177+
async function downloadLargeFile() {
178+
console.log('Starting download...');
179+
180+
// Step 1: Make the request
181+
// iOS: Downloads to temp file on disk (not in RAM)
182+
// Android: Opens connection, keeps ResponseBody stream (not in RAM)
183+
const response = await request({
184+
method: 'GET',
185+
url: 'https://example.com/large-file.zip',
186+
onProgress: (current, total) => {
187+
const percent = (current / total * 100).toFixed(1);
188+
console.log(`Downloading: ${percent}%`);
189+
}
190+
});
191+
192+
// Step 2: Request completes, inspect the response
193+
// At this point, large file is NOT in memory on either platform!
194+
console.log('Download complete!');
195+
console.log('Status code:', response.statusCode);
196+
console.log('Content-Type:', response.headers['Content-Type']);
197+
console.log('Content-Length:', response.contentLength);
198+
199+
// Step 3: Now decide what to do with the data
200+
if (response.statusCode === 200) {
201+
// Option A: Save to file (MOST MEMORY EFFICIENT)
202+
// iOS: Moves temp file (0 RAM overhead)
203+
// Android: Streams ResponseBody to file (~1MB RAM overhead)
204+
const file = await response.content.toFile('~/Downloads/file.zip');
205+
console.log('Saved to:', file.path);
206+
207+
// Option B: Load into memory (for processing)
208+
// iOS: Loads temp file into RAM
209+
// Android: Streams ResponseBody into RAM
210+
// WARNING: Use only for files that fit in memory!
211+
// const buffer = await response.content.toArrayBuffer();
212+
// console.log('Buffer size:', buffer.byteLength);
213+
214+
// Option C: Parse as JSON (for APIs)
215+
// iOS: Loads temp file and parses
216+
// Android: Streams ResponseBody and parses
217+
// const json = response.content.toJSON();
218+
// console.log('Data:', json);
219+
} else {
220+
console.error('Download failed with status:', response.statusCode);
221+
}
222+
}
223+
```
110224

111225
## Benefits of Consistent Behavior
112226

0 commit comments

Comments
 (0)