2013-03-26 10 views
10

Podsunąłem operację NSOperation i ustawiłem funkcję completionBlock, ale wydaje się, że nigdy nie wchodzi ona nawet po zakończeniu operacji. Oto mój kod:Dlaczego mój completionBlock nigdy nie jest wywoływany w NSOperation?

Katalog klasy kontroler ustawia 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]; 
... 
} 

Jak widać, jestem ustawienie bloku ukończenia do wykonania metody na głównym wątku, w jakiś inny kontroler.

Następnie, w main moja podklasowana klasa NSOperation: ImportWordOperation.m, uruchamiam operację w tle. I nawet overrode isFinished Ivar, aby metoda ukończenie być wyzwalany:

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

Jeśli usunąć isFinished metody dostępowe, a następnie blok ukończenie jest wywoływana ale droga przed ImportWordOperation wykończenia.

Czytałem kod, który znalazłem, który używa własnego bloku ukończenia, ale następnie, jakie jest użycie bloku zakończenia w podklasach NSOperation?

Wszelkie pomysły lub wskazujące na podobną rozwiązaną sytuację byłyby bardzo mile widziane.

Odpowiedz

17

W pewnym sensie wpadłeś w dziwną przestrzeń między podklasami współbieżnymi i nie współbieżnymi tutaj, w podklasach NSOperation. Zazwyczaj po zaimplementowaniu main operacja nie jest jednoczasowa, a isFinished zmienia się na , gdy tylko zakończy się main.

jednak podasz własną implementację isFinished, a także zakodowany tak, że isFinished nie zwraca YES aż po main odszedł. To sprawia, że ​​twoja operacja zaczyna działać jak operacja współbieżna na wiele sposobów - przynajmniej włączając w to potrzebę ręcznego wysyłania powiadomień KVO.

Szybkie rozwiązanie problemu polega na wdrożeniu setFinished: przy użyciu połączeń (will|did)ChangeValueForKey:. (Zmieniłem również nazwę ivar, aby odzwierciedlić nazewnictwo obowiązujące konwencje nazewnictwa). Poniżej znajduje się podklasa NSOperation, która moim zdaniem dokładnie modeluje działania twojej operacji, pod względem kończenia w sposób współbieżny.

@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 

nie jestem zaznajomiony z wymaganiami więc może masz pilną potrzebę, ale operacja wydaje się bardziej naturalne dopasowanie do równoległej pracy, który wykorzystuje start zamiast main. Zaimplementowałem mały przykład, który wydaje się działać poprawnie.

@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

dzięki !, był to 'main' /' metoda start'. Musiałem zaimplementować 'start' zamiast' main', ponieważ potrzebuję go współbieżnie. Zmieniłem to i dostosowałem KVO w setFinished i zadziałało !. Dodaję metodę '-cancel', aby zaokrąglić rzeczy do góry. –

1

I napotkał ten błąd podczas wdrażania asynchronicznego podklasę NSOperation.

Swift sposób o przedstawieniu kluczowych ścieżek jest z dyrektywą #keyPath, więc robiłem to (_executing i _finished są moje wewnętrzne zmienne):

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

Niestety #keyPath wyrażenia Powyższe postanowienie "executing" i "finished" i musimy podać powiadomienia KVO dla "isExecuting" i "isFinished". Właśnie dlatego completionBlock nie jest wywoływany.

Rozwiązaniem jest ciężko kodem je jako takie:

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

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