Async call Objective C iphone

2019-01-31 15:01发布

I'm trying to get data from a website- xml. Everything works fine.

But the UIButton remains pressed until the xml data is returned and thus if theres a problem with the internet service, it cant be corrected and the app is virtually unusable.

here are the calls:

{
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    if(!appDelegate.XMLdataArray.count > 0){
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
        [appDelegate GetApps]; //function that retrieves data from Website and puts into the array - XMLdataArray.

    }
    XMLViewController *controller = [[XMLViewController alloc] initWithNibName:@"MedGearsApps" bundle:nil];
    [self.navigationController pushViewController:controller animated:YES];
    [controller release];
}

It works fine, but how can I make the view buttons functional with getting stuck. In other words, I just want the UIButton and other UIButtons to be functional whiles the thing works in the background.

I heard about performSelectorInMainThread but i cant put it to practice correctly

any help is appreciated :)

4条回答
▲ chillily
2楼-- · 2019-01-31 15:30

You don’t understand the threading model much and you’re probably going to shoot yourself in the foot if you start adding asynchronous code without really understanding what’s going on.

The code you wrote runs in the main application thread. But when you think about it, you don’t have to write no main function — you just implement the application delegate and the event callbacks (such as touch handlers) and somehow they run automatically when the time comes. This is not a magic, this is simply a Cocoa object called a Run Loop.

Run Loop is an object that receives all events, processes timers (as in NSTimer) and runs your code. Which means that when you, for example, do something when the user taps a button, the call tree looks a bit like this:

main thread running
    main run loop
        // fire timers
        // receive events — aha, here we have an event, let’s call the handler
        view::touchesBegan…
            // use tapped some button, let’s fire the callback
            someButton::touchUpInside
                yourCode

Now yourCode does what you want to do and the Run Loop continues running. But when your code takes too long to finish, such as in your case, the Run Loop has to wait and therefore the events will not get processed until your code finishes. This is what you see in your application.

To solve the situation you have to run the long operation in another thread. This is not very hard, but you’ll have to think of a few potential problems nevertheless. Running in another thread can be as easy as calling performSelectorInBackground:

[appDelegate performSelectorInBackground:@selector(GetApps) withObject:nil];

And now you have to think of a way to tell the application the data has been loaded, such as using a notification or calling a selector on the main thread. By the way: storing the data in the application delegate (or even using the application delegate for loading the data) is not very elegant solution, but that’s another story.

If you do choose the performSelectorInBackground solution, take a look at a related question about memory management in secondary threads. You’ll need your own autorelease pool so that you won’t leak autoreleased objects.


Updating the answer after some time – nowadays it’s usually best to run the code in background using Grand Central Dispatch:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // No explicit autorelease pool needed here.
    // The code runs in background, not strangling
    // the main run loop.
    [self doSomeLongOperation];
    dispatch_sync(dispatch_get_main_queue(), ^{
        // This will be called on the main thread, so that
        // you can update the UI, for example.
        [self longOperationDone];
    });
});
查看更多
倾城 Initia
3楼-- · 2019-01-31 15:39

You can make use of a background operation that gets pushed into the operation queue:

BGOperation *op = [[BGOperation alloc] init];
[[self operationQueue] addOperation:op];
[op release];

I've created specific "commands" that get executed in the background:

@implementation BGOperation

# pragma mark Memory Management

- (BGOperation *)init
{
if ((self = [super init]) != nil)
    /* nothing */;
return self;
}

- (void)dealloc
{
self.jobId = nil;
[super dealloc];
}

# pragma mark -
# pragma mark Background Operation

- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [appDelegate GetApps];
[pool release];
return;
}

@end

After completion it might be a good idea to send a notification to the main thread because the internal database has been changed.

查看更多
一纸荒年 Trace。
4楼-- · 2019-01-31 15:43

Use NSURLConnection's connectionWithRequest:delegate: method. This will cause the specified request to be sent asynchronously. The delegate should respond to connection:didReceiveResponse: and will be sent that message once the response is completely received.

查看更多
混吃等死
5楼-- · 2019-01-31 15:50

It looks as if you might be using NSURLConnection inside your getApps method. If so, you should convert it to an asynchronous call.

查看更多
登录 后发表回答