【问题标题】:Nested completionBlock is not getting called objc嵌套的完成块没有被称为 objc
【发布时间】:2017-11-12 20:21:58
【问题描述】:

这是我的嵌套块,请看一下:

- (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);
}

问题出在这个- (void)initializeDictionary:(NSDictionary *)dictionary completionBlock:(void (^)(YoutubeVideo *)) completionBlock { } 块中,有下面的块

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

当这行代码没有执行时,块返回值NSSting值。

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

它在执行其余代码后被调用:

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

所以值没有插入到我的对象模型中。 请给我一个建议。提前致谢。
祝你有美好的一天。

【问题讨论】:

    标签: objective-c thread-safety grand-central-dispatch completion-block


    【解决方案1】:

    在执行其余代码后被调用

    您将异步执行与期望代码将同步执行混为一谈:

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

    这是一个典型的异步方法声明,其中completionBlock 参数应该在initializeDictionary 的所有工作完成后异步调用

        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);
    

    然后你调用initializeDictionary:的完成块之前你知道getChannelProfilePictureForChannelID:已经完成并调用它的完成块。

    }
    

    如果你正在编写一个异步方法,它本身需要调用一个异步方法,那么你必须在嵌套异步方法的完成中完成你的方法......

    是的,这有点令人困惑!让我们重新安排你的方法:

    - (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"];
    

    先做所有同步赋值,再做嵌套异步调用:

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

    此时getChannelProfilePictureForChannelID 的完成块已经完成了你想要它做的事情,现在做initializeDictionary:getChannelProfilePictureForChannelID 完成后需要做的任何剩余工作。这种情况下不多,直接调用initializeDictionary:比赛即可:

              completionBlock(youtubeVideo);
           }
        ];
    }
    

    HTH

    附录

    从您的 cmets 来看,我认为您误解了异步链接的工作原理。让我们看看以下是否有帮助。

    你写的方法有格式:

    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
    

    调用此方法时,A、B、D、E 会依次执行,然后方法返回。你不知道 C 什么时候执行,也不能保证它会在 E 之前执行,事实上,异步网络调用很可能不会(所以你甚至不可能得到意外的正确性)。

    要执行一系列异步操作,您需要通过延续块将它们链接起来。因此,您可以将方法更改为:

    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
    

    将 D & E 放入嵌套完成块中。现在,当您调用您的方法时,只有 A & B 在它返回之前执行。在稍后的某个时间点,嵌套的完成块被异步执行,C 和 D 被执行。最后执行E,原调用的完成块,从而完成工作。您现在已经保证了正确性,只有在嵌套的异步调用完成后才会执行 E。

    注意:我在阅读您的代码时注意到的是,在嵌套调用之后似乎不需要执行块 D(代码中的四个赋值),因此我将您的代码重新排列为:

    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
    

    将 D 提升到顶部。

    当您有一个本身依赖于另一个异步方法的异步方法时,这种异步调用链是基础 - 在每个阶段您都必须使用完成块链以正确的顺序执行代码。

    HTH

    【讨论】:

    • 非常感谢您清晰明了的解释。这很容易理解。但问题是[self getChannelProfilePictureForChannelID 在所有 4 次同步执行之后仍然被调用。我的意思是它已经返回了completionBlock(youtubeVideo);。我注意到的另一件事是 getChannelProfilePictureForChannelID 这个 [weakSelf getChannelProfileImageList:[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] completionBlock: completionBlock 当时没有打电话。在 initializeDictionary 中完成所有方法后调用它。
    • 那么我怎样才能设法从这里getChannelProfilePictureForChannelID 进一步调用那些asynchronize completionBlock,有点同步方式。
    • @Tulon - 添加了附录,希望对您有所帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-25
    • 1970-01-01
    • 1970-01-01
    • 2012-08-05
    • 2017-08-17
    相关资源
    最近更新 更多