How do I return a value from a Helper Class to a view controller?

江枫思渺然 提交于 2019-12-25 16:47:04

问题


I have a viewcontroller that calls a HelperClass class method in its viewDidLoad like so:

- (void)viewDidLoad{
    [super viewDidLoad];

    self.usersArray = [SantiappsHelper fetchUsers];    
}

That class method looks like this:

+(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;

}

The responseString is printed out just fine. But how do I return that value to my view controller? Because its a tableview controller which already loads its tableview before any of the data is gotten.


回答1:


The actual question is "How is a result returned from an asynchronous method?"

Say, you have an asynchronous task "doSomethingAsync" (which is a class method or instance method or a function, but this doesn't really matter).

The familiar synchronous form "doSomething" would simply return the result and can be declared as follows:

- (Result*) doSomething;

The equivalent asynchronous task "doSomethingAsync" can be declared using a completion handler:

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

Example:

Suppose a class "MyClass" defines a property "result" which will be initialized from the result of an asynchronous class method (class Foo). You retrieve the result in the method "fetchResult":

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

It may take a while to grasp what's going on here and it requires you to "think asynchronous" ;)

The important thing to realize is, that the completion handler is a block - which is defined inline and treated as if it were a normal object. The block is created by the call site and passed as an argument for the parameter completionHandler to the doSomethingAsync: method. The block itself defines the actions to take when the asynchronous task completes.

On the other hand, the asynchronous method internally must keep a reference to this block until it completes. Then, it must call the block and provide its result as an argument to the parameter result of the completion block.


There are other forms to "return" the result of an asynchronous function. One common pattern is to use a Future or a Promise. A Future or Promise simply represents the eventual result of an asynchronous function. It is an object that can be immediately returned from the asynchronous function - but its value (the result of the asynchronous task) is available only later when the asynchronous task finished. The task MUST eventually set a value for the promise when it finishes. This is called "resolving". That means, that the task must keep a reference to that returned promise object, and finally "resolve" it with either a value meaning success or a value meaning failure.

Assuming there is such a class "Promise", this would let you declare asynchronous methods like this:

- (Promise*) doSomethingAsync;

An implementation of a Promise may be fully support the "asynchronous model". In order to retrieve the result, you simply define what to do when the result is available. A particular implementation of a Promise may accomplish this for example:

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

Notice the "then", which is actually a property of the class Promise that returns a block:

@property then_block_t then;

This returned block of type "then_block_t" is immediately called via:

promise.then(...)  

Much like:

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

but shorter.

The block of type "then_block_t" has a parameter which is a completion block which will be invoked by the promise when the result is eventually available. The completion block is defined inline:

^(Result* result){ ... }

As you can see, the completion block has a parameter result which is the actual result of the asynchronous method.

OK, now your head may spin ;)

But now, go back to the example

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

which simply reads:

  • "Start asynchronous method [Foo doSomethingAsync] and return a promise.

  • When finished then execute the block where the result of task "doSomethingAsync" is passed with the argument result."

You can it write even shorter:

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

which is akin to the form with the completion handler:

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

The most important feature of a Promise, though, is that it allows us to "chain" two or more asynchronous tasks together. This is made possible since the block of type then_block_t which is returned from the property then has a return value of type Promise.

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

I'm pretty sure your head is spinning now in high frequency ;) - thus, an example will make this clear (hopefully):

Suppose you have two asynchronous methods: asyncA and asyncB. The first requires an input, processes it asynchronously and produces a result. The second method asyncB should take this result, process it asynchronously and finally print out @"OK" or an NSError - if something went wrong:

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

This reads:

  • "Asynchronously perform task "asyncA".

  • When finished then asynchronously perform task "asyncB".

  • If finished, then print out result."

You may notice that a handler will return a Promise object in the statement

return [self asyncB:outA];.

This will establish the "chain" form task "asyncA" to "asyncB". The eventual "value" of the returned promise will then appear as the result parameter in next handler.

A handler may also return an immediate result which happens to end up as the result parameter in the next handler as well.


An actual implementation in Objective-C differs slightly, in that the *then_block_t* has two parameters: one for the success case and one for the failure case:

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

I left this out in the former samples for brevity. An actual implementation would look like this:

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

Anther cool feature of promises is that errors will be forwarded through the chain of promises. That means, one can have multiple "chained" tasks say, A, B, C, D where only the success handler is defined. The last handler (pair) defines an error handler. If an error occurs in the first async task - that error will be forwarded through all promises until an error handler finally handles it. Success handlers will only be called when the task succeeded, and the error handler will only be called when the task failed:

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

There's more regarding Promises, but well beyond this SO answer.

An example for an implementation can be found here: RXPromise




回答2:


I would suggest the following:

Add the following to SantiappsHelper.h

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

In viewDidLoad, change

self.usersArray = [SantiappsHelper fetchUsers];    

to

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

Change

+(NSArray *)fetchUsers{

to

+(void)fetchUsersWithCompletionHandler:(Handler)handler {

in both .m and .h files.

And in this method, right after

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

add

if (handler)
    handler(usersArray);

Remove

return usersArray;

I think that should do. Also, execute the handler block on the main thread if that's desired.



来源:https://stackoverflow.com/questions/17416683/how-do-i-return-a-value-from-a-helper-class-to-a-view-controller

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!