0

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.

Tulon
  • 4,011
  • 6
  • 36
  • 56

1 Answers1

1

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

CRD
  • 52,522
  • 5
  • 70
  • 86
  • Thanks a lot for your nice and clear explanation. It was easy to understand. But the thing is `[self getChannelProfilePictureForChannelID` is still getting called after all 4 synchronize execution. What I mean is it already return the `completionBlock(youtubeVideo);`. Another thing I have noticed that in `getChannelProfilePictureForChannelID` this `[weakSelf getChannelProfileImageList:[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] completionBlock:` `completionBlock` didn't call at the time. It is called after getting all methods done in `initializeDictionary`. – Tulon Nov 12 '17 at 21:29
  • So how can I manage to call those further `asynchronize` `completionBlock` from here `getChannelProfilePictureForChannelID`, sort of synchronize way. – Tulon Nov 12 '17 at 21:37
  • @Tulon - Added an addendum, hope that helps. – CRD Nov 12 '17 at 22:01