For my last project, I came across a need:
- load data in a blocking way (to run in the background thread)
- but also gradually process the data as it is received (since the downloaded data can easily be 100 M, so it was inefficient to store everything in one big NSData *)
Thus, I needed to use an asynchronous NSURLConnection object (to be able to receive data gradually), but wrap it in a container that blocks the calling stream "between" two consecutive delegate calls connection:didReceiveData: and until connectionDidFinishLoading: or connection:didFailWithError:
I thought I would share my decision, since it took me several hours to collect the necessary code fragments found here and there (on StackOverflow and other forums).
Basically, the code starts a new NSURLConnection in the background thread ( dispatch_get_global_queue ), sets up a run loop so that it can receive delegate calls, and uses dispatch_semaphores to block calls and background threads in a "striped" path. The dispatch_semaphores code is beautifully wrapped inside the ProducerConsumerLock custom class.
BlockingConnection.m
#import "BlockingConnection.h" #import "ProducerConsumerLock.h" @interface BlockingConnection() @property (nonatomic, strong) ProducerConsumerLock* lock; @end @implementation BlockingConnection - (id)initWithURL:(NSURL*) url callback:(void(^)(NSData* data)) callback { if (self = [super init]) { self.lock = [ProducerConsumerLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; [NSURLConnection connectionWithRequest:request delegate:self]; while(!self.lock.finished) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } }); [self.lock consume:^(NSData* data) { if (callback != nil) { callback(data); } }]; } return self; } + (void) connectionWithURL:(NSURL*) url callback:(void(^)(NSData* data)) callback { BlockingConnection* connection; connection = [[BlockingConnection alloc] initWithURL:url callback:callback]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.lock produce:data]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { [self.lock produce:nil]; [self.lock finish]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [self.lock finish]; } @end
ProducerConsumerLock.h
@interface ProducerConsumerLock : NSObject @property (atomic, readonly) BOOL finished; - (void) consume:(void(^)(id object)) block; - (void) produce:(id) object; - (void) finish; @end
ProducerConsumerLock.m
#import "ProducerConsumerLock.h" @interface ProducerConsumerLock() { dispatch_semaphore_t consumerSemaphore; dispatch_semaphore_t producerSemaphore; NSObject* _object; } @end @implementation ProducerConsumerLock - (id)init { if (self = [super init]) { consumerSemaphore = dispatch_semaphore_create(0); producerSemaphore = dispatch_semaphore_create(0); _finished = NO; } return self; } - (void) consume:(void(^)(id)) block { BOOL finished = NO; while (!finished) { dispatch_semaphore_wait(consumerSemaphore, DISPATCH_TIME_FOREVER); finished = _finished; if (!finished) { block(_object); dispatch_semaphore_signal(producerSemaphore); } } } - (void) produce:(id) object { _object = object; _finished = NO; dispatch_semaphore_signal(consumerSemaphore); dispatch_semaphore_wait(producerSemaphore, DISPATCH_TIME_FOREVER); } - (void) finish { _finished = YES; dispatch_semaphore_signal(consumerSemaphore); } - (void)dealloc { dispatch_release(consumerSemaphore); dispatch_release(producerSemaphore); } @end
The BlockingConnection class can be used from the main thread (but this blocks the main thread) or from the user queue:
dispatch_async(queue, ^{ [BlockingConnection connectionWithURL:url callback:^(NSData *data) { if (data != nil) {
If you have comments or suggestions, please be prepared!