iOS重学之路--Runloop

这就是一片文言文,请各位客官耐着性子去看。

  • 1.1 字面意思

      (1) 运行循环
      (2) 跑圈
    
  • 1.2 基本作用(作用重大)

      (1) 保持程序的持续运行(ios程序为什么能一直活着不会死)
      (2) 处理app中的各种事件(比如触摸事件、定时器事件【NSTimer】、selector事件【选择器·performSelector···】)
      (3) 节省CPU资源,提高程序性能,有事情就做事情,没事情就睡觉
    
  • 1.3 重要说明

      (1)如果没有Runloop,那么程序一启动就会退出,什么事情都做不了。
      (2)如果有了Runloop,那么相当于在内部有一个死循环,能够保证程序的持续运行
      (3)main函数中的Runloop
              a 在UIApplication函数内部就启动了一个Runloop
                  该函数返回一个int类型的值
              b 这个默认启动的Runloop是跟主线程相关联的
    
  • 1.4 Runloop对象

      (1)在iOS开发中有两套api来访问Runloop
          a.foundation框架【NSRunloop】
          b.core foundation框架【CFRunloopRef】
      (2)NSRunLoop和CFRunLoopRef都代表着RunLoop对象,它们是等价的,可以互相转换
      (3)NSRunLoop是基于CFRunLoopRef的一层OC包装
    
  • 1.5 Runloop参考资料

苹果官方文档

CFRunLoopRef开源代码下载地址

  • 1.6 Runloop与线程
    1.Runloop和线程的关系:一个Runloop对应着一条唯一的线程
        
    2.Runloop的创建:主线程Runloop已经创建好了,子线程的runloop需要手动创建
    3.Runloop的生命周期:在第一次获取时创建,在线程结束时销毁
  • 1.7 获得Runloop对象
1.获得当前Runloop对象
    //01 NSRunloop
     NSRunLoop * runloop1 = [NSRunLoop currentRunLoop];
    //02 CFRunLoopRef
    CFRunLoopRef runloop2 =   CFRunLoopGetCurrent();

2.拿到当前应用程序的主Runloop(主线程对应的Runloop)
    //01 NSRunloop
     NSRunLoop * runloop1 = [NSRunLoop mainRunLoop];
    //02 CFRunLoopRef
     CFRunLoopRef runloop2 =   CFRunLoopGetMain();

3.注意点:开一个子线程创建runloop,不是通过alloc init方法创建,而是直接通过调用currentRunLoop方法来创建,它本身是一个懒加载的。
4.在子线程中,如果不主动获取Runloop的话,那么子线程内部是不会创建Runloop的。可以下载CFRunloopRef的源码,搜索_CFRunloopGet0,查看代码。
5.Runloop对象是利用字典来进行存储,而且key是对应的线程,Value为该线程对应的Runloop。
  • 1.8 Runloop相关类

(1)Runloop运行原理图


2.png

上图显示了线程的输入源

1.基于端口的输入源(Port Sources)
2.自定义输入源(Custom Sources)
3.Cocoa执行Selector的源(performSelectorxxxx方法)
4.定时源(Timer Sources )
线程针对上面不同的输入源,有不同的处理机制

1.handlePort——处理基于端口的输入源
2.customSrc——处理用户自定义输入源
3.mySelector——处理Selector的源
4.timerFired——处理定时源

(2)五个相关的类

a.CFRunloopRef
b.CFRunloopModeRef【Runloop的运行模式】
c.CFRunloopSourceRef【Runloop要处理的事件源】
d.CFRunloopTimerRef【Timer事件】
e.CFRunloopObserverRef【Runloop的观察者(监听者)】

(3)Runloop和相关类之间的关系图

1.png

(4)Runloop要想跑起来,它的内部必须要有一个mode,这个mode里面必须有source\timer,至少要有其中的一个。(因为source\timer是主动发消息过来的,有信号源可以监听,但是observer是被动监听的,它不是主动的,因此,mode 加这个Runloop是跑步起来的)

- (void)viewDidLoad {
    [super viewDidLoad];
    
//    NSLog(@"%@", [NSRunLoop currentRunLoop]);
    
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(execute) object:nil];
    self.thread = thread;
        [thread start];
}

- (void)execute
{
    /** 当你为此时创建的runloop添加observer时,它并不能运行起来(线程还是会死掉的)
     //        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
     //    [NSRunLoop currentRunLoop] addTimer:<#(nonnull NSTimer *)#> forMode:<#(nonnull NSString *)#>
     只有你添加了以上两中方式才能确保runloop存在,线程不死*/
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----");
    });
    
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    CFRelease(observer);
//
    //        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    //    [NSRunLoop currentRunLoop] addTimer:<#(nonnull NSTimer *)#> forMode:<#(nonnull NSString *)#>
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"------1111");
}
- (void)test
{
    NSLog(@"----------test----%@", [NSThread currentThread]);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
  • CFRunloopModeRef

      1.CFRunloopModeRef代表着Runloop的运行模式
      2.一个Runloop中可以有多个mode,一个mode里面又可以有多个source\observer\timer等等
      3.每次runloop启动的时候,只能指定一个mode,这个mode被称为该Runloop的当前mode
      4.如果需要切换mode,只能先退出当前Runloop,再重新指定一个mode进入
      5.这样做主要是为了分割不同组的定时器等,让他们相互之间不受影响
      6.系统默认注册了5个mode
          a.kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
          b.UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
          c.UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用(自我感觉然并卵)
          d.GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到(自我感觉然并卵)
          e.kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
    
  • CFRunloopTimerRef

(1)NSTimer相关代码

/*
    说明:
    (1)runloop一启动就会选中一种模式,当选中了一种模式之后就不鸟其它的模式。一个mode里面可以添加多个NSTimer,也就是说以后当创建NSTimer的时候,可以指定它是在什么模式下运行的。
    (2)它是基于时间的触发器,说直白点那就是时间到了我就触发一个事件,触发一个操作。基本上说的就是NSTimer
    
*/
- (void)timer2
{
    //NSTimer 调用了scheduledTimer方法,那么会自动添加到当前的runloop里面去,而且runloop的运行模式kCFRunLoopDefaultMode

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    //更改模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

}
- (void)timer1
{
    //    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    //定时器添加到UITrackingRunLoopMode模式,一旦runloop切换模式,那么定时器就不工作
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

    //定时器添加到NSDefaultRunLoopMode模式,一旦runloop切换模式,那么定时器就不工作
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

    //占位模式:common modes标记
    //被标记为common modes的模式 kCFRunLoopDefaultMode  UITrackingRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
  • CFRunloopSourceRef

      1.是事件源也就是输入源,有两种分类模式;
        一种是按照苹果官方文档进行划分的
        另一种是基于函数的调用栈来进行划分的(source0和source1)。
      2.具体的分类情况
          (1)以前的分法
              Port-Based Sources
              Custom Input Sources
              Cocoa Perform Selector Sources
    
          (2)现在的分法
              Source0:非基于Port的
              Source1:基于Port的,通过内核和其他线程通信,接收、分发系统事件
      3.可以通过打断点的方式查看一个方法的函数调用栈
    
  • CFRunLoopObserverRef

(1)CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

(2)如何监听

 //创建一个runloop监听者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

        NSLog(@"监听runloop状态改变---%zd",activity);


       switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"runloop进入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"runloop要去处理timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"runloop要去处理Sources");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"runloop要睡觉了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"runloop醒来啦");
                break;

            case kCFRunLoopExit:
                NSLog(@"runloop退出");
                break;
            default:
                break;
        }
    });

    //为runloop添加一个监听者
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    CFRelease(observer);

(3)监听的状态

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   //即将进入Runloop
    kCFRunLoopBeforeTimers = (1UL << 1),    //即将处理NSTimer
    kCFRunLoopBeforeSources = (1UL << 2),   //即将处理Sources
    kCFRunLoopBeforeWaiting = (1UL << 5),   //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),    //刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),            //即将退出runloop
    kCFRunLoopAllActivities = 0x0FFFFFFFU   //所有状态改变
};
  • 1.9 Runloop运行逻辑
3.png
4.png

(二)自动释放池

自动释放池什么时候释放?


通过Observer监听RunLoop的状态,一旦监听到RunLoop即将进入睡眠等待状态,就释放自动释放池(kCFRunLoopBeforeWaiting)
kCFRunLoopEntry; // 创建一个自动释放池
kCFRunLoopBeforeWaiting; // 销毁自动释放池,创建一个新的自动释放池
kCFRunLoopExit; // 销毁自动释放池

/*
 kCFRunLoopEntry = (1UL << 0),  1
 kCFRunLoopBeforeTimers = (1UL << 1), 2
 kCFRunLoopBeforeSources = (1UL << 2), 4
 kCFRunLoopBeforeWaiting = (1UL << 5), 32
 kCFRunLoopAfterWaiting = (1UL << 6), 64
 kCFRunLoopExit = (1UL << 7), 128
 */

// activities = 0x1 == 1
// activities = 0xa0 == 160

//<CFRunLoopObserver 0x7fe6a3611c50 [0x10a417a40]>{valid = Yes, activities = 0x1,
 repeats = Yes, order = -2147483647,
 callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10a59e4c2),
 context = <CFArray 0x7fe6a3611ae0 [0x10a417a40]>{type = mutable-small, count = 1,
 values = ( 0 : <0x7fe6a4800048> )}}

//<CFRunLoopObserver 0x7fe6a3611d30 [0x10a417a40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10a59e4c2), context = <CFArray 0x7fe6a3611ae0 [0x10a417a40]>{type = mutable-small, count = 1, values = (
//                                                                                                                                                                                                                                                                                0 : <0x7fe6a4800048>
//                                                                                                                                                                                                                                                                                )}}

/** 查看上面的activities   发现 监听的是下面三个值   0xa0 == 160
 kCFRunLoopBeforeWaitin|kCFRunLoopExit == 160
 */

/**
kCFRunLoopEntry = (1UL << 0),  1
+
kCFRunLoopBeforeWaiting = (1UL << 5), 32
kCFRunLoopExit = (1UL << 7), 128
*/

不太好的demo,请戳这里

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

推荐阅读更多精彩内容