runloop理解与运用

什么是runloop?

个人理解为:他是一个运行循环,因为他的存在才会导致我们的运行的程序才不会挂掉,我们的事件处理、定时器等等的事件才能有所反应。
举个例子:
看下如下的代码:

#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

可以看到命令行的main是没有runloop的,但是我们的ios程序是有runloop的,也就是我们的命令行函数执行完了 直接就return0了 那么程序也就死了 ,而我们的ios程序会一直存在。
所以我们猜测一下UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));的底层实现大致是这样的:

int vel = 0;
        do{
            // 消息监听
            
            // 消息处理
        }while (0 == vel);

runloop的对象获取

    NSRunLoop *mainRun = [NSRunLoop mainRunLoop];
    NSRunLoop *currentRun = [NSRunLoop currentRunLoop];

    CFRunLoopRef mainRunRef = CFRunLoopGetMain();
    CFRunLoopRef currentRunRef = CFRunLoopGetCurrent();
    
    NSLog(@"%p %p ==== %p %p",mainRun,currentRun,mainRunRef,currentRunRef);
image.png

我们可以看到mainrunloop和currentloop是一样的 但是c语言的runloop和我们的oc的runloop是不一样的,其实一个线程只对应一个runloop,也就是说目前四个都是一样的 下面我证明一下


image.png

由此可见他们都是同一个runloop

runloop与线程的关系

1.一个线程与之仅有一个runloop与之对应
2.runloop保存在一个全局的dictory里,线程作为key,value作为runloop
3.线程刚刚开始创建的时候并没有runloop,在第一次获取的时候runloop才开始创建。所以说主线程默认也是没有runloop的 只不过我们开始调用main函数。在main函数中调用了UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));所以他内部是有获取的 所以主线程是存在runloop的。
4.runloop会在线程结束的时候进行销毁,子线程默认是没有开启runloop的。

runloop相关的类 、结构

我们到这个网站(https://opensource.apple.com/tarballs/CF)下载一下底层的源码,打开新建一个工程将我们下载的资料拖进去

image.png

1.在coreFoundation中与runloop相关的五个类分别为:
CFRunLoopSourceRef CFRunLoopModeRef CFRunLoopRef CFRunLoopObserverRef CFRunLoopTimerRef
2.runloop 的结构如下:

typedef struct __CFRunLoop *CFRunLoopRef;
    struct __CFRunLoop {
        pthread_t _pthread;
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
    }

其中他的mode结构如下:

typedef struct __CFRunLoopode *CFRunLoopModeRef;
    struct __CFRunLoopode{
        CFStringRef _name;
        CFMutableSetRef _source0;
        CGMutablePathRef _source1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;

    };

其中modes包含了很多的CFRunLoopModeRef,只不过其中的一个mode是_currentMode
解释mode中的每一个的作用
_name:名字
_source0:触摸事件处理、performSeletor:onThread
_source1: 事件的捕捉、或者线程之间的port通信
_timers: 定时器、[self performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#>]
_observers:各种状态。
下面我们来验证一下_source0,打开函数的调用栈


image.png

可以看到source0 确实是触摸事件的处理。
下面在看一下观察的状态

 // 创建观察对象
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: // 即将进入loop
                NSLog(@"kCFRunLoopEntry");
                break;
            case kCFRunLoopBeforeSources: // 即将处理source
                NSLog(@"kCFRunLoopBeforeSources");
                break;
            case kCFRunLoopBeforeTimers: // 即将处理timer
                NSLog(@"kCFRunLoopBeforeTimers");
                break;
            case kCFRunLoopBeforeWaiting: // 即将进入睡眠
                NSLog(@"kCFRunLoopBeforeWaiting");
                break;
            case kCFRunLoopAfterWaiting: // 刚从睡眠中唤醒
                NSLog(@"kCFRunLoopAfterWaiting");
                break;
            case kCFRunLoopExit: // 退出loop
                NSLog(@"kCFRunLoopExit");
                break;
            default:
                break;
        }
    });
   //  kCFRunLoopCommonModes == kCFRunLoopDefaultMode(没有滚动事件的处理) + UITrackingRunLoopMode(滚动时候的情形)
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
image.png

可以看到runloop在点击事件之前是结束等待,然后在继续监听 也就是我们没有做任何事情之前他是处于睡眠状态的。

  • runloop是开始创建的时候就对应一种mode,其中他的常用mode只有kCFRunLoopDefaultMode(没有滚动事件的处理) 和 UITrackingRunLoopMode(滚动时候的情形),他只能结束一种mode在进入另一种mode,而且每个mode分别管理自己的CFRunLoopModeRef
    下面来验证一下,
    我们在xib中增加一个uitextview 滚动来看和停止滚动的


    image.png

    代码如下:

// 创建观察对象
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        switch (activity) {
            case kCFRunLoopEntry:
            {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit:
            {

                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit %@",mode);
                CFRelease(mode);
                break;

            }
            default:
            {
                break;
            }
        }
    });
   //  kCFRunLoopCommonModes == kCFRunLoopDefaultMode(没有滚动事件的处理) + UITrackingRunLoopMode(滚动时候的情形)
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);  
image.png

可以看到mode切换了 在滚动停止的时候。

  • 总结:
    1.如果当前的mode中没有timer、source0、source1、observer等等的 runloop会立刻退出。
    2.runloop创建的时候默认设置一个mode
    3.runloop只能从当前的mode退出切换到另一个mode

runloop的执行过程

1.通知observer:进入loop
2.通知observer:即将处理timer
3.通知observer:即将处理sources
4.处理blocks
比如这种情况

CFRunLoopPerformBlock(<#CFRunLoopRef rl#>, <#CFTypeRef mode#>, <#^(void)block#>)

5.处理source0(我们都知道source0是对事件的处理)
6.如果存在source1,那么就会进行第8步
7.通知observer:进行休眠
8.通知observer:结束休眠(被某个消息唤醒)
8.1 处理timers
8.2 处理gcd相关的可能
8.3 处理source1
9.处理blocks
10.根据执行结果决定如何的操作
{
回到第二步骤还是退出loop
}
11.通知observer:退出loop


image.png

runloop的运用:

  • 1.线程保活
    具体说一下他的作用:
    有时候我们需要在一个自线程中做事情,但是我们都知道一般来说子线程做完事情之后就死掉,如果我们想在一个子线程中一直做一个事情,让我们来控制自线程的生命周期我们普通的方法是实现不了的。
    我们需要用到runloop的知识,让我们的子线程一直存在,直到我们想让他销毁他就销毁。
    代码如下:

#import "ViewController.h"
#import "DGThread.h"

@interface ViewController ()
@property (strong, nonatomic) DGThread *thread;
@property (assign, nonatomic) BOOL isStopThread;

@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    __weak typeof(self)weakSelf = self;
    self.thread = [[DGThread alloc] initWithBlock:^{
        
        // 这里面进行线程包活
        NSLog(@"当前的线程 ---- %@",[NSThread currentThread]);
        // 往runloop里面添加观察者、observer、timer等等
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        // 开启runloop 让他来进行监听,好处理事件
        while (weakSelf.thread && !weakSelf.isStopThread) {
            NSLog(@"weakSelf.isStopThread --- %d",weakSelf.isStopThread);
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }];
    // 线程开启了
    [self.thread start];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    
    // 这个参数穿点为NO,为立刻执行后面的事情,比如立刻执行 123
    if (!self.thread) return;
    [self performSelector:@selector(startAction) onThread:self.thread withObject:nil waitUntilDone:NO];
    NSLog(@"123");
    
}

- (void)startAction{
    
    NSLog(@"老夫在这里做了很多的事情");
}

- (void)stop{
    // 停止runloop
    NSLog(@"%s",__func__);
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.isStopThread = YES;
    NSLog(@"已经停止定时器了");
}
-(void)dealloc{
    [self stopThread];
    NSLog(@"%s",__func__);
}
- (IBAction)stopThread {
    if (!self.thread || self.isStopThread) return;
    [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
    NSLog(@"已经停止线程了");
}
@end
image.png
  • 2防止定时器失效
    我们直到我们创建的定时器如果指定的模式是NSDefaultRunloopMode的情况下,那么如果我们空间中有一个UITextView在那滚动,那么定时器就不走了,如果停止滚动就走了。比如:
   
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"asdasdasdasdasd");
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

值得说明上面的这种写法和这种写法是等价的

[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        
    }];

如果我们将这个模式修改为:

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"asdasdasdasdasd");
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

我们发现定时器 即使是在滚动的情况下还是能走,当然静止的时候也还是能走的。
3.未完待续,我会继续更新 我觉得runloop的应用。

你可能感兴趣的:(runloop理解与运用)