NSThread 和 RunLoop

线程:一个独立执行代码的路径

进程:一个可执行程序,包含多个线程

使用场景

将与UI界面显示、影响界面流畅度的事情 都应该 子线程处理。

一. NSThread

1.NSThread创建

  1. 带有返回值的创建

    // 创建子线程 1
      NSThread *oneThread =  [[NSThread alloc]initWithTarget:self selector:@selector(threadEvent) object:nil];

    注意:此种方式创建的线程需要手动启动,下面会讲到。

  2. 不带返回值,创建直接启动进入线程

    [NSThread detachNewThreadSelector:@selector(threadEvent3:) toTarget:self withObject:@"123"];

2.NSThread 开启

    // 进入线程 1
    [oneThread start];
    // 进入线程 2
    [twoThread start];

3.NSThread 睡眠(等待)

+ (void)sleepUntilDate:(NSDate *)date;
//[NSThread sleepForTimeInterval:1.0];暂停一秒

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
//[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];暂停一秒

4.NSThread 取消

对于线程的取消,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函数中自己实现.比如上面在创建线程的时候指定的方法threadEvent就是这个线程的man函数。

5.NSThread 退出

-exit函数可以让线程立即退出,不管线程是否执行完毕,如果任务没有执行完,内存泄漏。

+ (void)exit;

6.NSThread 通讯

例如:主线程拿到json数据,丢给子线程,子线程解析完毕,再传回主线程。

//① 
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray *)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 *)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);

7.NSThread 优先级

希望先执行的线程设定高优先级,不着急的低优先级

+ (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 优先级的枚举属性

枚举优先级

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    NSQualityOfServiceUserInteractive = 0x21,//最高 用户交互级别
    NSQualityOfServiceUserInitiated = 0x19,//次高
    NSQualityOfServiceDefault = -1//默认
    NSQualityOfServiceUtility = 0x11,//不需要立即返回的任务
    NSQualityOfServiceBackground = 0x09,//后台 一点都不着急的
}

8.NSThread 主线程

@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);//获取主线程的thread

在线程的main函数中获取当前线程

+ (NSThread *)currentThread;

9.NSThread 通知

注册线程通知相关

//由当前线程派生出第一个其他线程时发送,一般一个线程只发送一次
NSString * const NSWillBecomeMultiThreadedNotification;

//我没用过。。。。。看意思是成为单线程通知
NSString * const NSDidBecomeSingleThreadedNotification;

//线程退出
NSString * const NSThreadWillExitNotification;

二.RunLoop

每一个线程都有一个runloop,如果不希望线程执行完任务退出,而是等待继续执行。需要启动runloop,主线程的runloop是系统自动启动的。

runloop的特点

  • RunLoop是iOS事件响应与任务处理最核心的机制

  • 主线程的RunLoop在应用启动的时候就会自动创建

  • 其他线程则需要在该线程下自己启动
  • 不能自己创建RunLoop
  • RunLoop并不是线程安全的,所以需要避免在其他线程上调用当前线程的RunLoop
  • RunLoop负责管理autorelease pools
  • RunLoop负责处理消息事件,即输入源事件和计时器事件

什么时候使用RunLoop

  1. 在子线程中使用 perfromSelect 方法和,因为这个方法实际上是创建一个timer加入到runloop,没有runloop就执行不了
  2. 在子线程使用nstimer
  3. 使一个子线程常驻,需要的时候给它派发任务
  4. 使用port 和其他线程通信

runLoop 与AutoreleasePool的关系

  1. RunLoop负责管理autorelease pools
  2. RunLoop通常在2次sleep之间释放autorelease的对象,也就是在RunLoop执行一圈完成的时候。

Runloop处理逻辑:

​ 1,通知Observer,即将进入loop

​ 2,通知Observer,将要处理timer

​ 3,通知Observer,将要处理Source0

​ 4,处理Source0
​ 5,如果有Source1,跳到第9步

​ 6,通知Obesrcer,线程即将休眠

​ 7,休眠,等待唤醒

​ 8,通知Observer,线程刚被唤醒

​ 9,处理唤醒时收到的消息,之后跳回2

​ 10,通知Oberver,即将退出Loop

runloop对外接口

在 CoreFoundation 里面关于 RunLoop 有5个类:

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

复制代码

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
};

复制代码

上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

你可能感兴趣的:(十方无敌)