2013-03-26 8 views
10

Ich habe eine NSOperation subclaskiert und meinen completionBlock gesetzt, aber es scheint nie einzutreten, auch wenn der Vorgang beendet ist. Hier ist mein Code:Warum wird mein completionBlock niemals in einer NSOperation aufgerufen?

Ein Katalog Controller-Klasse richtet die NSOperation:

- (void)setupOperation { 
... 

    ImportWordOperation *importWordOperation = [[ImportWordOperation alloc] initWithCatalog:words]; 
    [importWordOperation setMainObjectContext:[app managedObjectContext]]; 
    [importWordOperation setCompletionBlock:^{ 
     [(ViewController *)[[app window] rootViewController] fetchResults]; 
    }]; 
    [[NSOperationQueue mainQueue] addOperation:importWordOperation]; 
    [importWordOperation release]; 
... 
} 

Wie Sie sehen, ich bin Einstellung der Fertigstellung Block eine Methode auf dem Haupt-Thread, in einem anderen Controller auszuführen.

Dann, in main meine Unterklasse NSOperation Klasse: ImportWordOperation.m, ich fire-up die Hintergrund-Operation. Ich overrode sogar isFinished iVar, um für die Completion-Methode ausgelöst werden:

- (void)setFinished:(BOOL)_finished { 
    finished = _finished; 
} 

- (BOOL)isFinished { 
    return (self.isCancelled ? YES: finished); 
} 

- (void)addWords:(NSDictionary *)userInfo { 
    NSError *error = nil; 

    AppDelegate *app = [AppDelegate sharedInstance]; 

    NSManagedObjectContext *localMOC = [userInfo valueForKey:@"localMOC"]; 
    NSEntityDescription *ent = [NSEntityDescription entityForName:@"Word" inManagedObjectContext:localMOC]; 
    for (NSDictionary *dictWord in [userInfo objectForKey:@"words"]) { 
     Word *wordN = [[Word alloc] initWithEntity:ent insertIntoManagedObjectContext:localMOC]; 

     [wordN setValuesForKeysWithDictionary:dictWord]; 
     [wordN release]; 
    } 

    if (![[userInfo valueForKey:@"localMOC"] save:&error]) { 
     NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 
     abort(); 
    } 
    [localMOC reset]; 

    [self setFinished:YES]; 
} 


- (void)main { 

    finished = NO; 

    NSManagedObjectContext *localMOC = nil; 
    NSUInteger type = NSConfinementConcurrencyType; 
    localMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:type]; 
    [localMOC setUndoManager:nil]; 
    [localMOC setParentContext:[self mainObjectContext]]; 

    if (![self isCancelled]) { 
     if ([self.words count] > 0) { 
      [self performSelectorInBackground:@selector(addWords:) withObject:@{@"words":self.words, @"localMOC":localMOC}]; 
     } 
    } 
} 

Wenn ich isfinished Accessormethoden entfernen, dann wird der Abschluss-Block genannt, aber Art und Weise vor ImportWordOperation Oberflächen.

Ich habe den Code gelesen, den ich gefunden habe, der einen eigenen Completion-Block verwendet, aber was nützt dann der Completion-Block in NSOperation-Unterklassen?

Irgendwelche Ideen oder Punkte auf eine ähnliche gelöste Situation würden sehr geschätzt werden.

Antwort

17

Sie sind irgendwie in einen seltsamen Raum zwischen gleichzeitig und nicht gleichzeitig NSOperation Unterklassen hier gefallen. Wenn Sie main implementieren, ist Ihre Operation normalerweise nicht gleichzeitig und isFinished ändert sich zu YES, sobald main beendet wird.

Allerdings haben Sie Ihre eigene Implementierung von isFinished zur Verfügung gestellt und es auch codiert, so dass isFinished nicht YES bis zurückkehren, nachdem main verlassen hat. Dies führt dazu, dass Ihre Operation in vielerlei Hinsicht wie eine gleichzeitige Operation agiert - zumindest einschließlich der Notwendigkeit, KVO-Benachrichtigungen manuell auszugeben. Die schnelle Lösung für Ihr Problem ist setFinished: mit (will|did)ChangeValueForKey: Anrufe zu implementieren. (Ich habe auch den Ivar-Namen geändert, um die vorherrschenden Namenskonventionen wiederzugeben). Unten ist eine NSOperation Unterklasse, von der ich glaube, dass sie genau die Arbeitsweise Ihres Betriebs modelliert, in Bezug auf die gleichzeitige Fertigstellung.

@implementation TestOperation { 
    BOOL _isFinished; 
} 

- (void)setFinished:(BOOL)isFinished 
{ 
    [self willChangeValueForKey:@"isFinished"]; 
    // Instance variable has the underscore prefix rather than the local 
    _isFinished = isFinished; 
    [self didChangeValueForKey:@"isFinished"]; 
} 

- (BOOL)isFinished 
{ 
    return ([self isCancelled] ? YES : _isFinished); 
} 

- (void)main 
{ 
    NSLog(@"%@ is in main.",self); 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     sleep(1); 
     [self setFinished:YES]; 
    }); 
} 

@end 

Ich bin nicht vertraut mit Ihren Anforderungen so vielleicht Sie ein dringendes Bedürfnis haben, aber Ihr Betrieb würde eine natürliche Ergänzung für einen gleichzeitigen Betrieb scheint die start statt main verwendet. Ich habe ein kleines Beispiel implementiert, das scheinbar korrekt funktioniert.

@implementation TestOperation { 
    BOOL _isFinished; 
    BOOL _isExecuting; 
} 

- (void)setFinished:(BOOL)isFinished 
{ 
    if (isFinished != _isFinished) { 
     [self willChangeValueForKey:@"isFinished"]; 
     // Instance variable has the underscore prefix rather than the local 
     _isFinished = isFinished; 
     [self didChangeValueForKey:@"isFinished"]; 
    } 
} 

- (BOOL)isFinished 
{ 
    return _isFinished || [self isCancelled]; 
} 

- (void)cancel 
{ 
    [super cancel]; 
    if ([self isExecuting]) { 
     [self setExecuting:NO]; 
     [self setFinished:YES]; 
    } 
} 

- (void)setExecuting:(BOOL)isExecuting { 
    if (isExecuting != _isExecuting) { 
     [self willChangeValueForKey:@"isExecuting"]; 
     _isExecuting = isExecuting; 
     [self didChangeValueForKey:@"isExecuting"]; 
    } 
} 

- (BOOL)isExecuting 
{ 
    return _isExecuting; 
} 

- (void)start 
{ 
    NSLog(@"%@ is in start. isCancelled = %@", self, [self isCancelled] ? @"YES" : @"NO"); 
    if (![self isCancelled]) { 
     [self setFinished:NO]; 
     [self setExecuting:YES]; 
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{ 
      sleep(1); 
      [self setExecuting:NO]; 
      [self setFinished:YES]; 
     }); 
    } 
} 
@end 
+0

Dank !, es war die 'main' /' Starten' Methode. Ich musste "start" anstelle von "main" implementieren, da ich es gleichzeitig verwenden muss. Ich habe das geändert und KVO in setFinished angepasst und es hat funktioniert !. Ich füge die '-cancel'-Methode hinzu, um die Sache zu runden. –

1

ich diesen Fehler aufgetreten, während eine asynchrone Unterklasse von NSOperation implementieren.

Der Swift Weg Schlüsselpfade der Referenzierung ist mit der #keyPath Richtlinie, so dass ich das tat (_executing und _finished sind meine interne Variablen):

self.willChangeValue(forKey: #keyPath(Operation.isExecuting)) 
self._executing = false 
self.didChangeValue(forKey: #keyPath(Operation.isExecuting)) 

self.willChangeValue(forKey: #keyPath(Operation.isFinished)) 
self._finished = true 
self.didChangeValue(forKey: #keyPath(Operation.isFinished)) 

Leider sind die #keyPath Ausdrücke oben Entschlossenheit "executing" und "finished" bzw., und wir müssen KVO-Benachrichtigungen für "isExecuting" und "isFinished" werfen. Deshalb wird completionBlock nicht aufgerufen.

Die Lösung ist hart zu codieren, sie als solche:

self.willChangeValue(forKey: "isExecuting") 
self._executing = false 
self.didChangeValue(forKey: "isExecuting") 

self.willChangeValue(forKey: "isFinished") 
self._finished = true 
self.didChangeValue(forKey: "isFinished") 
Verwandte Themen