文章目录
1、NSRunLoop
1.1 介绍
RunLoop从字面上解析,就是一直循环的跑,实际上它也是在一直在跑。通常来说,一个线程执行完一个任务后,线程就会退出销毁。但是我们可以通过RunLoop操作,使该线程常驻,在有任务的时候唤醒线程执行相应的任务,在没有任务执行的时候保存睡眠状态,时刻准备着任务的呼唤。
1.2 语法
1、主线程相关联的RunLoop创建
CFRunLoopRef源码
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程 根据传入的主线程创建主线程对应的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主线程 将主线程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
2、 创建与子线程相关联的RunLoop
CFRunLoopRef源码
// 从字典中获取子线程的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
// 如果子线程的runloop不存在,那么就为该线程创建一个对应的runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 把当前子线程和对应的runloop保存到字典中
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个 Dictionary 里。所以我们创建子线程RunLoop时,只需在子线程中获取当前线程的RunLoop对象即可[NSRunLoop currentRunLoop];
如果不获取,那子线程就不会创建与之相关联的RunLoop,并且只能在一个线程的内部获取其 RunLoop
[NSRunLoop currentRunLoop];
方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。
RunLoop 的销毁发生在线程结束时。
3、RunLoop退出
- 主线程销毁RunLoop退出
- Mode中有一些Timer 、Source、 Observer,这些保证Mode不为空时保证RunLoop没有空转并且是在运行的,当Mode中为空的时候,RunLoop会立刻退出
- 我们在启动RunLoop的时候可以设置什么时候停止
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate
RunLoop处理逻辑
1、通知观察者 run loop 已经启动
2、通知观察者将要开始处理Timer事件
3、通知观察者将要处理非基于端口的Source0
4、启动准备好的Souecr0
5、如果基于端口的源Source1准备好并处于等待状态,立即启动:并进入步骤9
6、通知观察者线程进入休眠
7、将线程置于休眠直到任一下面的事件发生
(1)某一事件到达基于端口的源
(2)定时器启动
(3)Run loop 设置的时间已经超时
(4)run loop 被显式唤醒
8、通知观察者线程将被唤醒
9、处理未处理的事件,跳回2
(1)如果用户定义的定时器启动,处理定时器事件并重启 run loop。进入步骤 2
(2)如果输入源启动,传递相应的消息
(3)如果 run loop 被显式唤醒而且时间还没超时,重启 run loop。进入步骤 2
10、通知观察者run loop 结束
1.3注意点
1.每条线程都有唯一的与之对应的RunLoop对象。
2.主线程的RunLoop已经创建好了,而子线程的需要手动创建。(也就是说子线程的RunLoop默认是关闭的,因为有时候开了个线程但却没有必要开一个RunLoop,不然反而浪费了资源。 )
// main.m
int res = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
// 永远运行不到这里
// 在UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))这个函数的内部就已经启动了一个RunLoop,所以函数一直没有返回,这才使得程序保持运行。
NSLog(@"-----");
return res;
3.RunLoop在第一次获取时创建,在线程结束时销毁。(这就相当于 线程是一个类,RunLoop是类里的实例变量,这样便于理解)
2、NSThread
2.1 介绍
一个NSThread对象就代表一条线程
2.2 语法
创建、启动线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];// 启动线程,在线程thread中执行self的run方法
//创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"XL"];
//隐式创建并启动线程
[self performSelectorInBackground:@selector(run:) withObject:@"OC"];
优点:简单快捷
缺点:无法对线程进行更详细的设置
阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 调用这个方法的线程进入阻塞状态,
[NSThread sleepForTimeInterval:2];
//第二种线程暂停睡眠的方法
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[NSThread sleepUntilDate:[NSDate distantFuture]];
强制停止线程 ,
调用之后会立即终止线程,即使任务还没有执行完成也会中断.不推荐使用此方式退出子线程,可能会造成内存泄漏
+ (void)exit;// 这是一个类方法,调用这个方法的所在线程会进入死亡状态
[NSThread exit];
取消线程
- NSThread提供了一个cancel方法,和一个cancelled属性
cancel方法只是让isCancelled属性值置为YES,修改了线程状态,并不会停止/退出线程
,任务会继续执行除非你做了一些其他操作- 我们可以监听isCancelled值的变化,做出相应的调整,如下面示例代码中,在子线程任务中监听cancel状态,如果cancel == yes , 回收内存等资源,然后return或者调用exit退出线程,随后线程就会销毁
- (void)createThreadCancel
{
XLThread *thread = [[XLThread alloc] initWithTarget:self selector:@selector(runCancel) object:@"XL"];
thread.name = @"cancel-thread";
[thread start];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"before thread.isCancelled = %zd",thread.isCancelled);
[thread cancel];
NSLog(@"after thread.isCancelled = %zd",thread.isCancelled);
});
}
- (void)runCancel
{
for (NSInteger i = 0; i<10000; i++) {
NSLog(@"-----%zd , [NSThread currentThread] = %@", i , [NSThread currentThread]);
sleep(0.01);
if ([NSThread currentThread].isCancelled) {
// 进行线程退出前的一些操作,如内存回收等
[NSThread exit]; // 线程退出,其后面的代码不会被执行 , 或者调用return;
NSLog(@"-----exit [NSThread currentThread] = %@" , [NSThread currentThread]);
}
}
}
2.3 注意点
多线程的安全隐患
解决方法:互斥锁,@synchronized(锁对象) { // 需要锁定的代码 },注意:锁定1份代码只用1把锁,用多把锁是无效的,必须是同一个变量,虽然这个变量可以是任意类型的。
互斥锁的优缺点
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
互斥锁的使用前提:多条线程抢夺同一块资源,如果没有抢夺资源,就不要加锁