16

Ich bin ein großer Fan von Blöcken, aber habe sie nicht für Parallelität verwendet. Nach einigem googeln habe ich diese Idee zusammengefügt, um alles, was ich gelernt habe, an einem Ort zu verstecken. Das Ziel ist es, einen Block im Hintergrund auszuführen, und wenn es fertig ist, führen Sie einen anderen Block (wie UIView Animation) ...Lernen NSBlockOperation

- (NSOperation *)executeBlock:(void (^)(void))block completion:(void (^)(BOOL finished))completion { 

    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block]; 

    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ 
     completion(blockOperation.isFinished); 
    }]; 

    [completionOperation addDependency:blockOperation]; 
    [[NSOperationQueue mainQueue] addOperation:completionOperation];  

    NSOperationQueue *backgroundOperationQueue = [[NSOperationQueue alloc] init]; 
    [backgroundOperationQueue addOperation:blockOperation]; 

    return blockOperation; 
} 

- (void)testIt { 

    NSMutableString *string = [NSMutableString stringWithString:@"tea"]; 
    NSString *otherString = @"for"; 

    NSOperation *operation = [self executeBlock:^{ 
     NSString *yetAnother = @"two"; 
     [string appendFormat:@" %@ %@", otherString, yetAnother]; 
    } completion:^(BOOL finished) { 
     // this logs "tea for two" 
     NSLog(@"%@", string); 
    }]; 

    NSLog(@"keep this operation so we can cancel it: %@", operation); 
} 

Meine Fragen sind:

  1. Es funktioniert, wenn ich es laufen , aber vermisse ich irgendetwas ... verstecktes Land meins? Ich habe die Stornierung nicht getestet (weil ich keine lange Operation erfunden habe), aber sieht es so aus, als würde es funktionieren?
  2. Ich bin besorgt, dass ich meine Deklaration von backgroundOperation qualifizieren muss, damit ich im Completion-Block darauf verweisen kann. Der Compiler beklagt sich nicht, aber lauert dort ein Retain-Zyklus?
  3. Wenn die "string" ein ivar wäre, was würde passieren, wenn ich Schlüssel-Wert beobachtete, während der Block lief? Oder einen Timer für den Haupt-Thread einrichten und ihn regelmäßig protokollieren? Würde ich Fortschritte sehen können? Würde ich es atomar erklären?
  4. Wenn dies so funktioniert, wie ich es erwarte, dann scheint es eine gute Möglichkeit zu sein, alle Details zu verbergen und Nebenläufigkeit zu erhalten. Warum hat Apple das nicht für mich geschrieben? Fehle ich etwas Wichtiges?

Danke.

+1

Haben Sie darüber nachgedacht GCD mit? Oder ist das eine reine Lernübung? Eine serielle Warteschlange klingt genau nach dem, wonach Sie suchen. – borrrden

Antwort

17

Ich bin kein Experte in NSOperation oder NSOperationQueues aber ich denke, Code unten ein bisschen besser ist, obwohl ich denke, dass es immer noch einige Einschränkungen hat. Wahrscheinlich genug für einige Zwecke ist aber keine allgemeine Lösung für Concurrency:

- (NSOperation *)executeBlock:(void (^)(void))block 
         inQueue:(NSOperationQueue *)queue 
        completion:(void (^)(BOOL finished))completion 
{ 
    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block]; 
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ 
     completion(blockOperation.isFinished); 
    }]; 
    [completionOperation addDependency:blockOperation]; 

    [[NSOperationQueue currentQueue] addOperation:completionOperation]; 
    [queue addOperation:blockOperation]; 
    return blockOperation; 
} 

Jetzt können es verwenden:

- (void)tryIt 
{ 
    // Create and configure the queue to enqueue your operations 
    backgroundOperationQueue = [[NSOperationQueue alloc] init]; 

    // Prepare needed data to use in the operation 
    NSMutableString *string = [NSMutableString stringWithString:@"tea"]; 
    NSString *otherString = @"for"; 

    // Create and enqueue an operation using the previous method 
    NSOperation *operation = [self executeBlock:^{ 
     NSString *yetAnother = @"two"; 
     [string appendFormat:@" %@ %@", otherString, yetAnother]; 
    } 
    inQueue:backgroundOperationQueue 
    completion:^(BOOL finished) { 
     // this logs "tea for two" 
     NSLog(@"%@", string); 
    }]; 

    // Keep the operation for later uses 
    // Later uses include cancellation ... 
    [operation cancel]; 
} 

Einige Antworten auf Ihre Fragen:

  1. Cancelation. Normalerweise unterziehen Sie NSOperation der Unterklasse, so dass Sie self.isCancelled überprüfen und früher zurückkehren können. Siehe this thread, es ist ein gutes Beispiel. Im aktuellen Beispiel können Sie nicht überprüfen, ob die Operation aus dem von Ihnen gelieferten Block abgebrochen wurde, um eine NSBlockOperation zu machen, weil es zu diesem Zeitpunkt noch keine solche Operation gibt. Das Abbrechen von NSBlockOperation s, während der Block aufgerufen wird, ist anscheinend möglich, aber cumbersome. NSBlockOperation s sind für bestimmte einfache Fälle.Wenn Sie eine Stornierung benötigen, sind Sie besser Unterklassen NSOperation :)

  2. Ich sehe kein Problem hier. Beachten Sie zwei Dinge. a) Ich habe die Methode do geändert, um den Completion-Block in der aktuellen Queue auszuführen. b) Eine Queue wird als Parameter benötigt. Wie @ Mike Weller sagte, sollten Sie besser versorgen background queue so brauchen Sie nicht eine für jede Operation zu erstellen und können wählen, was Ihr Material verwenden Warteschlange zu laufen :)

  3. Ich denke ja, sollten Sie machen stringatomic . Eine Sache, die Sie nicht vergessen sollten, ist, dass, wenn Sie der Warteschlange mehrere Operationen zur Verfügung stellen, sie möglicherweise nicht in dieser Reihenfolge (unbedingt) ausgeführt werden, so dass Sie eine sehr seltsame Nachricht in Ihrem string erhalten könnten. Wenn Sie eine Operation nacheinander seriell ausführen müssen, können Sie Folgendes tun: [backgroundOperation setMaxConcurrentOperationCount:1];, bevor Sie Ihre Operationen in die Warteschlange stellen. Es gibt einen Lesewürdigen Notiz im docs aber:

    Weitere Funktionen Queue Behaviors Eine Operation Warteschlange führt Objekte seiner Warteschlange Betrieb auf der Grundlage ihrer Priorität und Bereitschaft. Wenn alle in der Warteschlange befindlichen Operationsobjekte die gleiche Priorität haben und ausgeführt werden können, wenn sie in die Warteschlange gestellt werden, dh ihre isReady-Methode gibt YES zurück, werden sie in der Reihenfolge ausgeführt, in der sie an die Warteschlange übergeben wurden. Bei einer Warteschlange, deren maximale Anzahl gleichzeitiger Vorgänge auf 1 festgelegt ist, entspricht dies einer seriellen Warteschlange. Sie sollten sich jedoch niemals auf die serielle Ausführung von Operationsobjekten verlassen. Änderungen in der Bereitschaft einer Operation können die resultierende Ausführungsreihenfolge ändern.

  4. Ich denke, nach Lesen dieser Zeilen Sie wissen :)

+0

Ausgezeichnete Antwort. Vielen Dank. – danh

+0

Aber nicht ganz richtig, IMHO. Beim Aufruf von executeBlock fehlt der Warteschlangenparameter – decades

+0

. Später habe ich den Beispielcode korrigiert. – nacho4d

8

Sie sollten keinen neuen NSOperationQueue für jeden executeBlock:completion: Aufruf erstellen. Dies ist teuer und der Benutzer dieser API hat keine Kontrolle darüber, wie viele Operationen gleichzeitig ausgeführt werden können.

Wenn Sie NSOperation Instanzen zurückgeben, sollten Sie es dem Aufrufer überlassen, zu entscheiden, in welche Warteschlange sie hinzugefügt werden sollen. Aber an diesem Punkt ist Ihre Methode wirklich nichts hilfreich und der Anrufer könnte auch die NSBlockOperation selbst erstellen.

Wenn Sie nur eine einfache und einfache Möglichkeit, einen Block im Hintergrund Spin-off und einige Code ausführen möchten, wenn es fertig ist, sind Sie wahrscheinlich besser dran machen einige einfache GCD Anrufe mit den dispatch_* Funktionen. Zum Beispiel:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    // do your background work 
    // ... 

    // now execute the 'completion' code. 
    // you often want to dispatch back to the main thread to update the UI 
    // For example: 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     // update UI, etc. 
     myLabel.text = @"Finished"; 
    }); 

}); 
+3

Ich sehe, danke. Wie wäre es mit dem Abbrechen? – danh

+0

@danh, über die Annullierung von Dingen bitte schauen Sie sich NSOperationsQueue und Methode 'cancelAllOperations' an;) –