NSArray enumerateObjectsUsingBlock:延迟枚举

时间:2013-01-12 09:33:54

标签: iphone ios xcode cocoa-touch

我使用快速枚举,在枚举块中,我异步发送网络请求。

所以会发生什么是enumerateObjectsUsingBlock:只需要超级快速调用块,让枚举块在一段时间后完成。

这会导致不同的结果,因为有些请求比其他请求完成得更快。所以它没有按我的意愿排序。

有没有办法将块设置为冻结,异步网络请求完成后,只是告诉它转到下一个?

这是一些代码

    NSArray *sites = [self.fetchedResultsController.fetchedObjects copy];
    NSLog(@"sites - %@",sites);
    [sites enumerateObjectsUsingBlock:^(Sites *site, NSUInteger idx, BOOL *stop) {
        NSLog(@"site name - %@,",site.name);

        [[Wrapper sharedWrapper] sendRequestTo:site completionBlock:{

                NSLog(@"site name - %@",site.name);
        }];
    }];

谢谢!

3 个答案:

答案 0 :(得分:1)

我想实现同样的目的,但继续使用块来简化我的代码,而不是通过递归方法传递参数的麻烦。我想出了这个NSArray类别:

NS_ASSUME_NONNULL_BEGIN

@interface NSArray(MH)

- (void)mh_asyncEnumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL* stop, dispatch_block_t next))block;

@end

NS_ASSUME_NONNULL_END

@implementation NSArray(MH)

- (void)mh_asyncEnumerateObjectsUsingBlock:(void (^)(id  _Nonnull obj, NSUInteger idx, BOOL* stop, dispatch_block_t next))block{
    __block NSUInteger index = 0;
    __block BOOL stop = NO;

    void (^next)();
    __block __weak typeof(next) weakNext;
    weakNext = next = ^void() {
        void (^strongNext)() = weakNext;
        // check if finished
        if(stop || index == self.count){
            return;
        }
        id obj = self[index];
        index++;
        block(obj, index - 1, &stop, strongNext);
    };
    next();
}

@end

它的使用方式如下:

NSArray* a = @[@"Malc", @"Bob", @"Jim"];
[a mh_asyncEnumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL *stop, dispatch_block_t next) {
    // simulate an async network call
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", obj);
        next();
    });
}];

输出:

2016-01-04 22:41:04.631 Malc
2016-01-04 22:41:05.632 Bob
2016-01-04 22:41:06.720 Jim

如您所见,此演示以1秒的延迟输出数组中的每个字符串。您可以通过在块内进行网络调用来使用它,然后在完成后调用next。如果您遇到错误并想取消只设置* stop = YES;在调用next()之前,就像使用普通枚举一样。

NSArray *sites = [self.fetchedResultsController.fetchedObjects copy];
NSLog(@"sites - %@",sites);
[sites mh_asyncEnumerateObjectsUsingBlock:^(Site *site, NSUInteger idx, BOOL *stop, dispatch_block_t next){
    NSLog(@"site name - %@,",site.name);

    [[Wrapper sharedWrapper] sendRequestTo:site completionBlock:{
          if(error){ // your completion block should have an error param!!!
              *stop = YES;
          }
          NSLog(@"site name - %@",site.name);
          next();
     }];
 }];

答案 1 :(得分:1)

  

有没有办法将块设置为冻结,之后   异步网络请求完成后,只需告诉它去   下一个?

NSArray *sites = [self.fetchedResultsController.fetchedObjects copy];
NSLog(@"sites - %@",sites);
[sites enumerateObjectsUsingBlock:^(Sites *site, NSUInteger idx, BOOL *stop) {
    NSLog(@"site name - %@,",site.name);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [[Wrapper sharedWrapper] sendRequestTo:site completionBlock:{

            NSLog(@"site name - %@",site.name);
dispatch_semaphore_signal(semaphore);
    }];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}];

^不是真正理想的方法,但是如果你想在等待异步请求完成之前同步迭代,那么上面将通过GCD完成。还有其他方法可以在异步任务完成后等待所有组离开时迭代和增加dispatch_group的位置,例如:

dispatch_group_t downloadGroup = dispatch_group_create();


        dispatch_group_enter(downloadGroup);
        [self fetchStuffInBackground:background withCompletion:^(NSArray *stuff, NSError *error) {
            NSLog(@"leaving stuff");
            dispatch_group_leave(downloadGroup);
        }];
        dispatch_group_enter(downloadGroup);
        [self fetchAOtherStuffInBackground:background withCompletion:^(NSArray *stuff, NSError *error) {
            NSLog(@"leaving other stuff");
            dispatch_group_leave(downloadGroup);
        }];
              dispatch_group_enter(downloadGroup);
        [self fetchLastStuffInBackground:background withCompletion:^(NSArray *lastStuff, NSError *error) {
            NSLog(@"leaving last stuff");
            dispatch_group_leave(downloadGroup);
        }];      
    }

}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
    if (callback) {
        callback(error);
    }
});

答案 2 :(得分:0)

  

有没有办法将块设置为冻结,异步网络请求完成后,只是告诉它转到下一个?

您可以通过重新组织代码来实现该结果:而不是使用枚举,只需执行完成块中的异步请求,即:

- (void) doRequestAsync:(NSArray*)sites index:(NSUInteger)index {

    if (index >= [sites count]) return;

    NSString* site = [sites objectAtIndex:index];
    [[Wrapper sharedWrapper] sendRequestTo:site completionBlock:{
            NSLog(@"site name - %@",site.name);
            [self doRequestAsync:sites index:++index];
    }];

}

替代方法是修改您的Wrapper类,使其使用异步网络(但在辅助线程上使用它,以避免阻止UI)。

或者您可以实施Async Completion Token pattern,以便在收到回复时对其进行重新排序。