The stack trace when this happens is as follows:
(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)
What happens here when the model loads, it reads the plist file. Reading the plist file calls -[NSData initWithContentsOfFile:] , which calls +[NSProgress progressWithTotalUnitCount:] in the main thread. As the release notes indicate , this will create NSProgress, which is a child of the current progress. initWithContentsOfFile: actually does this and creates a new NSProgress child that you created:
<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
What happens here, so that extra work is added to you. At the moment, he does not know anything about the extra work that you are going to add. Added child element added by initWithContentsOfFile: is removed from the tree and then you start adding your work.
Current progress starts at 0 and goes 100%. You see 100% because your KVO options do not include NSKeyValueObservingOptionInitial .
NSData adds a childās progress that starts at 0 and goes 100%.
The Core Data task adds a child that starts at 0 and (eventually) goes 100%.
However, the key point is that you are using performBlockAndWait: Although the block itself runs in a private queue, this method blocks the calling thread, which delays your KVO notifications. performBlockAndWait: will also reuse the calling thread, if possible, to be aware of.
If you edit your subTask method to wrap yourself with NSProgress, which will serve as the parent for the entire unit of work, yielding the current value at the end, you will probably come closer to what you expect:
- (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 can be a little harder to wrap around you, but with some experience it gets easier. I promise!