NSThread 和 RunLoop

本文详细介绍了iOS开发中的多线程技术,包括线程的创建与管理、线程间通信、线程优先级设置等内容,并深入探讨了RunLoop的工作原理及如何利用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 会直接退出,不进入循环。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值