为什么在处理主线程上的大量任务时,NSOperationQueue比GCD或者performSelectorOnMainThread更快?

例如,我有100次for循环。 并需要更新UIImageView,最后2种方法是一样的慢。 为什么? 他们之间有什么不同?

//fastest [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal]; [scrollView addSubview:btnThumb]; }]; //slowly dispatch_async(dispatch_get_main_queue(), ^ { [btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal]; [scrollView addSubview:btnThumb]; }); //slowly [btnThumb setImage:[UIImage imageWithData:data] forState:UIControlStateNormal]; [self performSelectorOnMainThread:@selector(testMethod:) withObject:[NSArray arrayWithObjects:scrollView, btnThumb, nil] waitUntilDone:NO]; -(void) testMethod:(NSArray*)objs { UIScrollView *scroll = [objs objectAtIndex:0]; UIButton *btn = [objs lastObject]; [scroll addSubview:btn]; } 

为了免去后代的疑虑,我们来考虑下面的testing工具,它是build立在一个简单的空应用程序模板上的。 它使用每个机制执行1000次操作,并且还有一个运行循环观察器,所以我们可以看到我们入队的asynchronous任务如何与主运行循环的旋转相关。 它logging到控制台,但这是asynchronous的,所以NSLog的成本不会混淆我们的测量。 它也有意地阻塞主线程,同时它NSOperations / dispatch_asyncs / performSelectors任务,这样排队的行为也不会干涉。 代码如下:

 #import "NSAppDelegate.h" dispatch_queue_t gLogQueue; #define NSLogAsync(...) dispatch_async(gLogQueue, ^{ NSLog(__VA_ARGS__); }); @implementation NSAppDelegate { dispatch_group_t g; NSUInteger numOps; useconds_t usleepDuration; } static void MyCFRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info); - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // parameters of test numOps = 1000; usleepDuration = 1000; // Set up a serial queue so we can keep the cost of calling NSLog more or less out of our test case. gLogQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); // Group allows us to wait for one test to finish before the next one begins g = dispatch_group_create(); // Insert code here to initialize your application CFRunLoopObserverRef rlo = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, YES, 0, MyCFRunLoopObserverCallBack, NULL); CFRunLoopAddObserver(CFRunLoopGetCurrent(), rlo, kCFRunLoopCommonModes); CFRelease(rlo); NSCondition* cond = [[NSCondition alloc] init]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSTimeInterval start = 0, end = 0; // pause the main thread dispatch_async(dispatch_get_main_queue(), ^{ [cond lock]; [cond signal]; [cond wait]; [cond unlock]; }); // wait for the main thread to be paused [cond lock]; [cond wait]; // NSOperationQueue for (NSUInteger i = 0; i < numOps; ++i) { dispatch_group_enter(g); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLogAsync(@"NSOpQ task #%@", @(i)); usleep(usleepDuration); // simulate work dispatch_group_leave(g); }]; } // unpause the main thread [cond signal]; [cond unlock]; // mark start time start = [NSDate timeIntervalSinceReferenceDate]; // wait for it to be done dispatch_group_wait(g, DISPATCH_TIME_FOREVER); end = [NSDate timeIntervalSinceReferenceDate]; NSTimeInterval opQDuration = end - start; NSLogAsync(@"NSOpQ took: %@s", @(opQDuration)); // pause the main thread dispatch_async(dispatch_get_main_queue(), ^{ [cond lock]; [cond signal]; [cond wait]; [cond unlock]; }); // wait for the main thread to be paused [cond lock]; [cond wait]; // Dispatch_async for (NSUInteger i = 0; i < numOps; ++i) { dispatch_group_enter(g); dispatch_async(dispatch_get_main_queue(), ^{ NSLogAsync(@"dispatch_async main thread task #%@", @(i)); usleep(usleepDuration); // simulate work dispatch_group_leave(g); }); } // unpause the main thread [cond signal]; [cond unlock]; // mark start start = [NSDate timeIntervalSinceReferenceDate]; dispatch_group_wait(g, DISPATCH_TIME_FOREVER); end = [NSDate timeIntervalSinceReferenceDate]; NSTimeInterval asyncDuration = end - start; NSLogAsync(@"dispatch_async took: %@s", @(asyncDuration)); // pause the main thread dispatch_async(dispatch_get_main_queue(), ^{ [cond lock]; [cond signal]; [cond wait]; [cond unlock]; }); // wait for the main thread to be paused [cond lock]; [cond wait]; // performSelector: for (NSUInteger i = 0; i < numOps; ++i) { dispatch_group_enter(g); [self performSelectorOnMainThread: @selector(selectorToPerfTask:) withObject: @(i) waitUntilDone: NO]; } // unpause the main thread [cond signal]; [cond unlock]; // mark start start = [NSDate timeIntervalSinceReferenceDate]; dispatch_group_wait(g, DISPATCH_TIME_FOREVER); end = [NSDate timeIntervalSinceReferenceDate]; NSTimeInterval performDuration = end - start; NSLogAsync(@"performSelector took: %@s", @(performDuration)); // Done. dispatch_async(dispatch_get_main_queue(), ^{ CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), rlo, kCFRunLoopCommonModes); NSLogAsync(@"Done. NSOperationQueue: %@s dispatch_async: %@s performSelector: %@", @(opQDuration), @(asyncDuration), @(performDuration)); }); }); } - (void)selectorToPerfTask: (NSNumber*)task { NSLogAsync(@"performSelector task #%@", task); usleep(usleepDuration); // simulate work dispatch_group_leave(g); } static NSString* NSStringFromCFRunLoopActivity(CFRunLoopActivity activity) { NSString* foo = nil; switch (activity) { case kCFRunLoopEntry: foo = @"kCFRunLoopEntry"; break; case kCFRunLoopBeforeTimers: foo = @"kCFRunLoopBeforeTimers"; break; case kCFRunLoopBeforeSources: foo = @"kCFRunLoopBeforeSources"; break; case kCFRunLoopBeforeWaiting: foo = @"kCFRunLoopBeforeWaiting"; break; case kCFRunLoopAfterWaiting: foo = @"kCFRunLoopAfterWaiting"; break; case kCFRunLoopExit: foo = @"kCFRunLoopExit"; break; default: foo = @"ERROR"; break; } return foo; } static void MyCFRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { NSLogAsync(@"RLO: %@", NSStringFromCFRunLoopActivity(activity)); } @end 

在这段代码的输出中,我们看到以下内容(删除了不相关/重复的部分):

 RLO: kCFRunLoopEntry RLO: kCFRunLoopBeforeTimers RLO: kCFRunLoopBeforeSources RLO: kCFRunLoopBeforeWaiting RLO: kCFRunLoopAfterWaiting NSOpQ task #0 RLO: kCFRunLoopExit RLO: kCFRunLoopEntry RLO: kCFRunLoopBeforeTimers RLO: kCFRunLoopBeforeSources RLO: kCFRunLoopBeforeWaiting RLO: kCFRunLoopAfterWaiting NSOpQ task #1 RLO: kCFRunLoopExit ... pattern repeats ... RLO: kCFRunLoopEntry RLO: kCFRunLoopBeforeTimers RLO: kCFRunLoopBeforeSources RLO: kCFRunLoopBeforeWaiting RLO: kCFRunLoopAfterWaiting NSOpQ task #999 RLO: kCFRunLoopExit NSOpQ took: 1.237247049808502s RLO: kCFRunLoopEntry RLO: kCFRunLoopBeforeTimers RLO: kCFRunLoopBeforeSources RLO: kCFRunLoopBeforeWaiting RLO: kCFRunLoopAfterWaiting dispatch_async main thread task #0 dispatch_async main thread task #1 ... pattern repeats ... dispatch_async main thread task #999 dispatch_async took: 1.118762016296387s RLO: kCFRunLoopExit RLO: kCFRunLoopEntry RLO: kCFRunLoopBeforeTimers RLO: kCFRunLoopBeforeSources performSelector task #0 performSelector task #1 ... pattern repeats ... performSelector task #999 performSelector took: 1.133482992649078s RLO: kCFRunLoopExit RLO: kCFRunLoopEntry RLO: kCFRunLoopBeforeTimers RLO: kCFRunLoopBeforeSources Done. NSOperationQueue: 1.237247049808502s dispatch_async: 1.118762016296387s performSelector: 1.133482992649078 

这给我们显示的是,在主队列中排队的NSOperation会在运行循环中每执行一次就执行一次。 (顺便说一下,这将允许视图为每个操作绘制,所以如果您正在更新这些任务中的UI控件,那么这将允许它们绘制)。使用dispatch_async(dispatch_get_main_queue(),...)-[performSelectorOnMainThread:...]所有入队的块/select器被一个接一个地调用,而不让视图被绘制或者类似的东西。 (如果在排队任务时不强行暂停主循环, 有时可以在排队过程中看到运行循环旋转一次或两次)。

最后,结果与我预期的一样:

  • NSOperationQueue:1.2372s
  • dispatch_async:1.1188s
  • performSelector:1.1335s

NSOperationQueue总是比较慢,因为旋转运行循环并不是免费的。 在这个testing工具中,运行循环甚至没有任何实质性的事情,它已经比dispatch_async慢了10%。 如果它正在做任何实质的事情,比如重画一个视图,那么速度就会慢很多 。 至于dispatch_async vs performSelectorOnMainThread:都在运行循环的一个旋转执行所有入列的项目,所以差异是相当微小的。 我期望它是关于消息发送开销,并pipe理performSelector...的目标和参数上的保留/释放performSelector...

所以与这个问题的含义相反, NSOperationQueue在客观上不是这三种机制中最快的,而是最慢的。 我怀疑是在OP的情况下, NSOperationQueue 显得更快,因为它的“第一次可见变化的时间”会更短,而对于dispatch_asyncperformSelector所有的入队操作都将被执行,只有这样,才能重绘和显示新的状态。 在病态的情况下,我希望这意味着只有最后一帧才被看到,虽然如果你没有在排队的时候阻塞主线程,你可能会得到一些可见的帧(但你会有效地放弃最在地面上的框架。)

无论哪种asynchronous执行机制客观上最快,它们都是非常糟糕的做animation的方法。