If I have a for loop that uses Obj-C blocks, how can I identify the last iteration?

asynchronous, objective-c, objective-c-blocks

Solution

The pointer comparison (`objectID == [objectIDs lastObject]`) is alright here as you will get the same objects if the array is not modified. If you are concerned that `objectIDs` might get modified (e.g. that it might actually be a mutable array and get modified on another thread or through side effects like notifications along the way), the best protection would be to copy the array right at the beginning:

- (void)fetchObjectsFromServerWithObjectIDs:(NSArray*)objectIDs {

    typeof(self) __weak blockSelf = self;
    NSArray *copiedIDs = [objectIDs copy];

    for(NSString *objectID in copiedIDs) {
        [self fetchObjectDataForObjectID:objectID withSuccessBlock:^{
            if(objectID == [copiedIDs lastObject]) {
                [blockSelf.delegate finishedFetching]; 
            }
        }];
    }
}

There are now two ways to call the delegate:

- Call it once the last fetch has finished.

- Or call it once the fetch for the last object has finished, even if other fetch requests are in progress.

So you can:

- Use a counter like @ChrisH proposed. This will call the delegate after the very last fetch has finished, no matter in which order they were processed.

- Use the pointer comparison. This will call the delegate after the fetch for lastObject has finished.

- For this, I think there's a minor optimization you can do. As it is, it has the downside that it captures the array in the block and invokes an unnecessary method call every time the block is called. Instead, you can save the last object in an variable, so that the last object instead of the whole array is captured by the block. Note that capturing the array by the block doesn't do any harm. It might just defer freeing some memory.

So you could do (@ChrisH's version plus `copy`):

- (void)fetchObjectsFromServerWithObjectIDs:(NSArray*)objectIDs {

    typeof(self) __weak blockSelf = self;
    NSArray *copiedIDs = [objectIDs copy];
    __block NSUInteger count = [copiedIDs count];

    for(NSString *objectID in copiedIDs) {
        [self fetchObjectDataForObjectID:objectID withSuccessBlock:^{
            if(--count == 0) {
                [blockSelf.delegate finishedFetching]; 
            }
        }];
    }
}

or:

- (void)fetchObjectsFromServerWithObjectIDs:(NSArray*)objectIDs {

    typeof(self) __weak blockSelf = self;
    NSArray *copiedIDs = [objectIDs copy];
    id lastObject = [copiedID lastObject];

    for(NSString *objectID in copiedIDs) {
        [self fetchObjectDataForObjectID:objectID withSuccessBlock:^{
            if(objectID == lastObject) {
                [blockSelf.delegate finishedFetching]; 
            }
        }];
    }
}

Problem

I have a simple class that exposes a method that makes multiple server calls to fetch object data for a list of object IDs. That method enumerates through the list of object IDs and makes individual server calls, shown below... ``` @implementation MyClass - (void)fetchObjectsFromServerWithObjectIDs:(NSArray*)objectIDs { typeof(self) __weak blockSelf = self; for(NSString *objectID in objectIDs) { [self fetchObjectDataForObjectID:objectID withSuccessBlock:^{ if(objectID == [objectIDs lastObject]) { //<---will this work? [blockSelf.delegate finishedFetching]; } }]; } } - (void)fetchObjectDataForObjectID:(NSString*)objectID withSuccessBlock:(void (^)())successBlock { void (^successWrapperBlock)(void) = ^{ //Some processing of returned data is done here if(successBlock) { successBlock(); } }; [HTTPClient fetchObjectDataWithObjectID:objectID withSuccessBlock:successWrapperBlock failureBlock:nil]; } @end ``` ...I'm trying to figure out the best way to check if the last `successBlock` is being executed so I can tell the delegate it's finished fetching the data. I have a comment in the code "<---Will this work?" marking the statement that's doing the check, but it doesn't feel safe to me since the block is asynchronous and `if(objectID == [objectIDs lastObject])` could be true when the first successBock if executed. How can I determine when the last `successBlock` is being executed? Thanks in advance for your wisdom!

Original source