runloop的线程保活、销毁和通信

2021-03-22  本文已影响0人  HughKaun

这是AF2.x经典的代码:

+ (NSThread *)networkRequestThread {
        static NSThread *_networkRequestThread = nil;
        static dispatch_once_t oncePredicate;
        dispatch_once(&oncePredicate, ^{
            _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
            [_networkRequestThread start];
        });
        return _networkRequestThread;
    }

    + (void)networkRequestThreadEntryPoint:(id)__unused object {
        @autoreleasepool {
            [[NSThread currentThread] setName:@"AFNetworking"];
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
            [runLoop run];
        }
    }

这里创建了一个线程,取名为AFNetworking,因为添加了一个runloop,所以这个线程不会被销毁,直到runloop停止。

[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

[runLoop run];

[NSRunLoop currentRunLoop]removePort:<#(nonnull NSPort *)#> forMode:<#(nonnull NSRunLoopMode)#>

只有从runloop中移除我们之前添加的端口,这样runloop没有任何事件,所以直接退出。

再次回到 AF2.x 的这行源码上,因为他用的是run,而且并没有记录下自己添加的NSMachPort,所以显然,他就没打算退出这个runloop,这是一个常驻线程。事实上,看过AF2.x源码的同学会知道,这个thread需要常驻的原因,在此就不在赘述了。

我们看看AF3.x是怎么用runloop的:

需要开启的时候:


CFRunLoopRun();

终止的时候:


CFRunLoopStop(CFRunLoopGetCurrent());

再看看RAC中的runloop:

do {
[NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} while (!done);

以上我们都大致分析了一下,后面我们再来讲讲为什么。
首先我们先讲讲runloop的概念:

我们先来谈谈Runloop Mode

iOS下Runloop的主要运行模式mode有:
1)NSDefaultRunLoopMode:默认的运行模式,除了NSConnection对象的事件。
2)NSRunLoopCommonModes:是一组常用的模式集合,将一个input source关联到这个模式集合上,等于将input source关联到这个模式集合中的所有模式上。在iOS系统中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode。

将UITrackingRunLoopMode或者其他模式添加到这个NSRunLoopCommonModes模式中,然后只需要将Timer关联到NSRunLoopCommonModes,即可以实现runloop运行在这个模式集合中任何一个模式时,这个timer都可以触发。

  1. UITrackingRunLoopMode: 用于跟踪触摸事件触发的模式(例如UIScrollView上下滚动),主线程当触摸事件触发时会设置为这个模式,可以用来在控件事件触发过程中设置Timer。
  2. GSEventReceiveRunLoopMode: 用于接受系统事件,属于内部的Run Loop模式。
  3. 自定义Mode:可以设置自定义的运行模式Mode,你也可以用CFRunLoopAddCommonMode添加到NSRunLoopCommonModes中。
总结一下:

Run Loop运行时只能以一种固定的模式运行,如果我们需要它切换模式,只有停掉它,再重新开启它。运行时它只会监控这个模式下添加的Timer Source和Input Source,如果这个模式下没有相应的事件源,Run Loop的运行也会立刻返回的。注意Run Loop不能在运行在NSRunLoopCommonModes模式,因为NSRunLoopCommonModes其实是个模式集合,而不是一个具体的模式,我可以在添加事件源的时候使用NSRunLoopCommonModes,只要Run Loop运行在NSRunLoopCommonModes中任何一个模式,这个事件源都可以被触发。

Run Loop运行接口

NSRunLoop的运行接口:


//运行 NSRunLoop,运行模式为默认的NSDefaultRunLoopMode模式,没有超时限制
- (void)run;
//运行 NSRunLoop: 参数为运时间期限,运行模式为默认的NSDefaultRunLoopMode模式
- (void)runUntilDate:(NSDate *)limitDate;
//运行 NSRunLoop: 参数为运行模式、时间期限,返回值为YES表示是处理事件后返回的,NO表示是超时或者停止运行导致返回的
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;

首先,第一个

*   (void)run; 无条件运行

第二个

*   (void)runUntilDate:(NSDate *)limitDate; 有一个超时时间限制


while (!Done)
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
NSLog(@"exiting runloop.........:");
}

第三个

//有一个超时时间限制,而且设置运行模式

*   (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;


*   (void)testDemo1
    {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"线程开始");
    //获取到当前线程
    self.thread = [NSThread currentThread];

    ```
          NSRunLoop *runloop = [NSRunLoop currentRunLoop];
          //添加一个Port,同理为了防止runloop没事干直接退出
          [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

          //运行一个runloop,[NSDate distantFuture]:很久很久以后才让它失效
          [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

          NSLog(@"线程结束");

      });

      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          //在我们开启的异步线程调用方法
          [self performSelector:@selector(recieveMsg) onThread:self.thread withObject:nil waitUntilDone:NO];
      });

    ```

    }

    *   (void)recieveMsg
        {
        NSLog(@"收到消息了,在这个线程:%@",[NSThread currentThread]);
        }

输出结果如下:


2016-11-22 14:04:15.250 TestRunloop3[70591:1742754] 线程开始
2016-11-22 14:04:17.250 TestRunloop3[70591:1742754] 收到消息了,在这个线程:<NSThread: 0x600000263c80>{number = 3, name = (null)}
2016-11-22 14:04:17.250 TestRunloop3[70591:1742754] 线程结束


/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {

/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;

/// 1\. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {

    Boolean sourceHandledThisLoop = NO;
    int retVal = 0;
    do {

        /// 2\. 通知 Observers: RunLoop 即将触发 Timer 回调。
        __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
        /// 3\. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
        __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
        /// 执行被加入的block
        __CFRunLoopDoBlocks(runloop, currentMode);

        /// 4\. RunLoop 触发 Source0 (非port) 回调。
        sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
        /// 执行被加入的block
        __CFRunLoopDoBlocks(runloop, currentMode);

        /// 5\. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
        if (__Source0DidDispatchPortLastTime) {
            Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
            if (hasMsg) goto handle_msg;
        }

        /// 6.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
        if (!sourceHandledThisLoop) {
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
        }

        /// 7\. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
        /// ? 一个基于 port 的Source 的事件。
        /// ? 一个 Timer 到时间了
        /// ? RunLoop 自身的超时时间到了
        /// ? 被其他什么调用者手动唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
            mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
        }

        /// 8\. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
        __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

        /// 9.收到消息,处理消息。
    handle_msg:

        /// 10.1 如果一个 Timer 到时间了,触发这个Timer的回调。
        if (msg_is_timer) {
            __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
        }

        /// 10.2 如果有dispatch到main_queue的block,执行block。
        else if (msg_is_dispatch) {
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        }

        /// 10.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
        else {
            CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
            sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
            if (sourceHandledThisLoop) {
                mach_msg(reply, MACH_SEND_MSG, reply);
            }
        }

        /// 执行加入到Loop的block
        __CFRunLoopDoBlocks(runloop, currentMode);

        if (sourceHandledThisLoop && stopAfterHandle) {
            /// 进入loop时参数说处理完事件就返回。
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout) {
            /// 超出传入参数标记的超时时间了
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(runloop)) {
            /// 被外部调用者强制停止了
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
            /// source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished;
        }

        /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
    } while (retVal == 0);
}

/// 11\. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

}


代码一长串,但是标了注释,应该大致能看明白,大概讲一下:


__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort)
{
// thread wait for receive msg
mach_msg(msg, MACH_RCV_MSG, port);
}

这一行类似sync这样的一个同步机制(其实不是,举个例子。。),把程序阻塞在这一行,直到有消息返回值,才继续往下进行。这一阻塞操作是系统内核来挂起的,阻塞了当前的线程,当有消息返回时,因为当前线程是被阻塞的,系统内核会再开辟一个新的线程去返回这个消息。然后程序继续往下进行。

回到上述的例子这种模式下的runloop:


*   (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;

我们让线程执行了一个事件,结果执行完,runloop就退出了,原因原来是这样:


if (sourceHandledThisLoop && stopAfterHandle)
{
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
}


/// 10.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}

所以在这里我们触发了事件之后,runloop被退出了,这时候我们也明白了为什么timer并不会导致runloop的退出。

CFRunLoopRef的运行接口:


//运行 CFRunLoopRef
void CFRunLoopRun();
//运行 CFRunLoopRef: 参数为运行模式、时间和是否在处理Input Source后退出标志,返回值是exit原因
SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);
//停止运行 CFRunLoopRef
void CFRunLoopStop( CFRunLoopRef rl );
//唤醒CFRunLoopRef
void CFRunLoopWakeUp ( CFRunLoopRef rl );

第一个

void CFRunLoopRun();


*   (void)runUntilDate:(NSDate *)limitDate; 有一个超时时间限制

这方式运行的runloop也能用CFRunLoopStop
停止掉的原因它是完全基于下面这种方式封装的:


SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);

可以看到参数几乎一模一样,前者默认returnAfterSourceHandled参数为YES,当触发一个非timer事件后,runloop就终止了。

第二个

SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);


enum {
kCFRunLoopRunFinished = 1, //Run Loop结束,没有Timer或者其他Input Source
kCFRunLoopRunStopped = 2, //Run Loop被停止,使用CFRunLoopStop停止Run Loop
kCFRunLoopRunTimedOut = 3, //Run Loop超时
kCFRunLoopRunHandledSource = 4 ////Run Loop处理完事件,注意Timer事件的触发是不会让Run Loop退出返回的,即使CFRunLoopRunInMode的第三个参数是YES也不行
};

看到这,我们发现我们忽略了NSRunloop第三种开启方式的返回值。
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
它其实就是基于CFRunLoopRunInMode封装的,它的返回值为一个Bool值,如果是PerfromSelector事件或者其他Input Source事件触发处理后,Run Loop会退出返回YES,其他返回NO。


*   (void)testDemo2
    {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

    ```
          NSLog(@"starting thread.......");
          NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(doTimerTask1:) userInfo:remotePort repeats:YES];
          [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

          //最后一个参数,是否处理完事件返回,结束runLoop
          SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 100, YES);
          /*
           kCFRunLoopRunFinished = 1, //Run Loop结束,没有Timer或者其他Input Source
           kCFRunLoopRunStopped = 2, //Run Loop被停止,使用CFRunLoopStop停止Run Loop
           kCFRunLoopRunTimedOut = 3, //Run Loop超时
           kCFRunLoopRunHandledSource = 4 ////Run Loop处理完事件,注意Timer事件的触发是不会让Run Loop退出返回的,即使CFRunLoopRunInMode的第三个参数是YES也不行
           */
          switch (result) {
              case kCFRunLoopRunFinished:
                  NSLog(@"kCFRunLoopRunFinished");

                  break;
              case kCFRunLoopRunStopped:
                  NSLog(@"kCFRunLoopRunStopped");

              case kCFRunLoopRunTimedOut:
                  NSLog(@"kCFRunLoopRunTimedOut");

              case kCFRunLoopRunHandledSource:
                  NSLog(@"kCFRunLoopRunHandledSource");
              default:
                  break;
          }
          NSLog(@"end thread.......");
      });

    ```

    }

    *   (void)doTimerTask1:(NSTimer *)timer
        {
        count++;
        if (count == 2) {
        [timer invalidate];
        }
        NSLog(@"do timer task count:%d",count);
        }

输出结果如下:


2016-11-23 09:19:28.342 TestRunloop3[88598:1971412] starting thread.......
2016-11-23 09:19:29.347 TestRunloop3[88598:1971412] do timer task count:1
2016-11-23 09:19:30.345 TestRunloop3[88598:1971412] do timer task count:2
2016-11-23 09:19:30.348 TestRunloop3[88598:1971412] kCFRunLoopRunFinished
2016-11-23 09:19:30.348 TestRunloop3[88598:1971412] end thread.......

总结一下:


[NSRunLoop currentRunLoop]runMode:<#(nonnull NSRunLoopMode)#> beforeDate:<#(nonnull NSDate *)#>


while (!cancel) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, YES);
}

当然,这里只是提供一个思路,具体有需求,可以根据实际需要。

基于runloop的线程通信

首先明确一个概念,线程间的通信(不仅限于通信,几乎所有iOS事件都是如此),实际上是各种输入源,触发runloop去处理对应的事件,所以我们先来讲讲输入源:
输入源异步的发送消息给你的线程。事件来源取决于输入源的种类:

至于run loop,它不关心输入源的是基于端口的输入源还是自定义的输入源。系统会实现两种输入源供你使用。两类输入源的区别在于:

当你创建输入源,你需要将其分配给run loop中的一个或多个模式。模式只会在特定事件影响监听的源。大多数情况下,run loop运行在默认模式下,但是你也可以使其运行在自定义模式。若某一源在当前模式下不被监听,那么任何其生成的消息只在run loop运行在其关联的模式下才会被传递。

1)基于端口的输入源:
在runloop中,被定义名为souce1。Cocoa和Core Foundation内置支持使用端口相关的对象和函数来创建的基于端口的源。例如,在Cocoa里面你从来不需要直接创建输入源。你只要简单的创建端口对象,并使用NSPort的方法把该端口添加到run loop。端口对象会自己处理创建和配置输入源。

在Core Foundation,你必须人工创建端口和它的run loop源.在两种情况下,你都可以使用端口相关的函数(CFMachPortRef,CFMessagePortRef,CFSocketRef)来创建合适的对象。

这里用Cocoa里的举个例子,Cocoa里用来线程间传值的是NSMachPort,它的父类是NSPort。
首先我们看下面:


NSPort *port1 = [[NSPort alloc]init];
NSPort *port2 = [[NSMachPort alloc]init];
NSPort *port3 = [NSPort port];
NSPort *port4 = [NSMachPort port];

我们打断点可以看到如下:

port图

继续看我们写的一个利用NSMachPort来线程通信的实例:


*   (void)testDemo3
    {
    //声明两个端口 随便怎么写创建方法,返回的总是一个NSMachPort实例
    NSMachPort *mainPort = [[NSMachPort alloc]init];
    NSPort *threadPort = [NSMachPort port];
    //设置线程的端口的代理回调为自己
    threadPort.delegate = self;

    ```
      //给主线程runloop加一个端口
      [[NSRunLoop currentRunLoop]addPort:mainPort forMode:NSDefaultRunLoopMode];

      dispatch_async(dispatch_get_global_queue(0, 0), ^{

          //添加一个Port
          [[NSRunLoop currentRunLoop]addPort:threadPort forMode:NSDefaultRunLoopMode];
          [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

      });

      NSString *s1 = @"hello";

      NSData *data = [s1 dataUsingEncoding:NSUTF8StringEncoding];

      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          NSMutableArray *array = [NSMutableArray arrayWithArray:@[mainPort,data]];
          //过2秒向threadPort发送一条消息,第一个参数:发送时间。msgid 消息标识。
          //components,发送消息附带参数。reserved:为头部预留的字节数(从官方文档上看到的,猜测可能是类似请求头的东西...)
          [threadPort sendBeforeDate:[NSDate date] msgid:1000 components:array from:mainPort reserved:0];
      });

    ```

    }

    //这个NSMachPort收到消息的回调,注意这个参数,可以先给一个id。如果用文档里的NSPortMessage会发现无法取值

    *   (void)handlePortMessage:(id)message
        {
        NSLog(@"收到消息了,线程为:%@",[NSThread currentThread]);
        //只能用KVC的方式取值
        NSArray *array = [message valueForKeyPath:@"components"];

        NSData *data = array[1];
        NSString *s1 = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@",s1);

        // NSMachPort *localPort = [message valueForKeyPath:@"localPort"];
        // NSMachPort *remotePort = [message valueForKeyPath:@"remotePort"];
        }

打印如下:


2016-11-23 16:50:20.604 TestRunloop3[1322:120162] 收到消息了,线程为:<NSThread: 0x60800026d700>{number = 3, name = (null)}
2016-11-23 16:50:26.551 TestRunloop3[1322:120162] hello

Cocoa 执行 Selector 的源:


[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]

[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]

自定义输入源:

下图中,程序的主线程维护了一个输入源的引用,输入源所需的自定义命令缓冲区和输入源所在的 runloop。当主线程有任务需要分发给工作线程时候,主线程会给命令缓冲区发送命令和必须的信息来通知工作线程开始执行任务。(因为主线程和输入源所在工作线程都可以访问命令缓冲区,因此这些访问必须是同步的)一旦命令传送出去,主线程会通知输入源并且唤醒工作线程的 runloop。而一收到唤醒命令,runloop 会调用输入源的处理程序,由它来执行命令缓冲区中响应的命令。

还是一样,我们来写一个实例来讲讲自定义的输入源(注:自定义输入源,只有用CF来实现):


CFRunLoopRef _runLoopRef;
CFRunLoopSourceRef _source;
CFRunLoopSourceContext _source_context;

首先我们声明3个成员变量,这是我们自定义输入源所需要的3个参数。具体我们举完例子之后再说。


*   (void)testDemo4
    {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

    ```
          NSLog(@"starting thread.......");

          _runLoopRef = CFRunLoopGetCurrent();
          //初始化_source_context。
          bzero(&_source_context, sizeof(_source_context));
          //这里创建了一个基于事件的源,绑定了一个函数
          _source_context.perform = fire;
          //参数
          _source_context.info = "hello";
          //创建一个source
          _source = CFRunLoopSourceCreate(NULL, 0, &_source_context);
          //将source添加到当前RunLoop中去
          CFRunLoopAddSource(_runLoopRef, _source, kCFRunLoopDefaultMode);

          //开启runloop 第三个参数设置为YES,执行完一次事件后返回
          CFRunLoopRunInMode(kCFRunLoopDefaultMode, 9999999, YES);

          NSLog(@"end thread.......");
      });

      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

          if (CFRunLoopIsWaiting(_runLoopRef)) {
              NSLog(@"RunLoop 正在等待事件输入");
              //添加输入事件
              CFRunLoopSourceSignal(_source);
              //唤醒线程,线程唤醒后发现由事件需要处理,于是立即处理事件
              CFRunLoopWakeUp(_runLoopRef);
          }else {
              NSLog(@"RunLoop 正在处理事件");
              //添加输入事件,当前正在处理一个事件,当前事件处理完成后,立即处理当前新输入的事件
              CFRunLoopSourceSignal(_source);
          }
      });

    ```

    }

    //此输入源需要处理的后台事件
    static void fire(void* info){
    NSLog(@"我现在正在处理后台任务");
    printf("%s",info);
    }

输出结果如下:


2016-11-24 10:42:24.045 TestRunloop3[4683:238183] starting thread.......
2016-11-24 10:42:26.045 TestRunloop3[4683:238082] RunLoop 正在等待事件输入
2016-11-24 10:42:31.663 TestRunloop3[4683:238183] 我现在正在处理后台任务
hello
2016-11-24 10:42:31.663 TestRunloop3[4683:238183] end thread.......


typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;

version
Version number of the structure. Must be 0.
info
An arbitrary pointer to program-defined data, which can be associated with the CFRunLoopSource at creation time. This pointer is passed to all the callbacks defined in the context.
retain
A retain callback for your program-defined info pointer. Can be NULL.
release
A release callback for your program-defined info pointer. Can be NULL.
copyDescription
A copy description callback for your program-defined info pointer. Can be NULL.
equal
An equality test callback for your program-defined info pointer. Can be NULL.
hash
A hash calculation callback for your program-defined info pointer. Can be NULL.
schedule
A scheduling callback for the run loop source. This callback is called when the source is added to a run loop mode. Can be NULL.
cancel
A cancel callback for the run loop source. This callback is called when the source is removed from a run loop mode. Can be NULL.
perform
A perform callback for the run loop source. This callback is called when the source has fired.

c)CFRunLoopSourceRef _source;
这个是自定义输入源中最重要的一个参数。它用来连接runloop与CFRunLoopSourceContext中的一些配置项,注意我们自定义的输入源,必须由我们手动来触发。需要先CFRunLoopSourceSignal(_source);
在看当前runloop是否在休眠中,来看是否需要调用CFRunLoopWakeUp(_runLoopRef);
(一般都是要调用的)。

  1. 定时源:

[NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>
[NSTimer timerWithTimeInterval:<#(NSTimeInterval)#> target:<#(nonnull id)#> selector:<#(nonnull SEL)#> userInfo:<#(nullable id)#> repeats:<#(BOOL)#>]

当然还有Block ,invocation的形式,就不做赘述了。第一种timer默认是把加到了NSDefaultRunLoopMode模式下。第二种timer没有默认值,我们使用的使用必须调用[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
去给它指定一个mode。

Core Foundation 创建定时器


CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,&myCFTimerCallback, &context);

最后用一张runloop运行时的流程图来梳理一下我们这些源触发的顺序

RunLoop

Run Loop的Observer

上图提到了Observer,顺带简单讲讲吧:Core Foundation层的接口可以定义一个Run Loop的观察者在--- Run Loop进入以下某个状态时得到通知:


// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
});
// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 释放Observer
CFRelease(observer);


typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 1 // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 2 // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 4 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 32 // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 64
// 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 128 // 即将退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 可以监听以上所有状态
};

写在结尾

本文转载于大神 涂耀辉基于runloop的线程保活、销毁与通信


</article>

13人点赞

[日记本](/nb/3947486)

作者:有梦想的老伯伯
链接:https://www.jianshu.com/p/c2c658ef4553
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
上一篇 下一篇

猜你喜欢

热点阅读