本文讲创建分线程的方法。
所有的类都是NSObject的子类,因此都继承了这些方法:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait);
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
最后一个方法是在后台执行的,不要用这个方法去做修改ui的动作。
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
创建后需要手动开启线程。
- (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
创建后线程自动开启。不论用哪种方法创建的线程,要保持线程安全要用到NSLock。这个NSLock,锁的是在其lock方法到unlock方法之间的代码,锁定之后,这段代码同一时间只会被一个线程执行。
NSOperationQueue会创建一定数量的线程来执行加入到其中的NSOperation,可以设置本队列可同时执行的最大线程数maxConcurrentOperationCount,默认的话,最大线程数为NSOperationQueueDefaultMaxConcurrentOperationCount,这个数值会根据系统环境自动适应。
加入到队列的NSOperation的并行执行。
如果需要指定顺序,可以通过添加依赖。一个NSOperation对象,只有当其依赖的NSOperation对象执行完,才会执行。
一个app可以有多个队列,各个队列分别管理各自的NSOperation对象。
NSOperation有许多子类,比如NSInnovationOperation、NSBlockOperation,这些子类的区别仅仅在于与动作绑定的方式不一样,队列会被同等对待它们。
GCD是一个可以发挥多核处理器性能的任务管理技术。
GCD与NSOperationQueue比较像的地方是,它也有队列,类型为dispatch_queue_t。和NSOperationQueue一样,队列管理着许多待执行的任务。
不同的是,GCD分为并行队列和串行队列。串行队列会将加入其中的任务全部放到一个线程中执行,而并行队列执行任务的思想和NSOperationQueue类似,会用多线程来执行任务。
(1)直接获取
主线程的运行也是有一个队列在进行管理,这个队列是main()方法执行的时候就已经创建了的,可以直接利用这个队列,然后往里面添加任务。要注意这个队列是串行队列。也就是说添加到主线程所在队列的任务是由主线程完成的。
获取主线程所在队列:
dispatch_get_main_queue();
还有另外一个方法可直接获取的队列:
dispatch_get_global_queue();
GCD自动创建了三个全局队列,并且都是并行的。三个队列用优先级来区分。
(2)自己创建新队列:
dispatch_queue_create(const char *label,dispatch_queue_attr_t attr);
第一个参数是队列名字,第二个参数决定这个队列是串行队列还是并行队列,其值为常量:DISPATCH_QUEUE_SERIAL
或者DISPATCH_QUEUE_CONCURRENT
。
任务很多时候以block的形式添加到队列。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
两个方法的第一个参数是队列,第二个参数是要加入的block。
区别在于,第一个方法是异步方法,会将block加入到队列之后就会马上返回;第二个方法是同步方法,会等到加入的block执行完以后才会返回。
因此要注意,由于主线程所在队列为串行队列,如果使用同步方法往里面加入的block,由于block抢不过主线程而一直得不到执行,但是不执行又不返回,最终导致应用程序卡死。也就是说,添加任务到串行队列最好使用异步方法。
尽管串行队列所管理的任务是在一个线程中串行执行的,但串行队列和其他队列之间则是并行执行的。
dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);
不管加入的是串行队列还是并行队列,都会延时执行。
创建分组
dispatch_group_t queueGroup= dispatch_group_create();
添加block到队列且标记分组
dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
标记了分组的任务和没有标记分组的任务对于队列来说是没有区别的,标记分组影响的只是任务相对于其他任务的执行次序。
方法一:
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
用这个方法添加的block会成为路障:在它之前加入到队列的任务都执行完,才轮到它执行,等它执行完以后,之后的任务才可以执行。这个方法只对并行队列生效。
方法二:
dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
执行这个方法时,block仍未加入队列,只是先登记了,要等执行这个方法之前已标记了group的任务都完成了,才会真正加入队列。
验证一下:
与添加block到队列的基本方法综合起来验证这些特殊方法的效果(下图):
代码中新建了一个并行队列,新建了一个分组。
然后往这个队列中加入了若干block,按方法的执行顺序命名为任务1~任务7。
任务1和任务4标记了分组,其他没有标记,任务3为路障,任务6先要等标记了相同分组的任务完成后才会加入队列。任务之间原则上并行执行。
运行结果:
从运行结果可以看到,
任务1和任务2在任务3(路障)之前加入到队列,因此都在任务3之前执行完毕。
任务4、5、7是并行执行的。
任务6等标记了相同分组的任务4完成后才加入队列。