NSThread
线程创建
对于NSThread来说,每一个对象就代表着一个线程,NSThread提供了2种创建线程的方法:
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
- detach方法直接创建并启动一个线程去Selector,由于没有返回值,如果需要获取新创建的Thread,需要在执行的Selector中调用
-[NSThread currentThread]
获取 - init方法初始化线程并返回,线程的入口函数由Selector传入。线程创建出来之后需要手动调用
-start
方法启动
线程操作
创建好线程之后当然需要对线程进行操作,NSThread给线程提供的主要操作方法有启动,睡眠,取消,退出
启动
我们使用init方法将线程创建出来之后,线程并不会立即运行,只有我们手动调用-start
方法才会启动线程
- (void)start NS_AVAILABLE(10_5, 2_0);
这里要注意的是:部分线程属性需要在启动前设置,线程启动之后再设置会无效。如qualityOfService
属性
睡眠
NSThread提供了2个让线程睡眠的方法,一个是根据NSDate传入睡眠时间,一个是直接传入NSTimeInterval
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
看到sleepUntilDate:
大家可能会想起runloop的runUntilDate:
。他们都有阻塞线程的效果,但是阻塞之后的行为又有不一样的地方,使用的时候,我们需要根据具体需求选择合适的API。
sleepUntilDate:
相当于执行一个sleep的任务。在执行过程中,即使有其他任务传入runloop,runloop也不会立即响应,必须sleep任务完成之后,才会响应其他任务runUntilDate:
虽然会阻塞线程,阻塞过程中并不妨碍新任务的执行。当有新任务的时候,会先执行接收到的新任务,新任务执行完之后,如果时间到了,再继续执行runUntilDate:
之后的代码
取消
对于线程的取消,NSThread提供了一个取消的方法和一个属性
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
- (void)cancel NS_AVAILABLE(10_5, 2_0);
不过大家千万不要被它的名字迷惑,调用-cancel
方法并不会立刻取消线程,它仅仅是将cancelled
属性设置为YES。cancelled
也仅仅是一个用于记录状态的属性。线程取消的功能需要我们在main函数中自己实现
要实现取消的功能,我们需要自己在线程的main函数中定期检查isCancelled
状态来判断线程是否需要退出,当isCancelled
为YES的时候,我们手动退出。如果我们没有在main函数中检查isCancelled
状态,那么调用-cancel
将没有任何意义
退出
与充满不确定性的-cancel
相比,-exit
函数可以让线程立即退出。
+ (void)exit;
-exit
属于核弹级别终极API,调用之后会立即终止线程,即使任务还没有执行完成也会中断。这就非常有可能导致内存泄露等严重问题,所以一般不推荐使用。
对于有runloop的线程,可以使用
CFRunLoopStop()
结束runloop配合-cancel
结束线程[2016.1.19更新]
感谢@NSHYJ的提醒。runloop启动的方法中run
和runUntilDate:
都无法使用CFRunLoopStop()
退出,只有runMode:beforeDate:
可以响应CFRunLoopStop()
,所以要想使用CFRunLoopStop()
退出runloop,必须使用runMode:beforeDate:
启动
线程通讯
线程准备好之后,经常需要从主线程把耗时的任务丢给辅助线程,当任务完成之后辅助线程再把结果传回主线程传,这些线程通讯一般用的都是perform方法
//①
- (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;
//③
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
//④
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
- ①:将selector丢给主线程执行,可以指定runloop mode
- ②:将selector丢给主线程执行,runloop mode默认为common mode
- ③:将selector丢个指定线程执行,可以指定runloop mode
- ④:将selector丢个指定线程执行,runloop mode默认为default mode
所以我们一般用③④方法将任务丢给辅助线程,任务执行完成之后再使用①②方法将结果传回主线程。
注意:perform方法只对拥有runloop的线程有效,如果创建的线程没有添加runloop,perform的selector将无法执行。
线程优先级
每个线程的紧急程度是不一样的,有的线程中任务你也许希望尽快执行,有的线程中任务也许并不是那么紧急,所以线程需要有优先级。优先级高线程中的任务会比优先级低的线程先执行
NSThread有4个优先级的API
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
@property double threadPriority NS_AVAILABLE(10_6, 4_0); // To be deprecated; use qualityOfService below
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0); // read-only after the thread is started
- 前2个是类方法,用于设置和获取当前线程的优先级
- threadPriority属性可以通过对象设置和获取优先级
- 由于线程优先级是一个比较抽线的东西,没人能知道0.5和0.6到底有多大区别,所以iOS8之后新增了qualityOfService枚举属性,大家可以通过枚举值设置优先级
|
|
NSQualityOfService主要有5个枚举值,优先级别从高到低排布:
- NSQualityOfServiceUserInteractive:最高优先级,主要用于提供交互UI的操作,比如处理点击事件,绘制图像到屏幕上
- NSQualityOfServiceUserInitiated:次高优先级,主要用于执行需要立即返回的任务
- NSQualityOfServiceDefault:默认优先级,当没有设置优先级的时候,线程默认优先级
- NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务
- NSQualityOfServiceBackground:后台优先级,用于完全不紧急的任务
一般主线程和没有设置优先级的线程都是默认优先级
主线程和当前线程
NSThread也提供了非常方便的获取和判断主线程的API
@property (readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
- isMainThread:判断当前线程是否是主线程
- mainThread:获取主线程的thread
除了获取主线程,我们也可以使用-currentThread
获取当前线程
+ (NSThread *)currentThread;
线程通知
NSThread有三个线程相关的通知
NSString * const NSWillBecomeMultiThreadedNotification;
NSString * const NSDidBecomeSingleThreadedNotification;
NSString * const NSThreadWillExitNotification;
- NSWillBecomeMultiThreadedNotification:由当前线程派生出第一个其他线程时发送,一般一个线程只发送一次
- NSDidBecomeSingleThreadedNotification:这个通知目前没有实际意义,可以忽略
- NSThreadWillExitNotification线程退出之前发送这个通知
不过这个方法必须在self.thread线程下调用。如果当前是主线程。可以perform到self.thread下调用这个方法结束线程
您的资助是我最大的动力!
金额随意,欢迎来赏!