runloop理解与运用

什么是runloop?

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

#import <Foundation/Foundation.h>

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<UITouch *> *)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的应用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容