2014-09-16 15 views
6

Ich habe eine große Aufgabe, die aus mehreren Teilaufgaben bestehen. und ich möchte Fortschrittsberichte für diese große Aufgabe hinzufügen.
für das möchte ich NSProgress verwenden, und entsprechend der Klassendokumentation kann ich diese Art von Subtask-Fortschritt tun, indem Sie seinen Kind-Eltern-Mechanismus verwenden.NSProgress seltsames Verhalten

Also, um es zu vereinfachen, nehmen wir an, ich habe große Aufgabe, die aus einer Teilaufgabe besteht (natürlich im wirklichen Leben gäbe es mehr Teilaufgaben). So ist es das, was ich getan habe:

#define kFractionCompletedKeyPath @"fractionCompleted" 

- (void)runBigTask { 
    _progress = [NSProgress progressWithTotalUnitCount:100]; // 100 is arbitrary 

    [_progress addObserver:self 
       forKeyPath:kFractionCompletedKeyPath 
        options:NSKeyValueObservingOptionNew 
        context:NULL]; 

    [_progress becomeCurrentWithPendingUnitCount:100]; 
    [self subTask]; 
    [_progress resignCurrent]; 
} 

- (void)subTask { 
    NSManagedObjectContext *parentContext = self.managedObjectContext; // self is AppDelegate in this example 
    NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [bgContext setParentContext:parentContext]; 

    [bgContext performBlockAndWait:^{ 
     NSInteger totalUnit = 1000; 
     NSInteger completedUnits = 0; 
     NSProgress *subProgress = [NSProgress progressWithTotalUnitCount:totalUnit]; 

     for (int i=0; i < totalUnit; i++) { 

      // run some Core Data related code... 

      completedUnits++; 
      subProgress.completedUnitCount = completedUnits; 
     } 
    }]; 
}  

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 
    if ([keyPath isEqualToString:kFractionCompletedKeyPath]) { 
     if ([object isKindOfClass:[NSProgress class]]) { 
      NSProgress *progress = (NSProgress *)object; 
      NSLog(@"progress… %f", progress.fractionCompleted); 
     } 
    } else { 
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 
    } 
} 

Wie Sie sehen können, die Subtask verwendet Hintergrund Kontext einige Core Data bezogenen Code auszuführen, und der Hintergrund Kontext nutzt den Haupt Kontext als sein Elternkontext.
Dies verursacht einige seltsame KVO der Fortschritt "fractionCompleted" -Eigenschaft. diese

ist der Druck:

progress… 1.000000 // why??? 
progress… 0.500000 // why????? 
progress… 1.000000 // why??????? 
progress… 0.666650 // why??????????? 
progress… 0.666990 
progress… 0.667320 
progress… 0.667660 
progress… 0.667990 
progress… 0.668320 
... 
progress… 1.000000 

Wie Sie der Druck mit 1.0 beginnt sehen können, 0,5 und 1,0 und dann geht auf 0,66?!
von hier aus handelt es sich normal und geht auf 1.0 wie ich es erwarte.

Ich habe versucht zu verstehen, warum das passiert, und ich habe festgestellt, dass, wenn ich den übergeordneten Kontext aus dem Hintergrund Kontext entfernen, es funktioniert gut! Ich bekomme einen Fortschritt von 0.0 auf 1.0.

Irgendwelche Ideen, warum passiert das? und wie kann ich das beheben?

habe ich eine sehrsimple project dieses Problem zu demonstrieren (Sie können die setParentContext entfernen: rufen, dass es ohne es funktioniert gut zu sehen)

+0

Die beste Dokumentation für NSProgress ist hier: https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/ – quellish

Antwort

4

Der Stack-Trace, wenn dies geschieht wie folgt aussieht:

(lldb) bt 
* thread #1: tid = 0x81f2, 0x0000000105bffcda Foundation`-[NSProgress setTotalUnitCount:], queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 
    * frame #0: 0x0000000105bffcda Foundation`-[NSProgress setTotalUnitCount:] 
    frame #1: 0x0000000105bfeb1b Foundation`+[NSProgress progressWithTotalUnitCount:] + 87 
    frame #2: 0x0000000105a31213 Foundation`_NSReadBytesFromFileWithExtendedAttributes + 287 
    frame #3: 0x0000000105a3109d Foundation`-[NSData(NSData) initWithContentsOfFile:] + 89 
    frame #4: 0x0000000105a30b40 Foundation`+[NSDictionary(NSDictionary) newWithContentsOf:immutable:] + 101 
    frame #5: 0x0000000105a5622a Foundation`+[NSDictionary(NSDictionary) dictionaryWithContentsOfFile:] + 45 
    frame #6: 0x00000001043c4560 CoreData`-[NSManagedObjectModelBundle initWithPath:] + 224 
    frame #7: 0x00000001043c42ed CoreData`-[NSManagedObjectModel initWithContentsOfURL:] + 205 
    frame #8: 0x00000001040f723f CDProgress`-[AppDelegate managedObjectModel](self=0x00007fbe48c21f90, _cmd=0x000000010459b37b) + 223 at AppDelegate.m:127 
    frame #9: 0x00000001040f7384 CDProgress`-[AppDelegate persistentStoreCoordinator](self=0x00007fbe48c21f90, _cmd=0x000000010459c1cb) + 228 at AppDelegate.m:142 
    frame #10: 0x00000001040f708c CDProgress`-[AppDelegate managedObjectContext](self=0x00007fbe48c21f90, _cmd=0x0000000104598f0d) + 92 at AppDelegate.m:111 
    frame #11: 0x00000001040f6bdb CDProgress`-[AppDelegate subTask](self=0x00007fbe48c21f90, _cmd=0x00000001040f7997) + 43 at AppDelegate.m:45 
    frame #12: 0x00000001040f6b89 CDProgress`-[AppDelegate runTask](self=0x00007fbe48c21f90, _cmd=0x00000001040f7928) + 233 at AppDelegate.m:40 
    frame #13: 0x00000001040f6a4b CDProgress`-[AppDelegate application:didFinishLaunchingWithOptions:](self=0x00007fbe48c21f90, _cmd=0x0000000104f5dba9, application=0x00007fbe48f00fb0, launchOptions=0x0000000000000000) + 571 at AppDelegate.m:26 
    frame #14: 0x000000010477c5a5 UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 234 
    frame #15: 0x000000010477d0ec UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2463 
    frame #16: 0x000000010477fe5c UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1350 
    frame #17: 0x000000010477ed22 UIKit`-[UIApplication workspaceDidEndTransaction:] + 179 
    frame #18: 0x00000001088092a3 FrontBoardServices`__31-[FBSSerialQueue performAsync:]_block_invoke + 16 
    frame #19: 0x000000010615fabc CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12 
    frame #20: 0x0000000106155805 CoreFoundation`__CFRunLoopDoBlocks + 341 
    frame #21: 0x00000001061555c5 CoreFoundation`__CFRunLoopRun + 2389 
    frame #22: 0x0000000106154a06 CoreFoundation`CFRunLoopRunSpecific + 470 
    frame #23: 0x000000010477e799 UIKit`-[UIApplication _run] + 413 
    frame #24: 0x0000000104781550 UIKit`UIApplicationMain + 1282 
    frame #25: 0x00000001040f7793 CDProgress`main(argc=1, argv=0x00007fff5bb09308) + 115 at main.m:16 
    frame #26: 0x000000010686f145 libdyld.dylib`start + 1 
(lldb) 

Was hier passiert, dass ist, wenn das Modell geladen wird, ist es eine plist-Datei liest. Das Lesen der PLIST-Datei ruft -[NSData initWithContentsOfFile:] auf, die +[NSProgress progressWithTotalUnitCount:] auf dem Hauptthread aufruft. Als release notes point out wird dies eine NSProgress erstellen, die ein Kind des aktuellen Fortschritts ist. initWithContentsOfFile: ist dies tatsächlich tun, und ein neues Kind der NSProgress Sie erstellt erstellen:

<NSProgress: 0x7f9353596f80> : Parent: 0x0/Fraction completed: 0.0000/Completed: 0 of 1 
    <_NSProgressGroup: 0x7f935601a0d0> : Portion of parent: 100 Children: 1 
     <NSProgress: 0x7f935600bf50> : Parent: 0x7f9353596f80/Fraction completed: 0.0000/Completed: 0 of 0 

Was hier passiert, ist, dass zusätzliche Arbeit vor Ihnen hinzugefügt wird. Zu diesem Zeitpunkt weiß es nichts über die zusätzliche Arbeit, die Sie hinzufügen möchten. Das von initWithContentsOfFile: hinzugefügte Kind wird aus dem Baum gelöscht und Sie beginnen dann, Ihre Arbeit hinzuzufügen.

Der aktuelle Fortschritt beginnt bei 0 und geht auf 100%. Sie sehen 100%, weil Ihre KVO-Optionen NSKeyValueObservingOptionInitial nicht enthalten.

NSData fügt einen untergeordneten Fortschritt hinzu, der bei 0 beginnt und auf 100% geht.

Ihre Core Data-Aufgabe fügt ein untergeordnetes Element hinzu, das bei 0 beginnt und (letztendlich) auf 100% geht.

Ein wichtiger Punkt hier ist jedoch, dass Sie performBlockAndWait: verwenden. Während der Block selbst in einer privaten Warteschlange ausgeführt wird, blockiert diese Methode den aufrufenden Thread, der Ihre KVO-Benachrichtigungen verzögert. performBlockAndWait: wird auch den aufrufenden Thread, wenn möglich, wiederverwenden, was zu beachten ist.

Wenn Sie Ihre subTask Methode bearbeiten sich mit einer NSProgress wickeln als Mutter für die gesamte Arbeitseinheit zu dienen, am Ende aktuellen resignieren, werden Sie wahrscheinlich Verhalten näher bekommen, was Sie erwarten:

- (void)subTask { 
    NSProgress *progress = [NSProgress progressWithTotalUnitCount:1]; 
    NSManagedObjectContext *parentContext = self.managedObjectContext; 
    NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
    [bgContext setParentContext:parentContext]; 

    [progress becomeCurrentWithPendingUnitCount:1]; 
    [bgContext performBlock:^{ 

    ... stuff 

    [progress resignCurrent]; 
} 

NSProgress kann ein wenig schwierig sein, den Kopf einzuwickeln, aber mit etwas Erfahrung wird es leichter. Ich verspreche!

+1

Und als Nebenbei bemerkt, ich dies als Fehler-Datei würde. Im schlimmsten Fall sagen sie dir, dass es wie geplant funktioniert. – quellish

0

Es scheint, muss es innerhalb von [NSManagedObjectModel initWithContentsOfURL:] ein NSProgress Zähler sein. Vor der Eingabe von [self subTask] haben Sie sich selbst eingerichtet, um Benachrichtigungen über Fortschrittsanzeigen zu erhalten (indem Sie _progress als aktuell festlegen und sich selbst registrieren, um Änderungen zu beobachten). Dann rufen Sie in dieser Routine den faulen Getter self.managedObjectContext an, der seinerseits aufruft, der anscheinend einen Fortschrittszähler mit 2 Einheiten hat. Es scheint, dass Sie sehr vorsichtig sein müssen, wo Sie die Anrufe auf [NSProgress becomeCurrentWithPendingUnitCount:] und [NSProgress resignCurrent] setzen.