Асинхронный вызов Objective C iphone - PullRequest
9 голосов
/ 25 апреля 2010

Я пытаюсь получить данные с веб-сайта - XML. Все отлично работает.

Но кнопка UIB остается нажатой до тех пор, пока не будут возвращены данные XML, и, таким образом, если возникнет проблема с интернет-службой, ее невозможно исправить и приложение практически не будет использоваться.

вот звонки:

{
    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];
}

Работает нормально, но как сделать кнопки просмотра работоспособными при застревании. Другими словами, я просто хочу, чтобы кнопки UIB и другие кнопки UIB работали, пока все работает в фоновом режиме.

Я слышал о executeSelectorInMainThread, но не могу правильно применить его на практике

любая помощь приветствуется:)

Ответы [ 4 ]

36 голосов
/ 25 апреля 2010

Вы не очень хорошо понимаете модель потоков, и вы, вероятно, собираетесь выстрелить себе в ногу, если начнете добавлять асинхронный код, не понимая, что происходит.

Код, который вы написали, выполняется восновная ветка приложения.Но когда вы думаете об этом, вам не нужно писать никакой функции main - вы просто реализуете делегат приложения и обратные вызовы событий (например, сенсорные обработчики), и они каким-то образом запускаются автоматически, когда приходит время.Это не волшебство, это просто объект Какао, называемый Run Loop .

Run Loop - это объект, который получает все события, обрабатывает таймеры (как в NSTimer) и запускаетваш код.Это означает, что когда вы, например, что-то делаете, когда пользователь нажимает кнопку, дерево вызовов выглядит примерно так:

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

Теперь yourCode делает то, что вы хотите, и Run Loopпродолжает работать.Но когда вашему коду требуется слишком много времени для завершения, как, например, в вашем случае, цикл выполнения должен ждать, и поэтому события не будут обработаны, пока ваш код не завершится.Это то, что вы видите в своем приложении.

Чтобы разрешить ситуацию, вы должны запустить длинную операцию в другом потоке.Это не очень сложно, но вам все же придется подумать о нескольких потенциальных проблемах.Запуск в другом потоке может быть так же просто, как вызов performSelectorInBackground:

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

И теперь вам нужно придумать способ сообщить приложению, что данные загружены, например:используя уведомление или вызывая селектор в главном потоке.Кстати, хранение данных в делегате приложения (или даже использование делегата приложения для загрузки данных) не очень элегантное решение, но это уже другая история.

Если вы выберете решение performSelectorInBackground,взгляните на связанный вопрос о управлении памятью во вторичных потоках .Вам потребуется собственный пул автоматического выпуска, чтобы не допускать утечки автоматически выпущенных объектов.


Обновление ответа через некоторое время - в настоящее время обычно лучше всего запускать код в фоновом режиме с помощью 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];
    });
});
3 голосов
/ 25 апреля 2010

Используйте NSURLConnection connectionWithRequest: делегат: метод. Это приведет к тому, что указанный запрос будет отправлен асинхронно. Делегат должен ответить на соединение: didReceiveResponse: и ему будет отправлено это сообщение, когда ответ будет полностью получен.

0 голосов
/ 25 апреля 2010

Похоже, что вы можете использовать NSURLConnection внутри вашего метода getApps. Если это так, вы должны преобразовать его в асинхронный вызов.

0 голосов
/ 25 апреля 2010

Вы можете использовать фоновую операцию, которая помещается в очередь операций:

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

Я создал определенные «команды», которые выполняются в фоновом режиме:

@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;
}

@ конец

После завершения может быть хорошей идеей отправить уведомление в основной поток, поскольку внутренняя база данных была изменена.

...