I'm trying to implement synchronous downloading with progress callback with NSURLConnection. When [connection start] is invoked, nothing happens - delegate callback methods are not just invoked (i'm testing on OSX in XCTestCase). What's wrong?
// header
@interface ASDownloadHelper : NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
{
NSMutableData *_receivedData;
NSUInteger _expectedBytes;
id<ASDownloadHelperListener> _listener;
NSError *_error;
BOOL _finished;
id _finishedSyncObject;
}
- (void) download: (NSString*)url file:(NSString*)file listener:(id<ASDownloadHelperListener>)listener;
@end
// impl
@implementation ASDownloadHelper
// delegate
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[_receivedData setLength:0];
_expectedBytes = [response expectedContentLength];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_receivedData appendData:data];
int percent = round( _receivedData.length * 100.0 / _expectedBytes );
[_listener onDownloadProgress:_receivedData.length total:_expectedBytes percent:percent];
}
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
_error = error;
[self setFinished:YES];
}
- (NSCachedURLResponse *) connection:(NSURLConnection*)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
return nil;
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
[self setFinished: YES];
}
- (BOOL) isFinished {
@synchronized(_finishedSyncObject) {
return _finished;
}
}
- (void) setFinished: (BOOL)finished {
@synchronized(_finishedSyncObject) {
_finished = finished;
}
}
// ---
- (void) download: (NSString*)downloadUrl file:(NSString*)file listener:(id<ASDownloadHelperListener>)listener {
_listener = listener;
_finished = NO;
_finishedSyncObject = [[NSObject alloc] init];
_error = nil;
NSURL *url = [NSURL URLWithString:downloadUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:30];
_receivedData = [[NSMutableData alloc] initWithLength:0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
[connection start];
// block the thread until downloading finished
while (![self isFinished]) { };
// error?
if (_error != nil) {
@throw _error;
return;
}
// success
[_receivedData writeToFile:file atomically:YES];
_receivedData = nil;
}
@end
Thanks to quellish i've found out that invocation queue should not be blocked as callback invocations (delegate methods) are done in invoker thread context. In my case i was running it in main test thread so i had to do workaround (and sleep in main thread for few seconds to let downloading finish):
- (void)testDownload
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
// ...
[_downloadHelper download:repositoryUrl file:downloadedFile listener:downloadlistener];
// progress callbacks are invoked in this thread context, so it can't be blocked
// ...
XCTAssertNotNil( ... );
});
// block main test queue until downloading is finished
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}