RunLoop学习

开篇几道面试题:

讲讲RunLoop,在项目中有用到吗?
runloop内部实现逻辑
runloop和线程的关系
timer与runloop的关系
程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应,为什么?怎样可以解决?
runloop是怎样响应用户操作的,具体流程是什么样?
说说runloop的几种状态
runloop的model作用是什么?


RunLoop,顾名思义,就是运行循环,就是在程序运行过程中循环做一些事情。
RunLoop的应用范围:
定时器(Timer)、PerformSelector、GCD
事件响应、手势识别、界面刷新、网络请求、自动释放池

RunLoop学习_第1张图片RunLoop做了一个类似do-while循环的事情,有事的时候去处理消息,没事的时候就睡眠并等待消息。
伪代码:
RunLoop学习_第2张图片
RunLoop的基本作用:
保持程序的持续运行
处理App中的各种事件(比如:触摸事件、定时器事件)
节省CPU资源,提高程序性能:该做事做事,没事去休息

RunLoop对象

iOS有两套API来访问和使用runloop
在Foundation框架下的NSRunLoop
在Core Foundation框架下的CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是对CFRunLoopRef的一层OC包装
CFRunLoopRef是开源的

NSRunLoop *runloop1 = [NSRunLoop currentRunLoop];
CFRunLoopRef runloop2 = CFRunLoopGetCurrent();

两种方法都可以拿到RunLoop对象。

RunLoop与线程关系

  1. 每条线程都有唯一的一个与之相对应的RunLoop对象
  2. RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  3. 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
  4. RunLoop会在线程结束时销毁
  5. 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop。
    稍微翻下源码:
    RunLoop学习_第3张图片RunLoop学习_第4张图片确实表明:
    每条线程都有唯一的一个与之相对应的RunLoop对象
    RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
    线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建

RunLoop相关的类

Core Foundation中关于RunLoop的5个类
1. CFRunLoopRef
2. CFRunLoopModeRef
3. CFRunLoopSourceRef
4. CFRunLoopTimerRef
5. CFRunLoopObserverRef

CFRunLoopRef的定义:
在这里插入图片描述RunLoop学习_第5张图片可以看出,里面有一个CFRunLoopModeRef类型的_currentModel,以及CFMutableSetRef类型的_modes。
需要说明的是,CFMutableSetRef是一个集合,集合里面是也是CFRunLoopModeRef类型,那么

CFRunLoopModeRef的结构又是怎样的呢?

RunLoop学习_第6张图片可以看出,CFRunLoopModeRef里面主要有:
mode的名字,以及sources0、sources1、observers、timers。
sources0、sources1里面装着CFRunLoopSourceRef类型的对象
observers里面装着CFRunLoopObserverRef类型的对象
timers里面装着CFRunLoopTimerRef类型的对象

可以看出:
CFRunLoopRef里面有CFRunLoopModeRef类型的_currentModel和_modes,而CFRunLoopModeRef里面又有CFRunLoopSourceRef、CFRunLoopObserverRef和CFRunLoopTimerRef三种类型。

CFRunLoopRef包含CFRunLoopModeRef
CFRunLoopModeRef包含CFRunLoopSourceRef、CFRunLoopObserverRef和CFRunLoopTimerRef

RunLoop学习_第7张图片
RunLoop里面有很多mode(模式),但是在运行的时候,只会选择一种mode(模式)。

CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的运行模式
一个RunLoop包含若干个Mode,每个Mode又包含若干个source0、source1、observer、timer
RunLoop启动时只能选择其中一个Mode作为currentMode。
如果需要切换Mode,只能退出当前loop,重新选择一个Mode进入。
不同组的source0、source1、observer、timer能分隔开来,互不影响
如果Mode里没有任何source0、source1、observer、timer,RunLoop会立马退出

常见的两种Mode模式:
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程在这个Mode下运行。
UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响。

NSRunLoop *runloop1 = [NSRunLoop currentRunLoop];
CFRunLoopRef runloop2 = CFRunLoopGetCurrent();
NSLog(@"%p, %p", runloop1, runloop2);

结果:0x600000f7cc60, 0x60000177c000

怎么打印的结果地址不一样呢?难道一个线程有两个runloop?
其实并不是,看下一个结果:

NSLog(@"111%@, 333%@", runloop1, runloop2);
111<CFRunLoop 0x60000177c000.....
333<CFRunLoop 0x60000177c000 [0x7fff80617cb0].....

可以看到,runloop1和runloop2的地址是一样的,都是runloop2的地址。
说明,NSRunLoop是对CFRunLoopRef的一层封装,实际runloop还是CFRunLoopRef的地址。

RunLoop学习_第8张图片

CFRunLoopModeRef里面的内容分别代表什么?

Source0
触摸事件处理
performSelector:onThread:

Source1
基于Port的线程间通信
系统事件捕捉

Timers
NSTimer
performSelector:withObject:afterDelay:

Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)


RunLoop学习_第9张图片lldb指令下,bt可以打印出函数调用栈
可以发现,触摸事件,是由Sourse0处理的。

CFRunLoopObserverRef

RunLoop学习_第10张图片添加Observer监听RunLoop的所有状态
RunLoop学习_第11张图片


RunLoop的运行逻辑

RunLoop学习_第12张图片RunLoop学习_第13张图片RunLoop学习_第14张图片


RunLoop休眠的实现原理

休眠是调用的内核api

RunLoop学习_第15张图片

RunLoop在实际开中的应用

  1. 控制线程生命周期(线程保活)
  2. 解决NSTimer在滑动时停止工作的问题
  3. 监控应用卡顿
  4. 性能优化

有关NSTimer和runloop的关系,可以参考NSTimer学习笔记


如何将子线程一直存在?(面试基本会遇到)

当然,你不能用strong指针拥有thread就算完事,那样只是拥有这个thread,但是thread可能已经失效,不能使用了。

@interface YZThread : NSThread
@end

#import "YZThread.h"
@implementation YZThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

----------------------------------
ViewController.m文件
#import "ViewController.h"
#import "YZThread.h"

@interface ViewController ()
@property (strong, nonatomic) YZThread *thread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.stopped = NO;
    __weak typeof(self) weakSelf = self;
    YZThread *thread = [[YZThread alloc] initWithBlock:^{
        NSLog(@"begin - %s, %@", __func__, [NSThread currentThread]);
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (weakSelf && !weakSelf.isStopped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        /**
        [[NSRunLoop currentRunLoop] run];
        相当于在执行下面的代码
        while (1) {
            [[NSRunLoop currentRunLoop] runMode:<#(nonnull NSRunLoopMode)#> beforeDate:<#(nonnull NSDate *)#>]
        }
        有个while循环,一直在调用
         */
        NSLog(@"end - %s, %@", __func__, [NSThread currentThread]);
    }];
    self.thread = thread;
    [thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (self.thread == nil) {
        return;
    }
    //waitUntilDone:YES 等test方法执行完毕,再执行打印123。NO  不等test方法执行完毕,就执行打印123
    [self performSelector:@selector(test) onThread:self.thread withObject:self waitUntilDone:YES];
    NSLog(@"123");
}

- (IBAction)stop:(id)sender {
    if (self.thread == nil) {
        return;
    }
     [self performSelector:@selector(threadStop) onThread:self.thread withObject:self waitUntilDone:YES];
}

- (void)threadStop
{
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());//停止runloop
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
    self.thread = nil;
}

//子线程需要做的操作
- (void)test
{
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [self stop:nil];
}
@end

简单来说就是:
在子线程中加入runloop来保证子线程存活

上面的用法能满足需求,但,可以做的更好,做下封装:

#import 
@interface YZKeepAliveThread : NSObject
/**
 开启线程
 */
- (void)run;

/**执行block*/
- (void)excuteTaskWithBlock:(void(^)(void))block;

/**
结束线程
*/
- (void)stop;
@end



#import "YZKeepAliveThread.h"
@interface YZKeepAliveThread ()
@property (strong, nonatomic) NSThread *thread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end

@implementation YZKeepAliveThread

- (instancetype)init
{
    if (self = [super init]) {
        self.stopped = NO;
        __weak typeof(self) weakSelf = self;
        self.thread = [[NSThread alloc] initWithBlock:^{
            NSLog(@"begin - %s, %@", __func__, [NSThread currentThread]);
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            while (weakSelf && !weakSelf.isStopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
            /**
            [[NSRunLoop currentRunLoop] run];
            相当于在执行下面的代码
            while (1) {
                [[NSRunLoop currentRunLoop] runMode:<#(nonnull NSRunLoopMode)#> beforeDate:<#(nonnull NSDate *)#>]
            }
            有个while循环,一直在调用
             */
            NSLog(@"end - %s, %@", __func__, [NSThread currentThread]);
        }];
    }
    return self;
}

/**
 开启线程
 */
- (void)run
{
    [self.thread start];
}

/**执行block*/
- (void)excuteTaskWithBlock:(void(^)(void))block
{
    if (self.thread == nil || block == nil) {
        return;
    }
    //waitUntilDone:YES 等test方法执行完毕,再执行打印123。NO  不等test方法执行完毕,就执行打印123
    [self performSelector:@selector(__excuteTaskWithBlock:) onThread:self.thread withObject:block waitUntilDone:NO];
}

- (void)__excuteTaskWithBlock:(void(^)(void))block
{
    block();
}

/**
结束线程
*/
- (void)stop
{
    if (self.thread == nil) {
            return;
     }
     [self performSelector:@selector(threadStop) onThread:self.thread withObject:self waitUntilDone:YES];
}

- (void)threadStop
{
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());//停止runloop
    self.thread = nil;
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [self stop];
}
@end


ViewController.m文件
#import "ViewController.h"
#import "YZKeepAliveThread.h"
@interface ViewController ()
@property (strong, nonatomic) YZKeepAliveThread *thread;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[YZKeepAliveThread alloc] init];
    [self.thread run];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    __weak typeof(self) weakSelf = self;
    [self.thread excuteTaskWithBlock:^{
        [weakSelf test];
    }];
}

- (IBAction)stop:(id)sender {
    [self.thread stop];
}

//子线程需要做的操作
- (void)test
{
    NSLog(@"子线程需要做的操作--%s, %@", __func__, [NSThread currentThread]);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

更多学习有关RunLoop深入理解RunLoop

你可能感兴趣的:(iOS底层学习,iOS)