iOS开发-RunLoop总结

序言

《iOS之应用程序启动过程及原理总结》一篇中介绍了iOS应用的启动原理。我们知道当应用启动后,系统会自动创建一个线程来执行任务,该线程被称为主线程或者UI线程。其实在主线程创建的时候,系统还会为主线程创建并启动一种机制(其实就是一个对象,该对象和应用的生命周期有关),叫做RunLoop,被称为运行循环机制。本文主要将介绍iOS应用中的RunLoop机制。

RunLoop简介

RunLoop概念

提到RunLoop,我们一般都会提到线程,这是为什么呢?先来看下 官方对 RunLoop 的定义: RunLoop是系统中和线程相关的基础架构的组成部分(
和线程相关
),一个RunLoop是一个事件处理环,系统利用这个事件处理环来安排事务,协调输入的各种事件。当一个iOS应用启动后,如果我们不做任何交互操作,那么该应用默认不会做任何响应,一旦我们触摸了屏幕,或者点击了某个按钮,程序就会立即做出相应的响应,给我们的操作一个反馈。就好像这个应用处于一个时刻准备着的状态,有事要做的时候,它就会马上做。没有事要做的时候,它就等待一样。应用的这一点全是靠RunLoop机制来实现的。RunLoop从字面理解可以把它看做一个运行循环,而且它会事件相关联,所有这里我们也暂时把它当做一个事件运行循环。

在系统中,所有的事件响应都由这个事件运行循环来派发和调度的。当系统没有接收到事件,运行循环就会处于休眠状态,来节约CPU的资源。当系统接收到事件,运行循环就会被唤醒,来分发和处理事件(这里涉及到事件的传递和响应者链条)。所以有了这个运行循环的存在,应用就不需要一直处于活跃状态,一切都由RunLoop来监管,这样大大的节约了系统资源。

苹果官方为我们提供了两个这样的运行循环对象:NSRunLoop 和 CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。下面结合这两个对象来介绍运行循环

RunLoop浅析

当应用调用main函数中的UIApplicationMain()方法启动应用的时候,系统一方面加载视图文件(storyboard或者是xib文件)和info.plist文件,创建必要的视图对象;另一方面创建主线程,并在主线程中创建一个RunLoop对象(称为主运行循环,MainRunLoop),并把该对象存放在一个全局的静态字典中,以线程对象作为key。官方源码:

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;

/// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);

    if (!loopsDic) {
        // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
    }

    /// 直接从 Dictionary 里获取。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));

    if (!loop) {
        /// 取不到时,创建一个
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }

    OSSpinLockUnLock(&loopsLock);
    return loop;
}

CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}

CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

由此可以看出,一个线程对象就对应一个RunLoop对象。创建后,默认启动该MainRunLoop对象。其内部是一个do-while循环。由此保证了应用程序的持续运行。其官方源码如下:

/// 用DefaultMode启动
void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}

/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

/// 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 如果有dispatch到main_queue的block,执行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);
                }
            }

            /// 执行加入到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);
    }

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

既然知道RunLoop机制是用来分发和处理事件的,那么我们来看看RunLoop能处理的事件类型:输入源(input source)和定时源(timer source)

  1. 输入源(input source):传递异步事件,消息通常来自于其他线程,处理其他线程的消息,如下载操作执行完毕,要回到主线程中更新UI,这些异步事件就是由RunLoop来监听和管理的。
  2. 定时源(timer source):传递同步事件,发生在特定时间,或者时间间隔,如定时检查UI界面上有没有刷新事件、点击事件等等,也就是处理本线程上的事件。

除了处理输入源,RunLoop也会生成关于RunLoop行为的notification。注册的RunLoop 观察者可以收到这些notification,并做相应的处理。可以使用Core Foundation在你的线程注册RunLoop观察者。相应的通知如下:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
};

输入源、定时源、观察者解析:

  • 输入源对应Core Foundation框架中的CFRunLoopSourceRef类:CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。Source1
    包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
  • 定时源对应Core Foundation框架中的CFRunLoopTimerRef类:CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
  • 观察者对应Core Foundation框架中的CFRunLoopTimerRef类:CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

RunLoop只有在一定的模式下才会运行,就是说要想启动RunLoop就要指定其运行的模式。系统已经提供了RunLoop运行的5种模式,如下:

  1. NSDefaultRunLoopMode:是RunLoop默认的模式,表示程序空闲。如果我们用NSTimer来每秒打印输出的时候,一旦有手势操作(如滑动、滚动等操作),那么NSTimer就会停止执行。
  2. UITrackingRunLoopMode:跟踪模式(是UIScrollView专用模式)。上面提到的NSTimer例子,一旦有滚动等操作,RunLoop就会自动从NSDefaultRunLoopMode模式切换到UITrackingRunLoopMode模式,目的就是为了保证滚动的流畅性,给用户提供流畅的体验。
  3. NSRunLoopCommonMode:这种模式会包含以上两种模式。我们执行滑动操作的同时,NSTimer会始终调用方法来执行打印,但是,一旦所调用的方法中有耗时的操作时,效果就会卡顿。所有在实际开发中,不建议使用该模式。但是,有的需求就是又要有耗时操作又要保证流畅。解决办法就是将耗时操作放到子线程中(子线程RunLoop需要手动启动:CFRunLoopRun(),CFRunLoopStop(CFRunLoopCurrent())停止循环)。
  4. UIInitializationRunLoopMode:在程序刚启动的时进入该模式,启动完就不在使用。
  5. GSEventReceiveRunLoopMode:接受系统事件的内部mode,通常用不到。

RunLoop构成

从上面可以看出RunLoop的结构。不过这里有一个重要的概念就是上面提到的模式:mode。RunLoop模式是所有要监视的输入源和定时源以及要通知的注册观察者的集合。每次运行RunLoop都会指定其运行在哪个模式下。以后,只有相应的源会被监视并允许接收他们传递的消息。(类似的,只有相应的观察者会收到通知)。其他模式关联的源只有在RunLoop运行在其模式下才会运行,否则处于暂停状态。

通常代码中通过指定名字来确定模式。Cocoa和Core Foundation定义了默认的以及一系列常用的模式,都是用字符串来标识。当然你也可以指定字符串来自定义模式。虽然你可以给模式指定任何名字,但是所有的模式内容都是相同的。你必须添加输入源,定时器或者RunLoop观察者到你定义的模式中。

所有由RunLoop的构成就清楚了,RunLoop必须指定一个mode,在mode中必须添加输入源,定时器或者RunLoop观察者。如果在启动RunLoop的时候,没有指定其模式,该RunLoop是没有任何效果的。如果没有添加任何源事件或Timer事件,线程会一直在无限循环的空转中,会一直占用CPU时间片,没有实现资源的合理分配。如果没有while循环驱动且没有添加任何源事件或Timer事件的线程,线程会直接完成,被系统回收。

RunLoop功能

分发和处理事件

在应用程序运行的时候,系统创建一个主线程,并创建一个主运行循环来管理该主线程。那运行循环是如何对事件进行分发和处理的呢?

苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收http://iphonedevwiki.net/index.php/IOHIDFamily
。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。

_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

更多事件处理可以阅读这篇文章

AutoreleasePool

在学习OC的内存管理的时候就一直接触AutoreleasePool这个概念,对它也是一知半解的。学习这篇文字后,我就会知道AutoreleasePool是在什么时候创建的,又是在什么时候被销毁的了。这里我们再简单的回顾一下AutoreleasePool的作用。

AutoreleasePool被称为自动释放池,在释放池中的调用了autorelease方法的对象都会被压在该池的顶部(以栈的形式管理对象)。当自动释放池被销毁的时候,在该池中的对象会自动调用release方法来释放资源,销毁对象。以此来达到自动管理内存的目的。

其实我们在开发中很少主动去创建AutoreleasePool对象,这是为什么呢?不是说用它来自动管理内存吗?其实系统在运行的时候就帮我们创建了AutoreleasePool对象,只是我们不知道而已。那么它是在什么时候被创建的呢?来看看官方源码:

CFRunLoop {
    current mode = kCFRunLoopDefaultMode
    common modes = {
        UITrackingRunLoopMode
        kCFRunLoopDefaultMode
    }

    common mode items = {

        // source0 (manual)
        CFRunLoopSource {order =-1, {
            callout = _UIApplicationHandleEventQueue}}
        CFRunLoopSource {order =-1, {
            callout = PurpleEventSignalCallback }}
        CFRunLoopSource {order = 0, {
            callout = FBSSerialQueueRunLoopSourceHandler}}

        // source1 (mach port)
        CFRunLoopSource {order = 0,  {port = 17923}}
        CFRunLoopSource {order = 0,  {port = 12039}}
        CFRunLoopSource {order = 0,  {port = 16647}}
        CFRunLoopSource {order =-1, {
            callout = PurpleEventCallback}}
        CFRunLoopSource {order = 0, {port = 2407,
            callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
        CFRunLoopSource {order = 0, {port = 1c03,
            callout = __IOHIDEventSystemClientAvailabilityCallback}}
        CFRunLoopSource {order = 0, {port = 1b03,
            callout = __IOHIDEventSystemClientQueueCallback}}
        CFRunLoopSource {order = 1, {port = 1903,
            callout = __IOMIGMachPortPortCallback}}

        // Ovserver
        CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
            callout = _wrapRunLoopWithAutoreleasePoolHandler}
        CFRunLoopObserver {order = 0, activities = 0x20,          // BeforeWaiting
            callout = _UIGestureRecognizerUpdateObserver}
        CFRunLoopObserver {order = 1999000, activities = 0xa0,    // BeforeWaiting | Exit
            callout = _afterCACommitHandler}
        CFRunLoopObserver {order = 2000000, activities = 0xa0,    // BeforeWaiting | Exit
            callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
        CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
            callout = _wrapRunLoopWithAutoreleasePoolHandler}

        // Timer
        CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0,
            next fire date = 453098071 (-4421.76019 @ 96223387169499),
            callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)}
    },

    modes = {
        CFRunLoopMode {
            sources0 =  { /* same as 'common mode items' */ },
            sources1 =  { /* same as 'common mode items' */ },
            observers = { /* same as 'common mode items' */ },
            timers =    { /* same as 'common mode items' */ },
        },

        CFRunLoopMode {
            sources0 =  { /* same as 'common mode items' */ },
            sources1 =  { /* same as 'common mode items' */ },
            observers = { /* same as 'common mode items' */ },
            timers =    { /* same as 'common mode items' */ },
        },

        CFRunLoopMode {
            sources0 = {
                CFRunLoopSource {order = 0, {
                    callout = FBSSerialQueueRunLoopSourceHandler}}
            },
            sources1 = (null),
            observers = {
                CFRunLoopObserver >{activities = 0xa0, order = 2000000,
                    callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
            )},
            timers = (null),
        },

        CFRunLoopMode {
            sources0 = {
                CFRunLoopSource {order = -1, {
                    callout = PurpleEventSignalCallback}}
            },
            sources1 = {
                CFRunLoopSource {order = -1, {
                    callout = PurpleEventCallback}}
            },
            observers = (null),
            timers = (null),
        },

        CFRunLoopMode {
            sources0 = (null),
            sources1 = (null),
            observers = (null),
            timers = (null),
        }
    }
}

App启动后,,系统在主线程RunLoop 里注册两个Observser,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其优先级最高,保证创建释放池发生在其他所有回调之前。第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 优先级最低,保证其释放池子发生在其他所有回调之后。在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

现在我们知道了AutoreleasePool是在RunLoop即将进入RunLoop和准备进入休眠这两种状态的时候被创建和销毁的。所以AutoreleasePool的释放有如下两种情况。一种是Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。还有一种就是手动调用AutoreleasePool的释放方法(drain方法)来销毁AutoreleasePool。

NSTimer

RunLoop还可以用来开启线程的运行循环。经过学习,我们知道主线程的运行循环是系统默认创建和开启的。只有那些子线程需要我们获取RunLoop,并手动开启。下面利用NSTimer的例子来介绍一下,如何开启当前线程的RunLoop。

首先创建一个子线程并在子线程中执行打印任务。代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTag) userInfo:nil repeats:YES];
    });
}

- (void)testTag {
    NSLog(@"testTag");
}

运行看效果:没有任何效果!!!

为什么呢?不知道NSTimer看上去是不是能让你们想到CFRunLoopTimerRef类。其实NSTimer可以看成就是CFRunLoopTimerRef。而CFRunLoopTimerRef是RunLoop机制中的定时源。还记得前面说过,要想运行RunLoop,就必须指定其运行模式,并向模式中添加输入源、定时源或者观察者。既然NSTimer是一种定时源,要想在子线程中运行就必须将该定时源添加到子线程的RunLoop中去才行啊,如下代码:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTag) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    });
}

- (void)testTag {
    NSLog(@"testTag");
}

再运行看看,还是没有效果。这又是为什么呢。我们想想,当前线程是一个子线程,子线程中的RunLoop默认是没有开启的呀。只有主线程才会默认开启。那么我们来手动开启该RunLoop。代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTag) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    });
}

- (void)testTag {
    NSLog(@"testTag");
}

再运行,成功了!!!可以得到,当当前线程为子线程的时候,要想运行RunLoop,必须手动开启RunLoop。

其实与定时源描述相关联的方法,在子线程中被调用都要手动开启RunLoop。这些方法如下:

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;

RunLoop实现

上面例子中,我们通过开启一个线程的RunLoop来执行定时源的回调方法。而且RunLoop也是一个对象。前面也说过,RunLoop内部其实就是一个do-while循环在驱动着,那么我们能不能自己实现一个RunLoop机制呢?下面我们来试试。

首先创建一个子线程,只有线程存在,RunLoop才会存在。还记得开头说的吧.........提示一下全局的静态字典。

- (void)testNewThread1{
    // 获取当前线程RunLoop
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
   // 向当前线程RunLoop添加源,并指定运行模式
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
   // 利用while循环驱动RunLoop运行
    while (!self.isCancelled && !self.isFinished) {
        @autoreleasepool {
            [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
        }
    }
}

这样实现的RunLoop机制会一直占用CPU资源,CPU资源不能合理分配。再看看下面的代码,也是AFNetworking中实现的RunLoop机制的代码:

+ (void)networkRequestThreadEntryPoint:(id)__unused object
{
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];     // 这里主要是监听某个 port,目的是让这个 Thread 不会回收
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

// 创建一个常驻线程,提外界使用
+ (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;
}

可以看出,RunLoop被开启的线程会一直存在。因为在没有事件发生的时候处于休眠状态,有事件发生的时候处于工作状态。以此来节约CPU资源。这样就可以让一个线程成为常驻线程,也就是说该线程一直存在。

RunLoop总结

RunLoop总结:

RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。

RunLoop是一种事件运行循环机制,是保持应用程序持续运行的一种机制。正是由于该机制的存在,应用程序才能在没有事件发生的时候处于休眠状态,有事件发生的时候处于工作状态。以此来节约CPU资源。这也是它的一大特点。

NSRunLoop是Cocoa框架中的类,与之对应的,在Core Foundation中是CFRunLoopRef类。两者的区别是前者不是线程安全的,后者是线程安全的,且两者可以相互转化。

RunLoop和线程的关系:

RunLoop是用来管理线程的,每个线程对应一个RunLoop对象。我们不可以去创建当前线程的RunLoop对象,但是我们可以去获取当前线程的RunLoop。RunLoop就是来监听该线程有无事件发生,如果有就工作,如果没有就休眠。

主线程的RunLoop对象默认开启,其他线程默认不开启。

RunLoop与AutoreleasePool;

RunLoop处理的事件类型;

RunLoop的运行模式mode;

时间: 2024-11-07 17:36:08

iOS开发-RunLoop总结的相关文章

iOS开发RunLoop学习:四:RunLoop的应用和RunLoop的面试题

一:RunLoop的应用 #import "ViewController.h" @interface ViewController () /** 注释 */ @property (nonatomic, strong) NSThread *thread; @end @implementation ViewController /** * 1:用NSThread创建线程的时候,不要忘记调用start方法来开启线程,在一条线程中的任务执行的顺序是同步的,串行执行,并且当线程中的任务执行完毕后

iOS开发RunLoop学习:一:RunLoop简单介绍

一:RunLoop的简单介绍 #import "ViewController.h" @interface ViewController () @end @implementation ViewController /** * 1:Runloop和线程的关系:1:一一对应,主线程的runloop已经默认创建,但是子线程的需要手动创建:创建子线程的runloop: NSRunLoop *run = [NSRunLoop currentRunLoop];currentRunLoop懒加载的,

iOS开发RunLoop学习:三:Runloop相关类(source和Observer)

一:RunLoop相关类: 其中:source0指的是非基于端口por,说白了也就是处理触摸事件,selector事件,source1指的是基于端口的port:是处理系统的一些事件 注意:创建一个RunLoop之后,有默认的运行模式mode,也可以为RunLoop指定运行模式,RunLoop启动必须得有运行模式,而且在运行模式中必须还有timer或是source事件其中之一,否则RunLoop就会退出.启动RunLoop必须调用start方法 二:RunLoop运行处理逻辑 RunLoop通知观

玩转iOS开发 - Runloop 具体解释

Runloop 具体解释 原文地址:https://www.cnblogs.com/zhchoutai/p/8451921.html

【iOS开发每日小笔记(九)】在子线程中使用runloop,正确操作NSTimer计时的注意点 三种可选方法

这篇文章是我的[iOS开发每日小笔记]系列中的一片,记录的是今天在开发工作中遇到的,可以用很短的文章或很小的demo演示解释出来的小心得小技巧.它们可能会给用户体验.代码效率得到一些提升,或是之前自己没有接触过的技术,很开心的学到了,放在这里得瑟一下.其实,90%的作用是帮助自己回顾.记忆.复习. 一直想写一篇关于runloop学习有所得的文章,总是没有很好的例子.正巧自己的上线App Store的小游戏<跑酷好基友>(https://itunes.apple.com/us/app/pao-k

iOS开发线程和RunLoop

一般来讲,一个线程一次只能执行一个任务,执行完毕后线程就会退出,如果我们需要一个机制让线程能随时处理时间但并不退出,通常的代码逻辑是这样: 这就是 Event Loop框架. runloop实际上就是一个管理其需要处理的事件和消息的对象,并提供了一个入口函数来执行上面Event loop的逻辑.线程执行了这个函数之后,就会一直处于这个函数内部"接受消息->等待->处理"的循环中,知道这个循环结束(例如传入quite消息),函数返回. 在OSX和iOS系统中,提供了两个这样的

iOS开发 - 啰嗦讲解 Runloop

写在前面的 为什么要了解 RunLoop?如果你想成为一个高级iOS开发工程师,那这是你必须了解的东西,他能帮助你更好的理解底层实现的原理,可以利用它的特性做出一些高效又神奇的功能.RunLoop这个东西已经是在各路大神的Blog里面描述和详解过很多次的了,我把它翻出来再写一遍,一来是为了让自己温故而知新,二来会重点详细解读一下当初我理解时候遇到的难点,为初.中级想要进阶的iOS开发盆友排排坑. 本人写的东西不是很好(从小语文没学好),之前就懂的人看了肯定会觉得我很啰嗦(本人处女座,比较爱会啰嗦

iOS开发 底层抛析运行循环—— RunLoop

http://blog.csdn.net/zc639143029/article/details/50012527 一.RunLoop基本概念 概念:程序的运行循环,通俗的来说就是跑圈. 1. 基本作用(作用重大) (1) 保持程序的持续运行(ios程序为什么能一直活着不会死) (2) 处理app中的各种事件(比如触摸事件.定时器事件[NSTimer].selector事件[选择器·performSelector···]) (3)节省CPU资源,提高程序性能,有事情就做事情,没事情就休息 2.

iOS开发基础笔试题(3)

前言 以下部分题目来源于网络,笔者在此处收集起来,既是要巩固自我,也希望能够帮助到同样需要的人!参考答案均为笔者所写,其有疑问或者出错之处,请在评论中提出,谢谢!不喜勿喷! 1.即时聊天App不会采用的网络传输方式 1 2 3 4 5 6 A UDP B TCP C HTTP D FTP 参考答案:D FTP:是文件传输协议,是File Transfer Protocol的简称,它的作用是用于控制互联网上文件的双向传输,因此一定不会是即时聊天使用的: UDP:是面向无连接的传输层协议,数据传输是