0

我有一个视图控制器,它在其 viewDidLoad 中调用 HelperClass 类方法,如下所示:

- (void)viewDidLoad{
    [super viewDidLoad];

    self.usersArray = [SantiappsHelper fetchUsers];    
}

该类方法如下所示:

+(NSArray *)fetchUsers{

NSString *urlString = [NSString stringWithFormat:@"http://www.myserver.com/myApp/getusers.php"];
NSURL *url = [NSURL URLWithString:urlString];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];

[request setHTTPMethod: @"GET"];

__block NSArray *usersArray = [[NSArray alloc] init];


dispatch_async(dispatch_get_main_queue(), ^{
    // Peform the request
    NSURLResponse *response;
    NSError *error = nil;
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
                                                 returningResponse:&response
                                                             error:&error];
    if (error) {
        // Deal with your error
        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
            NSLog(@"HTTP Error: %d %@", httpResponse.statusCode, error);
            return;
        }
        NSLog(@"Error %@", error);
        return;
    }

    NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
    NSLog(@"responseString fetchUsers %@", responseString);

    NSLog(@"inside of block");

    usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];

});
NSLog(@"outside of block");
return usersArray;

}

responseString 打印出来就好了。但是如何将该值返回给我的视图控制器?因为它是一个 tableview 控制器,它在获取任何数据之前已经加载了它的 tableview。

4

2 回答 2

4

实际的问题是“如何从异步方法返回结果?”

假设您有一个异步任务“doSomethingAsync”(它是类方法或实例方法或函数,但这并不重要)。

熟悉的同步形式 “doSomething”将简单地返回结果,可以声明如下:

- (Result*) doSomething;

可以使用完成处理程序声明等效的异步任务“doSomethingAsync” :

typedef void (^completion_block_t)(Result* result)
- (void) doSomethingAsync:(completion_block_t)completionHandler;

例子:

假设一个类“MyClass”定义了一个属性“result”,该属性将从异步类方法(Foo 类)的结果中初始化。您在“fetchResult”方法中检索结果:

- (void) fetchResult {
    [Foo doSomethingAsync:^(Result* result){
        self.result = result;
    }];
}

掌握这里发生的事情可能需要一段时间,它需要你“思考异步”;)

要意识到的重要一点是,完成处理程序是一个块 - 它被内联定义并被视为普通对象。该块由调用站点创建并作为参数completionHandler的参数传递给该doSomethingAsync:方法。块本身定义了异步任务完成时要执行的操作。

另一方面,异步方法在内部必须保持对这个块的引用,直到它完成。然后,它必须调用该块并将其结果作为参数提供给完成块的参数结果


还有其他形式可以“返回”异步函数的结果。一种常见的模式是使用FuturePromise。Future 或 Promise 仅表示异步函数的最终结果。它是一个可以立即从异步函数返回的对象- 但它的(异步任务的结果)仅在异步任务完成后才可用。任务最终必须在完成时为 Promise 设置一个值。这被称为“解决”。这意味着,任务必须保留对该返回的承诺对象的引用,并最终用一个表示成功的值“解决”它或一个值表示失败

假设有这样一个“Promise”类,这会让你声明这样的异步方法:

- (Promise*) doSomethingAsync;

Promise 的实现可能完全支持“异步模型”。为了检索结果,您只需定义结果可用时要执行的操作。Promise 的特定实现可以实现这一点,例如:

- (void) fetchResult {
    Promise* promise = [Foo doSomethingAsync];
    promise.then(^(Result* result){
        self.result = result;
    });
}

注意“then”,它实际上是Promise 类的一个属性,它返回一个

@property then_block_t then;

这个返回的“then_block_t”类型的块立即通过以下方式调用:

promise.then(...)  

很像:

then_block_t block = promise.then;
block( ... );

但更短。

“then_block_t”类型的块有一个参数,它是一个完成块,当结果最终可用时,承诺将调用它。完成块是内联定义的:

^(Result* result){ ... }

如您所见,完成块有一个参数结果,它是异步方法的实际结果。

好的,现在你的头可能会旋转;)

但是现在,回到例子

    Promise* promise = [Foo doSomethingAsync];
    promise.then(^(Result* result){
        self.result = result;
    });

简单地写着:

  • “启动异步方法 [Foo doSomethingAsync] 并返回一个承诺。

  • 完成后,执行任务“doSomethingAsync”的结果与参数result一起传递的块。”

你可以写得更短:

[Foo doSomethingAsync]
.then(^(Result* result) {
    self.result = result;
};

这类似于带有完成处理程序的表单:

[Foo doSomethingAsync:^(Result* result){
    self.result = result;
}];

然而,Promise 最重要的特性是它允许我们将两个或多个异步任务“链接”在一起。这是可能的,因为then_block_t从属性返回的类型块具有类型then的返回值Promise

typedef Promise* (^then_block_t)(completion_block_t onSuccess);

我很确定您的头现在正在高频旋转;)-因此,一个示例可以说明这一点(希望如此):

假设您有两个异步方法:asyncA 和 asyncB。第一个需要输入,异步处理并产生结果。第二种方法 asyncB 应该获取这个结果,异步处理它,最后打印出 @"OK" 或 NSError - 如果出现问题:

[self asyncA:input]
.then(^(OutputA* outA) {
    return [self asyncB:outA];
})
.then(^(OutputB* outB){
    NSLog(@"end result: %@", outB);
    return nil;
});

内容如下:

  • “异步执行任务“asyncA”。

  • 完成异步执行任务“asyncB”。

  • 如果完成,打印出结果。”

您可能会注意到,处理程序将在语句中返回一个Promise对象

return [self asyncB:outA];.

这将建立“链”表单任务“asyncA”到“asyncB”。返回的承诺的最终“值”将作为结果参数出现在下一个处理程序中。

处理程序也可能返回一个立即结果,该结果恰好作为下一个处理程序中的结果参数结束。


Objective-C 中的实际实现略有不同,因为 *then_block_t* 有两个参数:一个用于成功案例,一个用于失败案例:

typedef Promise* (^then_block_t)(completion_block_t onSuccess, failure_block_t onFailure);

为简洁起见,我在之前的示例中忽略了这一点。一个实际的实现应该是这样的:

[self asyncA:input]
.then(^(OutputA* out) {
    return [self asyncB:out];
}, nil)
.then(^(id result){
    NSLog(@"result: %@", result);
    return nil;
}, ^id(NSError*error){
    NSLog(@"ERROR: %@", error);
    return nil;
});

Promise 的另一个很酷的特性是错误将通过 Promise 链转发。这意味着,可以有多个“链式”任务,例如 A、B、C、D,其中只定义了成功处理程序。最后一个处理程序(对)定义了一个错误处理程序。如果第一个异步任务中发生错误 - 该错误将通过所有承诺转发,直到错误处理程序最终处理它。只有在任务成功时才会调用成功处理程序,而只有在任务失败时才会调用错误处理程序:

[self A]
.then(^(id result) {
    return [self B:result];
}, nil)
.then(^(id result) {
    return [self C:result];
}, nil)
.then(^(id result) {
    return [self D:result];
}, nil)

.then(^(id result) {
    NSLog(@"Success");
    return nil;
}, ^id(NSError*error){
    NSLog(@"ERROR: %@", error);
    return nil;
});

还有更多关于 Promises 的内容,但远远超出了这个 SO 答案。

可以在此处找到实现示例:RXPromise

于 2013-07-03T08:35:01.870 回答
1

我建议如下:

将以下内容添加到 SantiappsHelper.h

typedef void (^Handler)(NSArray *users);

在 viewDidLoad 中,更改

self.usersArray = [SantiappsHelper fetchUsers];    

[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
    self.usersArray = users;
}];

改变

+(NSArray *)fetchUsers{

+(void)fetchUsersWithCompletionHandler:(Handler)handler {

在 .m 和 .h 文件中。

在这种方法中,紧接着

usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];

添加

if (handler)
    handler(usersArray);

消除

return usersArray;

我认为应该这样做。此外,如果需要,请在主线程上执行处理程序块。

于 2013-07-02T03:25:00.570 回答