gpt4 book ai didi

ios - 管理多个 block 调用的最佳方式

转载 作者:行者123 更新时间:2023-12-01 17:54:40 26 4
gpt4 key购买 nike

我正在开发一个应用程序,当它开始执行时,它必须从 webService、类别、加载图像(有时会更改)、“如何使用”信息(也可以在服务器、客户端规范中更改.. )。为了获得这些数据,我调用了一些这样的方法(我有四种类似的方法,我需要一种方法):

-(void) loadAppInfo
{
__weak typeof(self) weakSelf = self;
completionBlock = ^(BOOL error, NSError* aError) {
if (error) {
// Lo que sea si falla..
}
[weakSelf.view hideToastActivity];

};

[self.view makeToastActivity];
[wpNetManager getApplicationInfoWithCompletionBlock:completionBlock];
}

在我的网络管理器中,我有这样的方法:
- (void)getApplicationInfoWithCompletionBlock:(CompletionBlock)completionBlock
{
NSString * lang = @"es";//[[NSLocale preferredLanguages] objectAtIndex:0];
NSString *urlWithString = [kAPIInfoScreens stringByAppendingString:lang];
NSMutableURLRequest *request = nil;
request = [self requestWithMethod:@"GET" path:urlWithString parameters:nil];

AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[self registerHTTPOperationClass:[AFHTTPRequestOperation class]];

[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Print the response body in text
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:nil];
NSDictionary *informations = [json objectForKey:kTagInfoSplash];
if([json count]!= 0){
for (NSDictionary *infoDic in informations) {
Info *info = [Info getInfoByTitle:[infoDic objectForKey:kTagInfoTitle]];
if (info) {
// [User updateUserWithDictionary:dic];
} else {
[Info insertInfoWithDictionary:infoDic];
}
}
[wpCoreDataManager saveContext];
}

if (completionBlock) {
completionBlock(NO, nil);
}

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error Registro: %@", error);
if (completionBlock) {
completionBlock(YES, error);
}

}];
[self enqueueHTTPRequestOperation:operation];
}

所以我所做的是在 viewDidLoad 中调用这个方法:
[self loadAppInfo];
[self loadCountriesFromJson];
[self loadCategoriesFromWS];
[self loadSplashFromWS];

所以,与其一一调用这些方法。我想我可以使用 GCD 来管理这个,同时调用加载图像,直到一切都完成,然后调用下一个 ViewController。我相信这是一个很好的解决方案吗?如果是问题是我不知道如何向 gcd 添加一些块。

我正在尝试这样做,而不是在 ViewDidLoad 中调用他的最后四个方法.但它的工作很奇怪:
-(void)myBackGroundTask
{
[self.view makeToastActivity];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self loadAppInfo];
[self loadCountriesFromJson];
[self loadCategoriesFromWS];
[self loadSplashDataFromWS ];
dispatch_async(dispatch_get_main_queue(), ^{
[self.view hideToastActivity];
[self nextController];
});
});

}
[self nextController]方法在我将所有内容保存在 Core Data 之前被调用我有错误..

最佳答案

因为你所有的四种方法

[self loadAppInfo];
[self loadCountriesFromJson];
[self loadCategoriesFromWS];
[self loadSplashFromWS];

是异步的,应该清楚为什么声明
[self nextController];
在这四个方法完成之前执行。对?

因此,当异步方法完成时,会调用完成处理程序。太糟糕了,您的异步方法都没有完成处理程序。 ;)

解决这个问题的关键似乎是你的异步方法有完成处理程序:
typedef void (^completion_t)(id result, NSError* error);

- (void) loadAppInfo:(completion_t)completionHandler;
- (void) loadCountriesFromJson:(completion_t)completionHandler;
- (void) loadCategoriesFromWS:(completion_t)completionHandler;
- (void) loadSplashFromWS:(completion_t)completionHandler;

看来,您想同时启动所有四个异步方法。

您必须如何以及何时调用语句 [self nextController]取决于这个调用对上述四种异步方法的最终结果是否有任何依赖。

例如,您可以声明:

A. [self nextController]应在 loadAppInfo: 时执行成功完成。所有其他异步方法都无关紧要。

解决方案如下所示:
    [self loadAppInfo:^(id result, NSError*error){
if (error == nil) {
[self nextController];
}
}];
[self loadCountriesFromJson:nil];
[self loadCategoriesFromWS:nil];
[self loadSplashFromWS:nil];

如果上述语句仅依赖于其中一种方法,则解决方案非常明显且简单。当您有这样的要求时,它会立即变得更加复杂:

B. [self nextController]当所有四种异步方法都成功完成(或不止一种,并且所有其他方法都无关紧要)时,应执行。

有几种方法可以解决这个问题。一种方法是使用调度组或信号量和一些状态变量以及调度队列来确保并发性。然而,这是相当复杂的,最终会导致阻塞线程,无法取消,而且也很不理想(此外它看起来也很hackish)。因此,我不会讨论该解决方案。

使用 NSOperation 和依赖

另一种方法是利用 NSOperation的依赖。这需要将每个异步方法包装成一个 NSOperation子类。您的方法已经是异步的,这意味着您在设计子类时需要考虑到这一点。

由于只能建立从一个 NSOperation 到另一个 NSOperation 的依赖关系,因此您还需要为您的语句创建一个 NSOperation 子类
[self nextController]
它需要被包装到它自己的 NSOperation 子类中。

假设你正确地子类化 NSOperation ,在一天结束时,你会得到五个模块和五个头文件:
LoadAppInfoOperation.h, LoadAppInfoOperation.m,
LoadCountriesFromJsonOperation.h, LoadCountriesFromJsonOperation.m,
LoadCategoriesFromWSOperation.h, LoadCategoriesFromWSOperation.m,
LoadSplashFromWSOperation.h, LoadSplashFromWSOperation.m
NextControllerOperation.h, NextControllerOperation.m

B. NextControllerOperation应在所有四个操作成功完成时启动:

在代码中,这看起来如下:
LoadAppInfoOperation* op1 = ...;
LoadCountriesFromJsonOperation* op2 = ...;
LoadCategoriesFromWSOperation* op3 = ...;
LoadSplashFromWSOperation* op4 = ...;

NextControllerOperation* controllerOp = ...;

[controllerOp addDependency:op1];
[controllerOp addDependency:op2];
[controllerOp addDependency:op3];
[controllerOp addDependency:op4];

NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation: op1];
[queue addOperation: op2];
[queue addOperation: op3];
[queue addOperation: op4];
[queue addOperation: controllerOp];

看起来不错?不?

一个更有吸引力的方法:Promises

如果此解决方案与 NSOperations看起来不太好,太详细了(五个 NSOperation 子类!)或其他什么,这里有一个更有吸引力的方法,它使用实现 Promise 的第三方库。

在我解释 Promises 的工作原理以及它们的用途之前(请参阅 wiki 以获得更一般的描述),我想在这里展示最终的代码,并解释如何在以后到达那里。

披露:这里的示例代码使用了第三方库 RXPromise它根据 Promise/A+ specification 实现了一个 Promise .我是 RXPromise 的作者图书馆。

还有一些在 Objective-C 中实现的 Promise 库,但你还是可以看看 RXPromise ;)(见下面的链接)

关键是创建返回 promise 的异步方法。假设您的所有方法现在都是异步的并且具有如下签名:
- (RXPromise*) doSomethingAsync;

然后,您的最终代码将如下所示:
// Create an array of promises, representing the eventual result of each task:

NSArray* allTasks = @[
[self loadAppInfo],
[self loadCountriesFromJson],
[self loadCategoriesFromWS],
[self loadSplashFromWS]
];
...

上面的语句是启动多个任务并将它们的结果对象( promise )保存在数组中的一种非常简短的形式。换句话说,数组 allTask​​s 包含其任务已启动并且现在全部并发运行的 promise 。

现在,我们继续并定义当此数组中的所有任务成功完成或任何任务失败时会发生什么。这里我们使用辅助类方法 all: :
...
[RXPromise all: allTasks]
.then(^id(id results){
// Success handler block
// Parameter results is an array of the eventual result
// of each task - in the same order
... // do something with the results
return nil;
},^id(NSError*error){
// Error handler block
// error is the error of the failed task
NSLog(@"Error: %@, error");
return nil;
});

请参阅上面代码中的注释以了解成功和错误处理程序(在所有任务完成后调用)是如何使用“晦涩的” then 定义的。 .

解释如下:

解释:

下面的代码使用 RXPromise 库。您可以获取 RXPromise Library的源代码可在 GitHub 上获得。

还有一些其他的实现( SHXPromiseOMPromises 和更多),只要稍加努力,就可以将下面的代码移植到其他 promise 库中。

首先,您需要一个异步方法的变体,如下所示:
- (RXPromise*) loadAppInfo;
- (RXPromise*) loadCountriesFromJson;
- (RXPromise*) loadCategoriesFromWS;
- (RXPromise*) loadSplashFromWS;

在这里,请注意异步方法没有完成处理程序。我们不需要这个,因为返回的对象——一个 Promise——代表异步任务的最终结果。当任务失败时,这个结果也可能是一个错误。

我重构了您的原始方法,以便更好地利用 Promise 的力量:

异步任务将创建 promise ,它最终必须通过 fulfillWithValue: 使用最终结果“解决”它。 ,或者当它失败时,通过 rejectWithReason: 出现错误.见下文 RXPromise被创建,立即从异步方法返回,并在任务完成或失败后“解决”。

在这里,您的方法 getApplicationInfo返回一个最终值为 HTTP 响应数据的 promise ,即 NSData包含 JSON(或可能是错误):
- (RXPromise*)getApplicationInfo
{
RXPromise* promise = [[RXPromise alloc] init];
NSString * lang = @"es";//[[NSLocale preferredLanguages] objectAtIndex:0];
NSString *urlWithString = [kAPIInfoScreens stringByAppendingString:lang];
NSMutableURLRequest *request = nil;
request = [self requestWithMethod:@"GET" path:urlWithString parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[self registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[promise fulfillWithValue:responseObject]
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[promise rejectWithReason:error];
}];
[self enqueueHTTPRequestOperation:operation];

return promise;
}

关于 Promise 的一些进一步说明:

客户端可以通过使用属性 then 注册处理程序块来分别获得最终结果和错误。 :
promise.then(<success_handler>, <error_handler>);

处理程序或可选,但您通常设置处理结果的一个或两个。

注意:使用 RXPromise,您可以随时随地注册处理程序块,数量不限! RXPromise 是完全线程安全的。您只需要在某处或根据需要保留对 promise 的强引用。但是,即使在设置处理程序时,您也不需要保留引用。

处理程序块将在私有(private)队列上执行。这意味着,您不知道将在其中执行处理程序的执行上下文,即线程,除非您使用此变体:
promise.thenOn(dispatch_queue, <success_handler>, <error_handler>);

在这里, dispatch_queue指定将执行处理程序(成功或错误处理程序)的队列。

两个或多个异步任务可以随后执行(也称为链接),其中每个任务产生一个结果,该结果成为后续任务的输入。

两个异步方法的“链接”的简短形式如下所示:
RXPromise* finalResult = [self asyncA]
.then(^id(id result){
return [self asyncBWithResult:result]
}, nil);

在这里, asyncBWithResult:只会在 asyncA 之后执行已经成功完成。上面的表达式返回一个 Promise finalResult,它代表什么的最终结果 asyncBWithResult:当它完成时“返回”作为结果,或者它包含来自链中失败的任何任务的错误。

回到你的问题:

您的方法 loadAppInfo现在调用异步方法 getApplicationInfo以获取JSON数据。成功后,它会解析它,从中创建托管对象并保存托管对象上下文。
它返回一个 promise ,其值是保存对象的托管对象上下文:
- (RXPromise*) loadAppInfo {
RXPromise* promise = [[RXPromise alloc] init];
[self getApplicationInfo]
.then(^(id responseObject){
NSError* err;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:&err];
if (json == nil) {
return err;
}
else {
[wpCoreDataManager.managedObjectContext performBlock:^{
NSDictionary *informations = [json objectForKey:kTagInfoSplash];
if([json count]!= 0){
for (NSDictionary *infoDic in informations) {
Info *info = [Info getInfoByTitle:[infoDic objectForKey:kTagInfoTitle]];
if (info) {
// [User updateUserWithDictionary:dic];
} else {
[Info insertInfoWithDictionary:infoDic];
}
}
[wpCoreDataManager saveContext]; // check error here!
[promise fulfillWithValue:wpCoreDataManager.managedObjectContext];
}
else {
[promise fulfillWithValue:nil]; // nothing saved
}
}];
}
}, nil);
return promise;
}

注意如何 performBlock已被用来确保托管对象与其托管对象上下文的执行上下文正确关联。此外,还使用了异步版本,它非常适合使用 Promise 的解决方案。

重构了这两个方法,它们仅执行您打算完成的任务,并且还重构了其他异步方法,这些方法现在返回与上述重构方法类似的 promise ,您现在可以完成您的任务,如开始所示。

关于ios - 管理多个 block 调用的最佳方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20098107/

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