Runloop

来源:互联网 发布:期刊软件 编辑:程序博客网 时间:2024/06/05 19:27
学过操作系统的都知道,一个线程对应一个消息队列,iOS里消息队列被叫做Runloop,但是,runloop比消息队列做的事多得多。
正常情况下,线程执行完任务(消息队列里的任务)就会自动退出,就main函数而言,他顺序执行完代码后就会return,但一个应用在其没有任务时也不会退出,当有事件时他会立马去执行,就像随时待命一样。这就是runloop的作用。简而言之runloop就是让线程一直活着。
程序入口:
 int main(int argc, char *argv[])      {

            @autoreleasepool {

              return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
          }
  }
/*
如果首要类(第三个参数)字符串的值为nil,UIKit就缺省使用UIApplication类;如果应用程序委托类(第四个参数)为nil,UIKit就会将应用程序主nib文件(针对通过Xcode模板创建的应用程序)中的某个对象假定为应用程序的委托对象。如果您将这些参数设置为非nil值,则在应用程序启动时,UIApplicationMain函数会创建一个与传入值相对应的类实例,并将它用于既定的目的。因此,如果您的应用程序使用了UIApplication类的定制子类(这种做法是不推荐的,但确实是可能的),就需要在第三个参数指定该定制类的类名。
- (BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions
iOS程序启动时总会调用此方法,其中第二个参数launchOptions为NSDictionary类型的对象,里面存储有此程序启动的原因。例如:若用户直接启动,lauchOptions内无数据; 若由其他应用程序通过openURL:启动,则lauchOptions==UIApplicationLaunchOptionsURLKey对应的对象为启动URL(NSURL),lauchOptions==UIApplicationLaunchOptionsSourceApplicationKey对应启动的源应用程序的bundle 
ID (NSString)。
*/
如果我把它写成,
int main(int argc, char *argv[])      {
            @autoreleasepool {
               int value=UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
                  printf(value);
              return value;
          }
  }

正常情况下int value=……后面的代码不会执行,这是因为,在执行UIApplicationMain函数时,会建立应用的main runloop,进行事件的处理(首先会调用– application:didFinishLaunchingWithOptions:)。runloop会让这个程序一直处于活着的状态,也就是Main函数会一直停留在UIApplicationMain函数,按理说程序正常退出时会返回,但如今iOS支持后台运行,系统在必要时会强行杀死不用的进程,一般这个函数不等返回进程就终止了
runloop是个对象,管理线程内的事务,他的作用有:
解耦:主调方产生事件—>压入消息队列—>被掉方处事件,主调方和被调方没有牵扯,称为解耦。

在 CoreFoundation 里面关于 RunLoop 有5个类:

  • CFRunLoopRef

  • CFRunLoopModeRef

  • CFRunLoopSourceRef

  • CFRunLoopTimerRef

  • CFRunLoopObserverRef

一、Source(源):我理解的源就是待处理的事件,source分两种

简单地说,触发事件可以是来自其他线程的消息(传递的异步事件),也就是上面说的source1;也可以是自定义的(source0),用selector方法调用的方法,多个用selector调用的方法在消息队列里排队,按照FIFO算法调用,执行完消息队列后,按理说线程会退出,可是因为有runloop,线程不会退出,而是出于休眠,等待其他事件将其唤醒。
二、Timer
还有一种情况,就是定义一个timer,每隔一段时间调用selector执行一个方法,我觉得,timer和runloop会让线程一直在消息队列里循环执行。如果timer在runloop处理某一事件时开始,timer会等到runloop下次开始时运行。即,当消息队列里有其他任务时,timer不会启动,当消息队列空一次,runloop重新启动时timer才会启动,这时消息队列里只有timer反复selector的方法。
PS:timer创建的方法有两种,一种需要手动添加到runloop里,一个不用,但两个其实都在runloop里。
三、Mode(Runloop的亮点)
RunLoop在同一时段只能且必须在一种特定Mode下Run
更换Mode时, 需要暂停当前的Loop,然后重启新的Loop
  • NSDefalutRunLoopMode      默认状态.空闲状态
  • UITrackingRunLoopMode     滑动ScrollView
  • UIInitializationRunLoopMode    私有,App启动时
  • NSRunLoopCommonModes     默认包括上面第一和第二
重启Runloop,意味着跳出当前的while(1)循环,换上新的Mode后再重新进入while(1),但以前的item仍然存在,新的Runloop跑旧的Runloop里的timer和其他源事件。
Runloop sleep时不会退出死循环。
一般情况下是NSDefalutRunLoopMode,timer在这个Mode下执行,当发生Stroller滑动时,UIKit有个Pop RunloopMode/Push RunloopMode操作,将NSDefalutRunLoopMode换成UITrackingRunLoopMode,在TracingRunloopMode下,timer不会执行,做share时用到了轮播图,轮播图不滑动时自己轮转,当发生拖动时,timer停止,所以才不会乱,当拖动停止,Mode换回来后,再继续执行原来的timer。
在NSRunLoopCommonModes 下,timer会一直执行。
四、CFRunLoopObserver
向内部报告RunLoop当前状态
是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。
五、Runloop与autorelease
UIKit通过RunLoopObserver在RunLoop两次Sleep间对AutoreleasePool进行pop和push,将这次Loop中产生的Autorelease对象释放。
六、Runloop与GCD
GDC与Runloop只有在Main Queue中才有关系
dispatch_after方法一段时间后将任务交给Runloop
GCD中的timer与主线程中的Runloop无关
CGD中dispatch到Main Queue的block被分发到Main Runloopz执行,dispatch after同理。
七、Runloop挂起与唤醒
·指定用于唤醒的mach_port端口
·调用mach_msg监听唤醒端口,被唤醒前,系统内核将这个线程挂起,停留在mach_msg_trap状态
·由另一个线程(或另一个进程中的某个线程)向内核发送这个端口的msg后,trap状态被唤醒,Runloop继续开始干活

八、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;

            }

            /// 通知 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);

            /// 收到消息,处理消息。

            handle_msg:

            /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。

            if (msg_is_timer) {

                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())

            } 

            /// 9.2 如果有dispatchmain_queueblock,执行block

            else if (msg_is_dispatch) {

                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

            } 

            /// 9.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);

                }

            }

            /// 执行加入到Loopblock

            __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);

    }

    /// 10. 通知 Observers: RunLoop 即将退出。

    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

}    

runloop进入休眠状态,即这个线程进入陷阱,进入内核态



原创粉丝点击