Note that there are some explanatory texts on larger screens.

plurals
  1. PONSURLConnection blocking wrapper implemented with semaphores
    primarykey
    data
    text
    <p>For my most recent project, I stumbled across the need to :</p> <ul> <li>download data in a blocking way (to be started in a background thread)</li> <li>but also progressively process the data as it is being received (as the downloaded data could easily be 100M so it was not efficient to store it all in one big NSData*)</li> </ul> <p>I thus needed to use an asynchronous NSURLConnection object (for being able to receive the data progressively) but wrap it in a container that would block the calling thread "in between" two successive <code>connection:didReceiveData:</code> delegate calls, and until either <code>connectionDidFinishLoading:</code> or <code>connection:didFailWithError:</code> were called.</p> <p>I thought I would share my solution as it took me well over a few hours to put together the right pieces of code found here and there (on StackOverflow and other forums).</p> <p>The code basically launches a new <code>NSURLConnection</code> on a background thread (<code>dispatch_get_global_queue</code>), sets the run loop to be able to receive the delegates calls, and uses <code>dispatch_semaphores</code> to block the calling and the background threads in an "alternating" way. The <code>dispatch_semaphores</code> code is nicely wrapped inside a custom <code>ProducerConsumerLock</code> class.</p> <p><strong>BlockingConnection.m</strong></p> <pre><code>#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 </code></pre> <p><strong>ProducerConsumerLock.h</strong></p> <pre><code>@interface ProducerConsumerLock : NSObject @property (atomic, readonly) BOOL finished; - (void) consume:(void(^)(id object)) block; - (void) produce:(id) object; - (void) finish; @end </code></pre> <p><strong>ProducerConsumerLock.m</strong></p> <pre><code>#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 </code></pre> <p>The BlockingConnection class can be used from the main thread (but this would block the main thread) or from a custom queue:</p> <pre><code>dispatch_async(queue, ^{ [BlockingConnection connectionWithURL:url callback:^(NSData *data) { if (data != nil) { //process the chunk of data as you wish NSLog(@"received %u bytes", data.length); } else { //an error occurred } }]; NSLog(@"finished downloading"); }); </code></pre> <p>If you have any comments or suggestions please be welcome!</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload