Подтвердить что ты не робот

Как обработчик завершения работает в iOS?

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

Итак, я видел этот код в Интернете о старой структуре твиттера:

[twitterFeed performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
        if (!error) {
            self.successLabel.text = @"Tweeted Successfully";
            [self playTweetSound];
        } else {
            // Show alert
        }
        // Stop indicator 
        sharedApplication.networkActivityIndicatorVisible = NO;
    }];

Здесь мы вызываем метод, который делает stuff (выполняет TWRequest) и возвращает, когда закончил с responseData и urlResponse и ошибкой. Только когда он возвращает, он выполняет блок, который предоставляет тесты и останавливает индикатор активности. ИДЕАЛЬНОЕ!

Теперь это настройка, которую я использую для другого приложения, которое работает, но я пытаюсь собрать фрагменты:

@interface
Define an ivar
typedef void (^Handler)(NSArray *users);
Declare the method
+(void)fetchUsersWithCompletionHandler:(Handler)handler;

@implementation
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
    //...Code to create NSURLRequest omitted...
    __block NSArray *usersArray = [[NSArray alloc] init];

    //A. Executes the request 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{

        // Peform the request
        NSURLResponse *response;
        NSError *error = nil;
        NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
                                                     returningResponse:&response
                                                                 error:&error];
        // Deal with your error
        if (error) {
            }
            NSLog(@"Error %@", error);
            return;
        }
        // Else deal with data
        NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
        usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];

        // Checks for handler & returns usersArray to main thread - but where does handler come from & how does it know to wait tip usersArray is populated?
        if (handler){
            dispatch_sync(dispatch_get_main_queue(), ^{
            handler(usersArray);
            });
        }
    });
}

Вот мое понимание:

  • fetchUsersWithCompletionHandler, очевидно, является гомологом executeRequestWithHandler
  • К сожалению, это немного сложнее, потому что есть вызов GCD на пути...

Но в основном, запрос выполняется и обрабатывается ошибка, данные обрабатываются, а затем проверяется обработчик. Мой вопрос: как эта часть обработчика работает? Я понимаю, что если он существует, он отправит обратно в основную очередь и вернет userArray. Но как он знает, ждать, пока userArray будет заполнен? Я предполагаю, что меня смущает тот факт, что метод: блок в этом случае имеет другой блок внутри него, вызов dispatch_async. Я предполагаю, что Im ищет логику, которая на самом деле делает вещи и знает, КОГДА возвращать responseData и urlResponse. Я знаю, что это не то же приложение, но я не могу видеть код для выполненияRequestWithHandler.

4b9b3361

Ответ 1

В основном в этом случае он работает так:

  • Вы вызываете fetchUsersWithCompletionHandler: из любого потока, который вам нравится (возможно, основного).
  • Он инициализирует NSURLRequest, затем вызывает: dispatch_async (dispatch_get_global_queue... который в основном создает блок и планирует его для обработки в фоновом режиме.
  • Поскольку это dispath_async, текущий поток оставляет метод fetchUsersWithCompletionHandler:.

    ...
    время проходит, пока фоновая очередь не имеет свободных ресурсов
    ...

  • И теперь, когда фоновая очередь бесплатна, он расходует запланированную операцию (Примечание: он выполняет запрос синхронный - так что он ждет данных):

    NSURLResponse *response;
    NSError *error = nil;
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
                                                 returningResponse:&response
                                                             error:&error];
    ...
    
  • Как только данные наступят, будет добавлен userArray.

  • Код продолжается до этой части:

    if (handler){
        dispatch_sync(dispatch_get_main_queue(), ^{
            handler(usersArray);
        });
    }
    
  • Теперь, если мы указали обработчик, он планирует блок для вызова в основной очереди. Это dispatch_sync, поэтому выполнение текущего потока не будет продолжаться до тех пор, пока основной блок не будет выполнен с блоком. На этом этапе фоновый поток терпеливо ждет.

    ...
    еще один минус проходит ...

  • Теперь главная очередь имеет несколько свободных ресурсов, поэтому она потребляет выше блока и выполняет этот код (передавая ранее заполненные пользователиArray в обработчик):

    handler(usersArray);
    
  • Как только это будет сделано, он возвращается из блока и продолжает потреблять все, что находится в основной очереди.

  • Так как основной поток выполняется с блоком, также может происходить и фоновый поток (застрявший в dispatch_sync). В этом случае он просто возвращается из блока.

Изменить: Что касается вопросов, которые вы задали:

  • Не похоже, что основная/фоновая очередь будет всегда занята, это просто возможно. (предполагая, что фоновая очередь не поддерживает параллельные операции, такие как основной). Представьте следующий код, который выполняется в основном потоке:

        dispatch_async(dispatch_get_main_queue(), ^{
            //here task #1 that takes 10 seconds to run
            NSLog(@"Task #1 finished");
        });
        NSLog(@"Task #1 scheduled");
    
        dispatch_async(dispatch_get_main_queue(), ^{
            //here task #2 that takes 5s to run
            NSLog(@"Task #2 finished");
        });
        NSLog(@"Task #2 scheduled");
    

Поскольку оба являются вызовами dispatch_async, вы планируете их для выполнения один за другим. Но задача № 2 немедленно не будет обрабатываться главной очередью, так как сначала она должна покинуть текущий цикл выполнения, во-вторых, она должна сначала завершить задачу № 1.

Таким образом, вывод журнала будет таким:

Task #1 scheduled
Task #2 scheduled
Task #1 finished
Task #2 finished

2. У вас есть:

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

Что объявляет block typedefe'd как Handler, у которого есть возвращаемый тип void, и который принимает NSArray * как параметр.

Позже у вас есть ваша функция:

+(void)fetchUsersWithCompletionHandler:(Handler)handler

Что берется в качестве блока параметров типа Handler и разрешает доступ к нему с использованием локального имени Handler.

И шаг № 8:

handler(usersArray);

который непосредственно вызывает блок Handler (например, вы вызывали какую-либо функцию C/С++) и передает usersArray в качестве параметра для него.