用信号量实现NSURLConnection阻塞包装器

对于我最近的项目,我偶然发现需要:

  • 以阻塞的方式下载数据(在后台线程中启动)
  • 而且会逐渐处理收到的数据(因为下载的数据可能很容易达到100M,所以将其全部存储在一个大的NSData *中效率不高)

因此,我需要使用一个asynchronous的NSURLConnection对象(为了能够逐步接收数据),但将其包装在一个容器中,该容器将阻止两个连续的connection:didReceiveData:之间的调用线程connection:didReceiveData:委托调用,直到connectionDidFinishLoading:connection:didFailWithError:被调用。

我以为我会分享我的解决scheme,因为它花了我几个小时把这里和那里find的正确的代码(在StackOverflow和其他论坛)。

代码基本上在后台线程( dispatch_get_global_queue )上启动一个新的NSURLConnection ,将运行循环设置为能够接收委托调用,并使用dispatch_semaphores以“交替”方式阻止调用线程和后台线程。 dispatch_semaphores代码很好地包装在一个自定义的ProducerConsumerLock类中。

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 

BlockingConnection类可以从主线程(但会阻塞主线程)或自定义队列中使用:

 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"); }); 

如果您有任何意见或build议,欢迎!