当多个块数据调用完成时如何通知
How to notify when multiple block data calls have completed
我希望通过块提取一些数据,但我只想 return 一旦检索到所有数据(所有任意数量的调用都已 returned 和没有得到错误)。我已经研究过 RAC 和 dispatch_groups,但还没有完全弄明白这一点。我尝试执行的代码如下所示:
NSMutableArray *arrayToReturn = [NSMutableArray alloc] init];
for (SPTPartialArtist *partialArtist in artistArray) {
[SPTRequest requestItemFromPartialObject:partialArtist withSession:self.session callback:^(NSError *error, id object) {
[arrayToReturn addObject:object];
}];
}
return arrayToReturn
但是,这只是 return 一个空数组,因为循环在任何数据进入之前就结束了。我本质上是在寻找一种方法,仅当块出现时才 return已在循环中的每个项目上调用,因此 return 数组充满了新对象
为什么不创建一个带有块参数的方法?您的代码将如下所示:
- (void)arrayWithBlock:(void(^)(NSMutableArray *array))block
{
NSMutableArray *arrayToReturn = [NSMutableArray alloc] init];
for (SPTPartialArtist *partialArtist in artistArray) {
[SPTRequest requestItemFromPartialObject:partialArtist withSession:self.session callback:^(NSError *error, id object) {
[arrayToReturn addObject:object];
if (arrayToReturn.count == artistArray.count) {
if (block) {
block(arrayToReturn);
}
}
}];
}
}
在控制器中维护一个数组(假设是 completionArray),并同时维护要在控制器中执行数据下载的块的计数(假设是 blockCount)。
每当特定块完成执行时,该块将在 completionArray 中添加 1(表示成功)或 0(表示失败)。
之后区块将检查blockCount是否匹配completionArray.count。如果它们匹配,则表示所有块都已执行完毕。
然后块将通知控制器相同。
希望这对您有所帮助。
既然你用 reactive-cocoa
标记了,让我们看看 RAC 方法。
您的问题是如何将一系列异步操作变成单个同步操作。 这是个坏主意。这是可能的,但它只会阻塞你所在的线程。如果您已经在使用 RAC,您应该使用信号来管理您的异步方法(例如这个),而不是强制它们同步。
但无论如何。
无论哪种方式,您都希望首先用基于信号的 API 包装 requestItemFromPartialObject:withSession:callback:
。您可能应该在 SPTRequest
的类别中执行此操作,但为了简洁起见,我会偷懒:
static RACSignal *request(id<SPTPartialObject> partialObject, SPTSession *session) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[SPTRequest requestItemFromPartialObject:partialObject withSession:session callback:^(NSError *error, id object) {
if (error) {
[subscriber sendError:error];
} else {
[subscriber sendNext:object];
[subscriber sendCompleted];
}
}];
return nil;
}];
}
然后你真正想做的是收集艺术家的集合,将其转化为信号集合,然后将其转化为结果集合。一旦你有了它,你可以选择将它转换成一个数组(同步方法)或 return 信号(正确的做法)。
首先,我们想把艺术家的排列变成艺术家的信号。它使事情变得更容易。您可以像这样将数组提升为信号:
RACSignal *artistSignal = [artistArray.rac_sequence signal];
然后将一个事物的信号异步转换为另一个事物的信号就像:
RACSignal *results = [artistSignal flattenMap:^RACSignal *(SPTPartialArtist *partialArtist) {
return request(partialArtist, self.session);
}];
flattenMap
是一个有点难以获得直觉的操作 -- 它就像 map
,但它的转换块 return 是 Signal<T>
而不是 T
,然后它 "unwraps" 每个信号都会给你一个 Signal<T>
作为结果(而不是像 map
那样的 Signal<Signal<T>>
)。
如果你真的想要同步(你不想),你可以(但不应该)使用toArray
方法:
return [results toArray];
这将阻塞,直到所有请求都完成,所以如果您要执行此操作,您需要确保您是在主线程之外执行它,否则您的整个应用程序将冻结。
不过,正确的做法是 return 信号本身,并让您的调用者知道此方法是异步的。
此解决方案还将对结果进行正确排序,因此您不会 运行 陷入当前代码和其他一些答案所具有的排序问题(结果数组包含按返回顺序排列的结果,不是要求的订单)。
你真正需要的是一个dispatch_group和一个完成块,如下所示:
-(void)downloadDataForArtists:(NSArray*)artistsArray withCompletion:(void (^)(NSArray* dataArray))completion
{
NSMutableArray *arrayToReturn = [NSMutableArray alloc] init];
dispatch_group_t downloadGroup = dispatch_group_create();
for (SPTPartialArtist *partialArtist in artistArray) {
dispatch_group_enter(downloadGroup);
[SPTRequest requestItemFromPartialObject:partialArtist withSession:self.session callback:^(NSError *error, id object) {
[arrayToReturn addObject:object];
dispatch_group_leave(downloadGroup);
}];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
if (completion) {
completion(arrayToReturn);
}
});
}
使用dispatch_group是正确的方法。代码可能是这样的:
-(void)requestItemsFromArtists:(NSArray *)artistArray completionBlock:(void(^)(NSArray *))block{
NSMutableArray *items = [NSMutableArray array];
dispatch_group_t group = dispatch_group_create();
for(SPTPartialArtist *artist in artistArray){
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[SPTRequest requestItemFromPartialObject:artist withSession:self.session callback:^(NSError *error, id object){
[items addObject:object];
}];
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if(block){
block(items);
}
});
}
我希望通过块提取一些数据,但我只想 return 一旦检索到所有数据(所有任意数量的调用都已 returned 和没有得到错误)。我已经研究过 RAC 和 dispatch_groups,但还没有完全弄明白这一点。我尝试执行的代码如下所示:
NSMutableArray *arrayToReturn = [NSMutableArray alloc] init];
for (SPTPartialArtist *partialArtist in artistArray) {
[SPTRequest requestItemFromPartialObject:partialArtist withSession:self.session callback:^(NSError *error, id object) {
[arrayToReturn addObject:object];
}];
}
return arrayToReturn
但是,这只是 return 一个空数组,因为循环在任何数据进入之前就结束了。我本质上是在寻找一种方法,仅当块出现时才 return已在循环中的每个项目上调用,因此 return 数组充满了新对象
为什么不创建一个带有块参数的方法?您的代码将如下所示:
- (void)arrayWithBlock:(void(^)(NSMutableArray *array))block
{
NSMutableArray *arrayToReturn = [NSMutableArray alloc] init];
for (SPTPartialArtist *partialArtist in artistArray) {
[SPTRequest requestItemFromPartialObject:partialArtist withSession:self.session callback:^(NSError *error, id object) {
[arrayToReturn addObject:object];
if (arrayToReturn.count == artistArray.count) {
if (block) {
block(arrayToReturn);
}
}
}];
}
}
在控制器中维护一个数组(假设是 completionArray),并同时维护要在控制器中执行数据下载的块的计数(假设是 blockCount)。
每当特定块完成执行时,该块将在 completionArray 中添加 1(表示成功)或 0(表示失败)。
之后区块将检查blockCount是否匹配completionArray.count。如果它们匹配,则表示所有块都已执行完毕。
然后块将通知控制器相同。
希望这对您有所帮助。
既然你用 reactive-cocoa
标记了,让我们看看 RAC 方法。
您的问题是如何将一系列异步操作变成单个同步操作。 这是个坏主意。这是可能的,但它只会阻塞你所在的线程。如果您已经在使用 RAC,您应该使用信号来管理您的异步方法(例如这个),而不是强制它们同步。
但无论如何。
无论哪种方式,您都希望首先用基于信号的 API 包装 requestItemFromPartialObject:withSession:callback:
。您可能应该在 SPTRequest
的类别中执行此操作,但为了简洁起见,我会偷懒:
static RACSignal *request(id<SPTPartialObject> partialObject, SPTSession *session) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[SPTRequest requestItemFromPartialObject:partialObject withSession:session callback:^(NSError *error, id object) {
if (error) {
[subscriber sendError:error];
} else {
[subscriber sendNext:object];
[subscriber sendCompleted];
}
}];
return nil;
}];
}
然后你真正想做的是收集艺术家的集合,将其转化为信号集合,然后将其转化为结果集合。一旦你有了它,你可以选择将它转换成一个数组(同步方法)或 return 信号(正确的做法)。
首先,我们想把艺术家的排列变成艺术家的信号。它使事情变得更容易。您可以像这样将数组提升为信号:
RACSignal *artistSignal = [artistArray.rac_sequence signal];
然后将一个事物的信号异步转换为另一个事物的信号就像:
RACSignal *results = [artistSignal flattenMap:^RACSignal *(SPTPartialArtist *partialArtist) {
return request(partialArtist, self.session);
}];
flattenMap
是一个有点难以获得直觉的操作 -- 它就像 map
,但它的转换块 return 是 Signal<T>
而不是 T
,然后它 "unwraps" 每个信号都会给你一个 Signal<T>
作为结果(而不是像 map
那样的 Signal<Signal<T>>
)。
如果你真的想要同步(你不想),你可以(但不应该)使用toArray
方法:
return [results toArray];
这将阻塞,直到所有请求都完成,所以如果您要执行此操作,您需要确保您是在主线程之外执行它,否则您的整个应用程序将冻结。
不过,正确的做法是 return 信号本身,并让您的调用者知道此方法是异步的。
此解决方案还将对结果进行正确排序,因此您不会 运行 陷入当前代码和其他一些答案所具有的排序问题(结果数组包含按返回顺序排列的结果,不是要求的订单)。
你真正需要的是一个dispatch_group和一个完成块,如下所示:
-(void)downloadDataForArtists:(NSArray*)artistsArray withCompletion:(void (^)(NSArray* dataArray))completion
{
NSMutableArray *arrayToReturn = [NSMutableArray alloc] init];
dispatch_group_t downloadGroup = dispatch_group_create();
for (SPTPartialArtist *partialArtist in artistArray) {
dispatch_group_enter(downloadGroup);
[SPTRequest requestItemFromPartialObject:partialArtist withSession:self.session callback:^(NSError *error, id object) {
[arrayToReturn addObject:object];
dispatch_group_leave(downloadGroup);
}];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
if (completion) {
completion(arrayToReturn);
}
});
}
使用dispatch_group是正确的方法。代码可能是这样的:
-(void)requestItemsFromArtists:(NSArray *)artistArray completionBlock:(void(^)(NSArray *))block{
NSMutableArray *items = [NSMutableArray array];
dispatch_group_t group = dispatch_group_create();
for(SPTPartialArtist *artist in artistArray){
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[SPTRequest requestItemFromPartialObject:artist withSession:self.session callback:^(NSError *error, id object){
[items addObject:object];
}];
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if(block){
block(items);
}
});
}