iOS Design: Using the delegate pattern in a librar

2019-05-20 22:16发布

问题:

I have a library project that uses ASIHTTPRequest to make URL requests and parse the responses. The library will be used by a separate iPhone app project.

If my iPhone controller code responds to a touch event, then calls into the library to make URL requests, how do I best perform the requests asynchronously?

In the library, if I use the delegate pattern for asynchronous requests as shown in the ASIHTTPRequest sample code, how do I return data from the library back to the calling code in the iPhone controller?

If I instead make synchronous URL requests with ASIHTTPRequest inside the library, what's the easiest way to put the calls to the library from the iPhone controller on a separate thread to avoid tying up the UI thread?

回答1:

I'm no ASIHTTPRequest expert (NSURLRequest has always done me fine), but from a quick poke at the code, it looks like you'd use its delegate and didFinishSelector properties to give it someone to tell when the URL request is finished. So, for example:

- (void)startURLRequest
{
    ASIHTTPRequest *myRequest;

    /* code to set the request up with your target URL, etc here */

    myRequest.delegate = self;
    myRequest.didFinishSelector = @selector(HTTPRequestDidFinish:);

    /* ... */

    [myRequest startAsynchronous];
}

- (void)HTTPRequestDidFinish:(ASIHTTPRequest *)request
{
    NSLog(@"Request %@ did finish, got data: %@", request, request.data);
    [myTargetForData didReceiveData:request.data fromURL:request.originalURL];
}

Apple explicitly recommend that you use the built-in runloop style mechanisms for asynchronous HTTP fetching, not separate threads. Using separate threads is likely to result in worse performance — at least in terms of battery life and/or device heat, even if it's still fast enough.

That said, as a learning point, by far the quickest way to switch something onto a separate thread and have it report back to the main thread (remember: UIKit objects may be messaged only from the main thread) is by changing this:

- (void)postResult:(NSString *)result
{
    instanceOfUILabel.text = result;
}

- (void)doExpensiveOperationOn:(NSString *)source
{
     /* lots of expensive processing here, and then... */
     [self postResult:result];
}

- (IBAction)userWantsOperationDone:(id)sender
{
     [self doExpensiveOperationOn:@"some value or another"];
}

Into this:

- (void)postResult:(NSString *)result
{
    instanceOfUILabel.text = result;
}

- (void)doExpensiveOperationOn:(NSString *)source
{
     /* we're on a thread without an autorelease pool now, probably we'll want one */
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

     /* lots of expensive processing here, and then... */

     /* in this simplified example, we assume that ownership of 'result' is here on this thread, possibly on the autorelease pool, so wait until postResult has definitely finished before doing anything that might release result */
     [self performSelectorOnMainThread:@selector(postResult:) withObject:result waitUntilDone:YES];

     [pool release];
}

- (IBAction)userWantsOperationDone:(id)sender
{
     [self performSelectorOnBackgroundThread:@selector(doExpensiveOperationOn:) withObject:@"some value or another"];
}

There's about a million possible concurrency errors you can make by just going threaded without thinking about it though, and in that example an obvious problem is that whatever triggered the IBAction can [probably] trigger it several more times before doExpensiveOperationOn has finished. Multithreading is not something to be dashed into lightly.



回答2:

For anyone's future reference, the easiest approach I found is to use the async request functionality built into ASIHTTPRequest, setting my library object as the delegate and setting the didFinishSelector: and didFailSelector: values to different methods inside my library for each request.

At the end of processing each response, I assign the parsed response (an NSString* or NSArray*) to a property of my library object instead of returning a value.

When my iOS view controller delegate is loaded, I add a change observer to each of the properties in the library using Key-Value Observing. When the response is parsed and assigned to the property in the library, the observeValueForKeyPath:ofObject:change:context: method is called in the code of my view controller delegate, and from there I can figure out which property was changed and therefore what UI needs to be updated.