私の最近のプロジェクトでは、次の必要性に出くわしました。
- ブロック方式でデータをダウンロードする (バックグラウンド スレッドで開始する)
- また、データを受信しながら段階的に処理します (ダウンロードされるデータは簡単に 100M になる可能性があるため、すべてを 1 つの大きな NSData* に格納するのは効率的ではありませんでした)。
connection:didReceiveData:
したがって、非同期の NSURLConnection オブジェクトを (データを段階的に受信できるようにするために) 使用する必要がありましたが、2 つの連続するデリゲート呼び出しの「間」で、またはいずれconnectionDidFinishLoading:
かconnection:didFailWithError:
が呼び出されるまで、呼び出しスレッドをブロックするコンテナーにラップする必要がありました。
あちこち (StackOverflow やその他のフォーラム) で見つかった適切なコードをまとめるのに数時間以上かかったので、ソリューションを共有すると思いました。
このコードは基本的NSURLConnection
に、バックグラウンド スレッド ( dispatch_get_global_queue
) で new を起動し、デリゲートの呼び出しを受信できるように実行ループを設定し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");
});
ご意見やご提案がありましたら、お気軽にどうぞ!