Nested completionBlock is not getting called objc

This is my nested block, please take have a look :

- (void)getVideoList:(NSDictionary*)videoData
     completionBlock:(void (^)(NSMutableArray *))
completionBlock {
    NSArray *videos = (NSArray*)[videoData objectForKey:@"items"];
    NSMutableArray* videoList = [[NSMutableArray alloc] init];

    for (NSDictionary *videoDetail in videos) {
        if (videoDetail[@"id"][@"videoId"]){
            [self initializeDictionary:videoDetail completionBlock:^(YoutubeVideo * utubeVideo) {
                [videoList addObject:utubeVideo];
//                NSLog(@"zuuudo %@", utubeVideo.channelProfileImageURL);
            }];
        }
    }
    completionBlock(videoList);
}

- (void)initializeDictionary:(NSDictionary *)dictionary completionBlock:(void (^)(YoutubeVideo *))
completionBlock {
    YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];

    youtubeVideo.videoTitle = dictionary[@"snippet"][@"title"];
    youtubeVideo.videoID = dictionary[@"id"][@"videoId"];
    youtubeVideo.channelID = dictionary[@"snippet"][@"channelId"];
    [self getChannelProfilePictureForChannelID:youtubeVideo.channelID completionBlock:^(NSMutableArray *channelList) {
        NSLog(@"[channelList objectAtIndex:0] %@", [channelList objectAtIndex:0]);
        youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
    }];
    youtubeVideo.channelTitle = dictionary[@"snippet"][@"channelTitle"];
    youtubeVideo.videoDescription = dictionary[@"snippet"][@"description"];
    youtubeVideo.pubDate = [self dateWithJSONString:dictionary[@"snippet"][@"publishedAt"]];
    youtubeVideo.thumbnailURL = dictionary[@"snippet"][@"thumbnails"]
    [@"high"][@"url"];
    completionBlock(youtubeVideo);
}

- (void)getChannelProfilePictureForChannelID:(NSString*)channelID completionBlock:(void (^)(NSMutableArray *))completionBlock
{
    NSString *URL = [NSString stringWithFormat:@"https://www.googleapis.com/youtube/v3/channels?part=snippet&fields=items/snippet/thumbnails/default&id=%@&key=%@", channelID, apiKey];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:[URL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]];

    NSURLSession *session = [NSURLSession sharedSession];
    [[session dataTaskWithRequest:request
                completionHandler:^(NSData *data,
                                    NSURLResponse *response,
                                    NSError *error) {
                    if (!error){
                        [self getChannelProfileImageList:[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] completionBlock:
                         ^(NSMutableArray * channelList) {
                             // return the final list
                             completionBlock(channelList);
                         }];
                    }
                    else {
                        // TODO: better error handling
                        NSLog(@"error = %@", error);
                    }
                }] resume];
}

- (void)getChannelProfileImageList:(NSDictionary*)channelData
     completionBlock:(void (^)(NSMutableArray *))
completionBlock {
    NSArray *channels = (NSArray*)[channelData objectForKey:@"items"];
    NSMutableArray *channelList = [[NSMutableArray alloc] init];

    for (NSDictionary *channelDetail in channels) {
        [self initializeDictionaryForChannelProfileImage:channelDetail completionBlock:^(NSString *chnlProfileImageURL) {
            [channelList addObject:chnlProfileImageURL];
        }];
        //[channelList addObject:[self initializeDictionaryForChannelProfileImage:channelDetail]];
        //[channelList addObject:[[YoutubeVideo alloc] initWithDictionaryForChannelProfileImage:channelDetail]];
    }
    completionBlock(channelList);
}

- (void)initializeDictionaryForChannelProfileImage:(NSDictionary *)dictionary completionBlock:(void (^)(NSString *))
completionBlock
{
    _channelProfileImageURL = dictionary[@"snippet"][@"thumbnails"]
    [@"default"][@"url"];

    completionBlock(_channelProfileImageURL);
}

Problem is in this - (void)initializeDictionary:(NSDictionary *)dictionary completionBlock:(void (^)(YoutubeVideo *)) completionBlock { } block, has the below block

[self getChannelProfilePictureForChannelID:youtubeVideo.channelID completionBlock:^(NSMutableArray *channelList) { NSLog(@"[channelList objectAtIndex:0] %@", [channelList objectAtIndex:0]); youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0]; }];

Where these line of code is not executing when the block return value NSSting value.

youtubeVideo.channelProfileImageURL = _channelProfileImageURL;
NSLog(@"youtubeVideo.channelProfileImageURL %@", youtubeVideo.channelProfileImageURL);

It is getting called after executing rest of the code:

    youtubeVideo.channelTitle = dictionary[@"snippet"][@"channelTitle"];
    youtubeVideo.videoDescription = dictionary[@"snippet"][@"description"];
    youtubeVideo.pubDate = [self dateWithJSONString:dictionary[@"snippet"][@"publishedAt"]];
    youtubeVideo.thumbnailURL = dictionary[@"snippet"][@"thumbnails"]
    [@"high"][@"url"];

So the value is not inserting in my object model. Please give me a suggestion. Thanks in advance.
Have a good day.

1 answer

  • answered 2017-11-12 20:50 CRD

    It is getting called after executing rest of the code

    You are mixing up asynchronous execution with an expectation that code will be executed synchronously:

    - (void)initializeDictionary:(NSDictionary *)dictionary 
                 completionBlock:(void (^)(YoutubeVideo *))completionBlock
    {
    

    This is a typical declaration for an asynchronous method where the completionBlock argument should be called asynchronously after the all the work of initializeDictionary has been completed.

        YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];
    
        youtubeVideo.videoTitle = dictionary[@"snippet"][@"title"];
        youtubeVideo.videoID = dictionary[@"id"][@"videoId"];
        youtubeVideo.channelID = dictionary[@"snippet"][@"channelId"];
    

    Three synchronous assignments.

        [self getChannelProfilePictureForChannelID:youtubeVideo.channelID 
                                   completionBlock:^(NSMutableArray *channelList)
           {
              NSLog(@"[channelList objectAtIndex:0] %@", [channelList objectAtIndex:0]);
              youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
           }
        ];
    

    This is a nested call to another asynchronous method, which will call its completion block after it has finished. At the point it returns it probably has not yet called its competition block.

        youtubeVideo.channelTitle = dictionary[@"snippet"][@"channelTitle"];
        youtubeVideo.videoDescription = dictionary[@"snippet"][@"description"];
        youtubeVideo.pubDate = [self dateWithJSONString:dictionary[@"snippet"][@"publishedAt"]];
        youtubeVideo.thumbnailURL = dictionary[@"snippet"][@"thumbnails"]
        [@"high"][@"url"];
    

    Four more synchronous assignments...

        completionBlock(youtubeVideo);
    

    And then you call the completion block of initializeDictionary: before you know that getChannelProfilePictureForChannelID: has completed and called its completion block.

    }
    

    If you are writing an asynchronous method which itself needs to call an asynchronous method then you have to complete your method in the nested asynchronous method's completion...

    Yes that's a bit confusing in words! Let's rearrange your method:

    - (void)initializeDictionary:(NSDictionary *)dictionary 
                 completionBlock:(void (^)(YoutubeVideo *))completionBlock
    {
        YoutubeVideo *youtubeVideo = [[YoutubeVideo alloc] init];
    
        youtubeVideo.videoTitle = dictionary[@"snippet"][@"title"];
        youtubeVideo.videoID = dictionary[@"id"][@"videoId"];
        youtubeVideo.channelID = dictionary[@"snippet"][@"channelId"];
    
        youtubeVideo.channelTitle = dictionary[@"snippet"][@"channelTitle"];
        youtubeVideo.videoDescription = dictionary[@"snippet"][@"description"];
        youtubeVideo.pubDate = [self dateWithJSONString:dictionary[@"snippet"][@"publishedAt"]];
        youtubeVideo.thumbnailURL = dictionary[@"snippet"][@"thumbnails"]
        [@"high"][@"url"];
    

    Do all the synchronous assignments first, then do the nested asynchronous call:

        [self getChannelProfilePictureForChannelID:youtubeVideo.channelID 
                                   completionBlock:^(NSMutableArray *channelList)
           {
              NSLog(@"[channelList objectAtIndex:0] %@", [channelList objectAtIndex:0]);
              youtubeVideo.channelProfileImageURL = [channelList objectAtIndex:0];
    

    At this point the completion block of getChannelProfilePictureForChannelID has done what you want it to, now do any remaining work that initializeDictionary: needs to do after getChannelProfilePictureForChannelID completes. This is not much in this case, just call initializeDictionary: competition:

              completionBlock(youtubeVideo);
           }
        ];
    }
    

    HTH

    Addendum

    From your comments I think you are misunderstanding how asynchronous chaining needs to work. Let's see if the following helps.

    The method you wrote has for format:

    A - block of work to do before async nested call
    B - async call
       nested async completion block
       C - block of work to do after nested call completes
    D - second block of work
    E - call our async completion block
    

    When you call this method A, B, D & E will execute in order and then the method will return. You've no idea when C will execute and there is no guarantee it will execute before E, indeed with async network calls in all probability it will not (so you're unlikely to even get accidental correctness).

    To do a sequence of async operations you need to chain them via the continuation blocks. So you can change your method to:

    A - block of work to do before async nested call
    B - async call
       nested async completion block
       C - block of work to do after nested call completes
       D - second block of work
       E - call our async completion block
    

    Putting D & E into the nested completion block. Now when you call your method only A & B execute before it returns. At some later point the nested completion block is executed asynchronously and C and D are executed. Finally E is executed, the completion block of the original call, thus completing the work. You've now guaranteed correctness, E will only be executed after the nested async call has completed.

    Note: What I noticed when reading your code was that block D (the set of four assignments in your code) did not seem to be required to be executed after the nested call so I rearranged your code as:

    A & D - block of work to do before async nested call
    B - async call
       nested async completion block
       C - block of work to do after nested call completes
       E - call our async completion block
    

    hoisting D to the top.

    This chaining of asynchronous calls is fundamental when you have an async method which itself relies on another async method – at every stage you must use the completion block chain to execute code it the correct order.

    HTH