我有一个由几个子任务组成的大任务。我想为这一重大任务增加进度报告。
为此,我想使用NSProgress,并且根据类文档,我可以使用它的子父机制来完成这种子任务进度。
因此,为了简化它,假设我有一个由一个子任务组成的大任务(当然,在现实生活中会有更多的子任务)。这就是我所做的:
#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];
}
}如您所见,子任务使用背景上下文运行一些Core数据相关代码,后台上下文使用主上下文作为父上下文。
这会导致进度的"fractionCompleted“属性出现一些奇怪的KVO。
这是印刷品:
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 如您所见,打印以1.0、0.5和1.0开头,然后转到0.66 ?!
从现在起,它的行为正常,并达到1.0,正如我所期望的。
我试图理解为什么会发生这种情况,并且我注意到,如果我从背景上下文中删除父上下文,它就会工作得很好!我的进度从0.0提高到1.0。
知道为什么会发生这种事吗?我怎么才能解决这个问题?
我添加了一个非常的简单工程来演示这个问题(您可以删除setParentContext: call,以确保它在没有它的情况下运行良好)
发布于 2014-09-17 03:10:52
发生这种情况时的堆栈跟踪如下所示:
(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) 这里所发生的是,当模型加载时,它正在读取一个plist文件。读取plist文件调用-[NSData initWithContentsOfFile:],该文件在主线程上调用+[NSProgress progressWithTotalUnitCount:]。作为发布说明指出,这将创建一个NSProgress,它是当前进度的子进程。initWithContentsOfFile:实际上正在这样做,并创建了您创建的NSProgress的一个新子级:
<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 这里发生的是在你面前增加了额外的工作。在这一点上,它不知道任何额外的工作,您要添加。initWithContentsOfFile:添加的子程序完成,从树中删除,然后开始添加工作。
当前的进度从0开始,到100%。您看到100%是因为您的KVO选项不包括NSKeyValueObservingOptionInitial。
NSData添加了一个从0开始到100%的子进程。
核心数据任务添加一个从0开始的子任务,并且(最终)增加到100%。
然而,这里的一个关键点是您正在使用performBlockAndWait:。当块本身在私有队列上运行时,此方法将阻塞调用线程,这将延迟您的KVO通知。如果可能的话,performBlockAndWait:还将重用调用线程,这是需要注意的事情。
如果您编辑您的subTask方法将自己包装成一个NSProgress作为整个工作单元的父级,在结束时退出当前,您可能会得到更接近您预期的行为:
- (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可能有些困难,但有了一些经验,它会变得更容易。我保证!
发布于 2014-09-16 19:23:01
似乎在NSProgress的内部一定有一个[NSManagedObjectModel initWithContentsOfURL:]计数器。在输入[self subTask]之前,您将自己设置为接收任何进度指示符的通知(方法是将_progress设置为current,并注册self以观察更改)。然后,在这个例程中,你称为懒惰的getter self.managedObjectContext,它反过来又称为[NSManagedObjectModel initWithContentsOfURL:],它显然有一个2单元的进度计数器。似乎您需要非常小心地放置对[NSProgress becomeCurrentWithPendingUnitCount:]和[NSProgress resignCurrent]的调用。
https://stackoverflow.com/questions/25870717
复制相似问题