gpt4 book ai didi

ios - viewDidLoad多线程问题

转载 作者:行者123 更新时间:2023-11-29 13:00:23 25 4
gpt4 key购买 nike

我必须进行一个单独的api调用,该API会为每个我想获取有关信息的电影返回一些JSON数据。我试图遍历电影ID的数组,并在viewDidLoad方法中的每个ID上调用populateAssetObject。

如果进入调试模式并逐步执行for循环,它将正确填充所有5个标题的电影,但如果正常运行,则电影数组只是前两个对象。我认为这可能是某些多线程引起的?我并不是那个领域的专家,有人知道我的问题吗?

viewDidLoad:

_movies = [[NSMutableArray alloc] init];

for (NSString *curr in assetIDs) {
[self populateAssetObject:curr];
}

这是populateAssetObject方法
-(void)populateAssetObject:(NSString *)videoID {
NSString *urlString = [NSString stringWithFormat:@"[api url]", videoID];
NSURL *url = [NSURL URLWithString:restURLString];

NSData *data = [[NSData alloc] initWithContentsOfURL:url];
NSError *error = nil;

NSDictionary *contents = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];

OVDAsset *newAsset = [[OVDAsset alloc] init];
[newAsset setTitle:[contents valueForKey:@"title"]];
[newAsset setDescription:[contents valueForKey:@"longDescription"]];

[self.movies addObject:newAsset];
}

最佳答案

您的方法有一个重要问题:

您的方法populateAssetObject:是一个同步方法,它将访问远程资源。此方法将在主线程上执行,因此将阻止UI。

循环调用它只会使情况变得更糟。

您真正需要的是一个异步方法,该方法在后台线程中执行所有操作,而完成块则在整个操作完成后通知调用站点:

typedef void (^completion_t)();
- (void) populateAssetsWithURLs:(NSArray*) urls completion:(completion_t)completionHandler;

然后在 viewDidLoad中执行以下操作:
- (void) viewDidLoad {
[super viewDidLoad];
[self populateAssetsWithURLs:urls ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
}

棘手的部分是实现 populateAssetsWithURLs:completion:方法。

一种快速而肮脏的方法,如下所示:
- (void) populateAssetsWithURLs:(NSArray*) urls
completion:(completion_t)completionHandler
{
NSUInteger count = [urls count];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (NSUInteger i = 0; i < count; ++i) {
[self synchronousPopulateAssetObject:urls[i]];
}
if (completionHandler) {
completionHandler();
}
});
}

- (void) synchronousPopulateAssetObject:(NSString*)url {
...
}

这种方法有几个问题:

它阻塞至少一个线程(但可能更多),以等待结果。这种方法在系统资源方面效率低下-但它可能有效。

异步回路

更好的方法是采用异步设计。这种方法的棘手部分是您有一系列异步任务( asynchronousPopulateAssetObject),每个任务都需要异步启动,并且最终结果也只能异步获得。具有 for loop非常不适合使循环异步。

因此,您可能会想象这样的API,它可能是 NSArray的Category:

类别NSArray:
typedef void (^completion_t)(id result);

-(void) forEachPerformTask:(task_t)task completion:(completion_t)completionHandler;

请注意, tasktask_t类型的异步块,具有自己的完成处理程序作为参数:
typedef void (^task_t)(id input, completion_t);

该任务将异步应用于数组中的每个元素。处理完所有元素后,将通过调用方法forEachPerformTask中传递的完成处理程序来通知客户端。

您可以在GitHub Gist上找到完整的实现和简短示例: transform_each.m

很快,我将编辑答案并演示一种更为优雅的方法,该方法利用了一个帮助程序库,特别适合解决此类异步模式。

但在此之前,我将仅演示另一种使用 NSOperationQueue的方法:

NSOperationQueue
NSOperationQueue具有不可估量的优势,可以通过 取消来消除正在运行和待处理的异步任务。实际上,执行无法取消的操作列表的异步解决方案是不完整的解决方案,在大多数情况下可能根本不适用。

此外, NSOperationQueue可以同时执行其任务。可以使用 maxConcurrentOperationCount属性设置发生任务的数量。

使用 NSOperationQueue时会有一些变化,最简单的基本思想是:
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2;

for (NSString* url in urls) {
[queue addOperationWithBlock:^{
[self synchronousPopulateAssetObject:url];
}];
}

在此,操作队列配置为并行运行两个任务。将使用便捷的方法 addOperationWithBlock:创建一个操作,该方法从一个块动态创建一个 NSOperation对象。

任务将立即在for循环中入队。这与“分发方法”不同,其实现在Gist上显示。在“派遣方法”中,只有在前一个任务完成后,新任务才入队。这对系统资源非常友好。

此处的缺点是无法异步确定所有任务的完成时间。但是,有一种使用 waitUntilAllOperationsAreFinished方法的“阻止”解决方案。由于此方法 阻止了调用线程,并且由于我们需要异步方法 populateAssetsWithURLs:completion:,因此需要将同步方法包装为异步方法,如下所示:
- (void) populateAssetsWithURLs:(NSArray*) urls
queue:(NSOperationQueue*)queue
completion:(completion_t)completionHandler
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (NSString* url in urls) {
[queue addOperationWithBlock:^{
[self synchronousPopulateAssetObject:url];
}];
}
[queue waitUntilAllOperationsAreFinished];
if (completionHandler) {
completionHandler();
}
});
}

注意:由客户端提供队列。这是因为客户端可以随时将 cancelAllOperations发送到队列,以停止执行挂起的和正在运行的任务。

这里的缺点是,我们需要一个额外的线程,该线程被阻塞只是为了传递最终结果(可以将其作为完成处理程序中的参数传递)。

另一个缺点是,当使用便捷的方法 addOperationWithBlock:时,我们没有机会为异步任务指定完成处理程序。

使用便捷方法 addOperationWithBlock:时的另一个缺点是,我们没有获得设置其他 NSOperation对象的依赖关系所需的 NSOperation(请参阅NSOperation的官方文档中的 Operation Dependencies)。

如果我们想充分利用 NSOperationsNSOperationQueue的功能,我们将不得不更加详细。例如,有一个完成处理程序可以在操作队列处理完所有任务后通知 call 站点,但这是可行的-但这需要建立依赖关系,这需要在我们需要子类的 NSOperation 对象上,并且需要执行该代码以前是简单的异步任务。

但是,依赖项功能非常宝贵,我强烈建议您尝试使用它。

关于ios - viewDidLoad多线程问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19914106/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com