2013-08-17 18 views
18

Próbuję zrozumieć bloki obsługi ukończeń &. Sądzę, że można używać bloków do wielu głębokich rzeczy programistycznych bez programów obsługi zakończenia, ale myślę, że rozumiem, że programy obsługi zakończenia są oparte na blokach. (Tak więc w zasadzie programy do obsługi zakończenia wymagają bloków, ale nie odwrotnie).Jak działa moduł obsługi zakończenia w systemie iOS?

Więc widziałem ten kod w internecie o starych ram Twitterze:

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

Tutaj wzywamy metodę, która robi rzeczy (wykonuje TWRequest) i powraca po zakończeniu z responseData & urlResponse & błędu. Dopiero po zwrocie wykonuje blok, który uzyskały testy, i zatrzymuje wskaźnik aktywności. IDEALNY!

Teraz jest to konfiguracja mam dla innej aplikacji, która działa, ale próbuję umieścić kawałki razem:

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

Oto moje rozumienie:

  1. fetchUsersWithCompletionHandler jest oczywiście homologa performRequestWithHandler
  2. Niestety jest to trochę bardziej skomplikowane, ponieważ istnieje wywołanie GCD w sposób ...

Zasadniczo żądanie jest wykonywane, a błąd jest przetwarzany, dane są przetwarzane, a następnie sprawdzany jest program obsługi. Moje pytanie brzmi: jak działa ta część obsługi? Rozumiem, że jeśli istnieje, to odeśle z powrotem do głównej kolejki i zwróci obiekt usersArray. Ale skąd wiadomo, że trzeba poczekać, aż zapełni się userArray? Domyślam się, że mylące jest to, że metoda: block w tym przypadku ma inny blok w sobie, wywołanie dispatch_async. Domyślam się, że to, czego szukam, to logika, która faktycznie robi rzeczy i wie, KIEDY zwracać responseData i urlResponse. Wiem, że to nie ta sama aplikacja, ale nie widzę kodu dla performRequestWithHandler.

Odpowiedz

29

Zasadniczo w tym przypadku to działa tak:

  1. zadzwonić fetchUsersWithCompletionHandler: od cokolwiek wątek lubisz (prawdopodobnie stanowić główny).
  2. Inicjuje NSURLRequest, następnie wywołuje. dispatch_async (dispatch_get_global_queue ... które zasadniczo tworzy blok, a rozkłady IT do przetwarzania na kolejce tle
  3. Ponieważ jest dispath_async, bieżący wątek opuszcza fetchUsersWithCompletionHandler: metoda.

    ...
    upływu czasu, aż kolejka tło ma kilka wolnych zasobów
    ...

  4. i teraz, gdy backgr ound kolejka jest za darmo, to zużywa zaplanowaną operację (Uwaga: Wykonuje synchronicznego żądanie - dlatego czeka na dane):

    NSURLResponse *response; 
    NSError *error = nil; 
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request 
                  returningResponse:&response 
                     error:&error]; 
    ... 
    
  5. Gdy dane pochodzą, wówczas usersArray jest wypełniana.

  6. Kod trwa do strony:

    if (handler){ 
        dispatch_sync(dispatch_get_main_queue(), ^{ 
         handler(usersArray); 
        }); 
    } 
    
  7. Teraz, jeśli mamy określone obsługi, to harmonogramy blokowania dla wywołania przy głównej kolejki. Jest to dispatch_sync, więc wykonanie na bieżącym wątku nie będzie kontynuowane, dopóki główny wątek nie zostanie wykonany z blokiem. W tym momencie wątek tła cierpliwie czeka.

    ...
    kolejna chwila przechodzi
    ...

  8. Teraz głównym kolejka ma jakieś wolne zasoby, więc zużywa powyżej bloku i wykonuje ten kod (przechodząc uprzednio zaludnionych usersArray do „obsługi”):

    handler(usersArray); 
    
  9. po to się robi, to zwraca się z bloku i nadal zużywa cokolwiek to jest w głównej kolejki.

  10. Ponieważ główny wątek jest wykonywany wraz z blokiem, również wątek tła (zatrzymany w trybie dispatch_sync) może przejść dalej. W tym przypadku po prostu wraca z bloku.

Edit: Co do pytań, zapytał:

  1. To nie tak jak główny kolejkę/tło będzie zawsze zajęty, to tylko może być. (zakładając, że kolejka tła nie obsługuje równoczesnych operacji, takich jak główna). Wyobraź sobie następujący kod, który jest wykonywany w głównym wątku:

    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"); 
    

Ponieważ oba są dispatch_async połączeń, zaplanować ich do realizacji jeden po drugim. Ale zadanie nr 2 nie zostanie natychmiast przetworzone przez główną kolejkę, ponieważ najpierw musi opuścić bieżącą pętlę wykonawczą, po drugie musi najpierw zakończyć zadanie nr 1.

więc wyjście dziennika będzie tak:

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

2.You mają:

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

Które deklaruje zablokować typedefe'd jak Handler który ma void typ zwracany oraz że akceptuje NSArray * jako parametr.

Później masz swoją funkcję:

+(void)fetchUsersWithCompletionHandler:(Handler)handler 

który trwa jako bloku parametrów typu Handler i umożliwić dostęp do niego za pomocą lokalnej nazwy handler.

I etap 8:

handler(usersArray); 

który właśnie bezpośrednio wywołuje handler blok (jakbyś wywołanie dowolnego C/C++ funkcja) i przechodzi usersArray jako parametr do niego.

+0

2 pytania: (1) dlaczego mówisz w kroku 3 "czas mija, aż kolejka tła ma trochę wolnych zasobów". Dlaczego NIE miałoby wolnych zasobów? to samo w krokach 7/8 z główną kolejką. Dlaczego NIE miałby wolnych zasobów i musiałby czekać? I najważniejsze, w jaki sposób handler "obsługuje" tablicę usersArray, aby ją odesłać? Jaka część kodu mówi handlerowi, aby przesłać dane do tego, kto go nazwał? – marciokoko

+0

Zobacz moją zmienioną odpowiedź. – deekay

+0

Jaka jest potrzeba konwersji 'NSData' na' NSString', a następnie z powrotem do 'NSData'? – Rishab