iOS RunLoop

RunLoop 应用:NSTimer、 PerformSelector、常驻线程
iOS 中有两套API访问 Foundation(NSRunLoop), CoreFoundation CFRunLoopRef

一、RunLoop 引入

  • 在程序的启动入口Main 函数中,UIApplicationMain 函数内部就启动了一个与主线程相关联的RunLoop
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
  • RunLoop 字面意义上:跑圈(那我可不可以看成是死循环呢),那么来验证一下吧
int main(int argc, char * argv[]) {
   @autoreleasepool {
       
       // 查看了UIApplicationMain 的返回值是int类型
//        UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);

       NSLog(@"welcome to Runloop");
       int jjTest = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
       NSLog(@"快到这里来");
       return jjTest;
   }
}
iOS RunLoop_第1张图片
控制的输出是这样的.png
  • 从上可以看出main 函数里面启动了个RunLoop,而且是保持运行的状态

二、RunLoop里面到底有啥呢?

  • 上面说过RunLoop 和 主线程的关系,那线程和RunLoop 是什么关系呢?
    • 每条线程都有一个唯一的与之对应的RunLoop对象
    • Foundation:
      • 1.获取当前线程RunLoop:[NSRunLoop currentRunLoop]
      • 2.获取主线程RunLoop:[NSRunLoop mainRunLoop]
    • CoreFoundation:
      • 1.获取当前线程RunLoop:CFRunLoopGetCurrent()
      • 2.获取主线程RunLoop:CFRunLoopGetMain()
  • 了解一个对象的最好方式就是打印出来看看:
iOS RunLoop_第2张图片
RunLoop 对象
  • 我的天该怎么看:JJ看到的貌似是一些CFRunLoopObserver CFRunLoopSource, 还有timers 等对象,打印出来发现还是有些复杂了,what should i do~

  • 那就只能看看官方文档有关RunLoop的描述了,如果英文不好的就继续往下

  • 根据文档的描述:RunLoop相关联的类有五个(再看看打印出来的内容)

    • CFRunLoopRef
    • CFRunLoopSourceRef
    • CFRunLoopObserverRef
    • CFRunLoopTimerRef
    • CFRunLoopModeRef
  • RunLoop 中对象

iOS RunLoop_第3张图片
RunLoop 对象图示

三、RunLoop相关类的理解

  • 1.从上图可以看出

    • 一个RunLoop 可以有多个Mode(模式),每个模式又包含若干个 Source/Observer/Timer
    • RunLoop 一次只能运行一种Mode,切换Mode需要退出当前Mode
  • CFRunLoopModeRef 一共有五种模式

    • kCFRunLoopDefaultMode 默认模式,通常主线程在这个模式下运行
    • kCFRunLoopCommonModes 占位符(表示)
    • UITrackingRunLoopMode 界面跟踪Mode,用于追踪Scrollview的触摸滑动
    • UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不在使用。
    • GSEventReceiveRunLoop:内部Mode,接收系事件。
  • CFRunLoopSourceRef(函数调用栈)

    • Source0:非基于Port(处理事件)
    • Source1:基于Port (接收分发系统事件)
    • 触摸事件:
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"-------");
}
  • 触摸事件的函数调用栈调用顺序入下图:
iOS RunLoop_第4张图片
不难看出Source0、1的作用
  • CFRunLoopTimerRef:基于时间的触发器(和NSTimer差不多)

  • CFRunLoopObserverRef:监听RunLoop状态的观察者

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // 1
    kCFRunLoopBeforeTimers = (1UL << 1), // 2
    kCFRunLoopBeforeSources = (1UL << 2), // 4
    kCFRunLoopBeforeWaiting = (1UL << 5), // 32
    kCFRunLoopAfterWaiting = (1UL << 6), // 64
    kCFRunLoopExit = (1UL << 7), // 128
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • RunLoop 处理逻辑
iOS RunLoop_第5张图片
转载

四、RunLoop 使用

  • 监听RunLoop:监听RunLoop的状态 用途:在RunLoop唤醒前做些操作

    • 创建Observer:CFRunLoopObserverCreateWithHandler
    • 添加观察者 :CFRunLoopAddObserver
    • 释放Observer:CFRelease
      • 本例ARC状态,ARC只能对OC对象进行内存管理,C中的对象需要自行管理
      // 创建observer
      CFRunLoopObserverRef obser = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
          NSLog(@"-----Runloop状态------%zd", activity);
      });
      
      // 添加观察者:监听RunLoop的状态
      CFRunLoopAddObserver(CFRunLoopGetMain(), obser, kCFRunLoopDefaultMode);
      
      // 释放Observer
      CFRelease(obser);
    
  • 在RunLoop中创建定时器:

    • 定时器NSTimer的创建方式一:timerWithTimeInterval:
      • 创建timer
      • 将timer 添加到RunLoop中
      NSTimer *timer =[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(JYTest) userInfo:nil repeats:YES];
      // 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
      //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
      // 定时器只运行在UITrackingRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
      //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
      // 标记为common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode
      [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    

}

- 定时器NSTimer的创建方式二:scheduledTimerWithTimeInterval
  - 创建timer:此方法是自动将timer 添加到线程中
  - 注意NSTimer 在`子线程创建timer` 需要手动开启当前线程的RunLoop

```objc
- (void)scheduledTimer {
  // 调用了
  NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(JYTest) userInfo:nil repeats:YES];
  
  // 改变Timer的模式调用了scheduledTimer返回的定时器,已经自动被添加到当前runLoop中,而且是NSDefaultRunLoopMode
  [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void) JYTest {
  
  NSLog(@"----0 0 ");
}
  • 需求一 开辟一个不死线程来处理自定义的一些事件(常驻线程)

    • 如何让一个线程处理一个任务之后不死呢?
    • 答:主线程是通过创建RunLoop 使得主线程不死,一直处理事件。所以创建一个子线程,并创建子线程与之对应的RunLoop
  • 创建一个不死线程:

    • 创建一个子线程
    • 为了监听线程是否被销毁,本例自定了一个JJThread,并重写dealloc 方法
// 将线程定义成属性
@property (nonatomic, strong) JJThread *thread;


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.thread = [[JJThread alloc] initWithTarget:self selector:@selector(jjTest) object:nil];
    [self.thread start];
    
}
  • 创建RunLoop,方式一
    • 1.前面说过RunLoop中必须有source,Observer,Timer 其中的任一一个,若为空则执行一次RunLoop,就退出Runloop
    • 2.stackoverflow解释上面说的这个问题
- (void)jjTest {
    
    // NSRunloop 中必须要有timer, source, observer 一个或者多个,不然会导致
    NSLog(@"-----jjTest-------%@", [NSThread currentThread]);

    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"-----jjTest-------%@", [NSThread currentThread]);

    
    // http://stackoverflow.com/questions/31199802/what-does-nsrunloop-currentrunloop-runmodensdefaultrunloopmode-beforedate
}
  • 创建RunLoop,方式二

    • 上面说RunLoop 必须要有中必须要有timer, source, observer 一个或者多个,这里为什么没有源还行呢。
    • 答:因为RunLoop一直在执行退出,进入的状态,这个中间若是接收到performSelector 发放不就有source,可以尝试一下
    • 注意点:while 条件中会定义一个BOOL 的属性,做标记管理RunLoop 是否退出死循环。
- (void)jjTest {
    
    NSLog(@"----------run----%@", [NSThread currentThread]);
    
    while (1) {
        [[NSRunLoop currentRunLoop] run];
    }
}


- (void)jjRun {
    
    NSLog(@"------jjRun------%@", [NSThread currentThread]);
}

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

  • PS:有需要源码github/JeversonJee下载

你可能感兴趣的:(iOS RunLoop)