Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 273
BeachRunnerFred Avatar asked Dec 11 '22 09:12

BeachRunnerFred


2 Answers

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:

  1. Call it once the last fetch has finished.
  2. 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]; 
            }
        }];
    }
}
like image 146
DarkDust Avatar answered Dec 13 '22 22:12

DarkDust


This is how I've handled the same situation in the past:

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

    typeof(self) __weak blockSelf = self;

    __block int c = [objectIDs count];

    for (NSString *objectID in objectIDs) {

        [self fetchObjectDataForObjectID:objectID withSuccessBlock:^{

            if (--c == 0) {
                [blockSelf.delegate finishedFetching]; 
            }

        }];
    }
}

It assumes that the array isn't going to be modified during iteration, which I would infer in this case as the object being passed is an immutable array.

like image 20
ChrisH Avatar answered Dec 13 '22 21:12

ChrisH