RunLoop

CF-1151.16文件源码地址:http://opensource.apple.com/tarballs/CF/

CF-1151.16文件RunLoop解释: https://www.cnblogs.com/chengsh/p/8629605.html

RunLoop概念

  1. runloop作用
    runLoop的存在解决了cpu空转的问题,它是不停的检测是否有事件触发,如果有就唤醒cpu进行处理事件,处理完事件进入睡眠态,等待下次唤醒。这样cpu利用率就提高了。
  2. CFRunLoopRef和NSRunLoop
    CFRunLoopRef是在CoreFoundation框架中,它是c代码,是线程安全的。NSRunLoop是对CFRunLoop的封装,它是线程不安全的。
  3. RunLoop和线程关系
    RunLoop和线程是一一对象关系 coreFoundation框架中的CFRunLoopRef对应pthread_t。NSRunLoop对应NSThread

RunLoop与线程的关系

  1. runLoop与线程关系
    runLoop与线程是一一对应的关系,它是以线程为key以runLoop为value保存在一个全局的字典里。

  2. runLoop何时创建
    runLoop不获取是不会创建的,只有当获取时候才会创建。比如主线程的runLoop,是在第一次获取时候创建的。其他线程的runLoop也是获取时候不存在,才创建的。

  3. runLoop何时销毁
    runLoop是在线程销毁时候结束

  4. coreFoundation框架的相关源码如下:

/// 全局的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 对外的接口

  1. CoreFoundation里面的runLoop类
    CoreFoundation里面关于runLoop一共有5个类 CFRunLoopRef,CFRunLoopModeRef,CFRunLoopSourceRef,CFRunLoopTimerRef,CFRunLoopObserveRef。

  2. 结构如下图所示:
    image

  3. 结构解释

    • 关于Mode。一个RunLoop包含若干个Mode,一个RunLoop某一个时刻只能有一个mode,这个mode为CurrentMode。当runLoop想要切换Mode时候需要退出Loop。再重新进入一个mode。
    • 关于Source。source有两个一个是source0,一个是source1.source0是不会主动触发事件的。它需要我们手动调用方法。如手动调用CFRunLoopSourceSignal,将这个source标记为待处理,然后手动调用CFRunLoopWakeUp来唤醒RunLoop,让其处理这个事件。source1是可以主动唤醒runLoop线程。
    • 关于Timer。它是一个时间的触发器,它包含一个时长,和一个回调函数。当加入runLoop后到了这个时长后就会进行该函数的回调。
    • 关于observer。它是runLoop的观察者,它可以观察runLoop的当前的状态。

RunLoop的Mode

  1. 主线程的RunLoop的mode
    苹果公开提供的有两个,kCFRunLoopDefaultMode和UITrackingRunLoopMode。kCFRunLoopDefaultMode是app的默认模式,UITrackingRunLoopMode是UIScrollView的滑动模式。NSTimer默认是加入了主线程的RunLopp的default模式了,所以当界面滑动时候定时器是不相应的。有两个办法,一个是也将定时器加入到UITrackingRunLoopMode模式。还要一个好办法是将定时器加入到kCFRunLoopCommonModes模式,(实际上是将定时器加入到了commonModels里了或者commonModeItems里了)当runLoop进入到UITrackingRunLoopMode模式时候,runLoop会将_commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。(默认两种模式都标记了Common)

  2. mode的操作
    当传入一个modeName时候可以操作这个mode,比如传入kCFRunLoopDefaultMode或者UITrackingRunLoopMode。当传入一个没有的mode时候就会自动创建这个mode。mode只可以增加,不可以减少。

  3. 系统默认会注册5个mode

    • UIInitializationRunLoopMode
      刚启动APP时会调用一次,启动完以后不再使用。
    • kCFRunLoopDefaultMode
      App的默认 Mode,通常主线程是在这个 Mode 下运行的。
    • UITrackingRunLoopMode
      界面跟踪 Mode,用于 ScrollView 追踪触摸滑动
    • kCFRunLoopCommonModes
      这是一个占位的 Mode,没有实际作用。
    • GSEventReceiveRunLoopMode
      接受系统事件的内部 Mode,通常用不到。
    • 其他Mode
      苹果内部还有很多的 Mode,但那些 Mode 在开发中就很难遇到了。
struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array
    ...
};
 
struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
};

runLoop核心代码

  1. CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { ....
 //通知runLoop即将进入
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    //执行__CFRunLoopRun (主要!!!)
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    //如果runLoopExit就通知runLoop退出了
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
....
}
  1. __CFRunLoopRun
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

//判断runLoop,runLoopMode是停止
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
	return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	rlm->_stopped = false;
	return kCFRunLoopRunStopped;
    }
    
//如果设置了超时时间,到时间就唤醒runLoop    
    /* 定义了一个dispatch_source_t,为了唤醒runLoop(由于超时唤醒runLoop)
     *  504911232.0 >= seconds > 0将会因为超时而唤醒runLoop,其他时间不会run方法就不会
     */
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT /* #define TIMER_INTERVAL_LIMIT    504911232.0 */) {
        /* 获取一个队列 */
	dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
        /* 创建一个计时器 */
	timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
        /* 将计时器赋值给 timeout_context->ds*/
	timeout_context->ds = timeout_timer;
	timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
        /* 超时时间的赋值 */
	timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
        /* 设置上下文 */
	dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
        /* 设置超时时间的回调事件(这个事件唤醒了runLoop) */
	dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        /* 设置取消超时事件回调函数 */
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        /* 设置超时时间 */
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        /* 启动计数器 */
        dispatch_resume(timeout_timer);
    } else { // infinite timeout
        /* 调用run方法会走这里 */
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    
//进入do,while循环
   do{
       //执行通知回调timers和sources0
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        /* runLoop触发sourc0回调 */
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        //如果有source1事件就去处理source1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
        }
        
       /* 如果poll为1则不执行将要进入睡眠observer */
	   if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
	
	  /* 如果poll=1,说明当前输入活跃状态,那么久不执行已经离开睡眠态。否则就执行已经离开睡眠态的回调 */
	  if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
	
	  /******** ios处理timers事件 *********/
       if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
        // Re-arm the next timer
        __CFArmNextTimerInMode(rlm, rl);
       }
            
       /*********** ios处理dispatch事件 ***********/
       __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPAT CH_QUEUE__(msg);
    
      //处理source1事件
      mach_msg_header_t *reply = NULL;
		sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
		if (NULL != reply) {
		    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
		    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
		}
		
		if (sourceHandledThisLoop && stopAfterHandle) {
	    retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            /* 超时事件 */
            retVal = kCFRunLoopRunTimedOut;
    	} else if (__CFRunLoopIsStopped(rl)) {
           /* runLoop停止事件 */
            __CFRunLoopUnsetStopped(rl);
	    retVal = kCFRunLoopRunStopped;
	   } else if (rlm->_stopped) {
        /* runLoopMode 停止 */
	    rlm->_stopped = false;
	    retVal = kCFRunLoopRunStopped;
   	  } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
        /* runLoopMode是空 */
	    retVal = kCFRunLoopRunFinished;
    	}
   } while (0 == retVal);
        
}

AutoreleasePool

  1. App启动后,苹果在主线程的runLoop创建两个Observer。
    • 第一个 Observer 监视的事件是Entry(即将进入Loop)时候,会创建一个自动释放池。它的优先级最高,来保证在回调之前创建好自动释放池。
    • 第二个 Observer 监视了两个事件。准备进入休眠时候调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush()。pop方法优先级最低,保证在回调后进行清倒释放池。清倒完释放池后就创建新的释放池。

事件响应

  1. 系统注册一个Source1来接收系统事件
    当点击一个按钮时候,首先IOKit.frameWork生成一个IOHIDEvent事件并由SpringBoard接收。随后用mach port转发给需要的App进程。随后苹果注册的source1就会被触发回调。并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等

界面更新

  1. 界面更新原理

    • 苹果会在主线程的runLoop中注册一个observer,这个observer负责监听cpu即将进入睡眠态(BeforeWaiting),一旦cpu进入睡眠态将触发一个回调函数_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数会遍历所有待处理的UIView/CALayer以执行实际的绘制和调整,并更新 UI 界面。
    • 如我们在ViewDidLoad方法里写了一些更新View的代码,打断点,明明程序已经执行了更新UI的代码,但是为什么屏幕上并没有看到呢。其实这是因为,当执行完更新屏幕代码后只是把该View标记为代办事件,并加入到了一个全局的容器中而已,当整个代码跑起来后cpu进入睡眠态后触发了主线程的RunLoop的observer,触发了更新View的函数回调进行更新view,这时候我们才看到屏幕上的视图有变化。
  2. 阻塞主线程为什么不行呢
    阻塞主线程为什么就看不到屏幕界面更新,屏幕视图变化呢。那是因为,当阻塞了主线程后,cpu会不停的处理主线程的代码,cpu无法进入睡眠态,导致无法触发主线程的runLoop回调,也就无法更新ui视图,所以看不到屏幕视图变化,甚至卡顿。

RunLoop的应用

一.runLoop在系统层级的应用
  1. 界面的更新 (原理如上)
  2. 事件相应(原理如上)
  3. autoReleasepool(原理如上)
二.runLoop在编码层级的应用
  1. AFN
    一个app要进行很多次网络请求,网络请求是在后台线程运行。那么就是后台线程不停的创建,不停地销毁这是不开学的。这时候需要做到一个子线程永远运行(即线程永驻)。

  2. timer
    定时器往往是加入到了主线程的kCFRunLoopDefault模式,这样每次滑动界面时候runLoop切换到UITrackingRunLoopMode时候定时器就得不到回调。解决方法是将定时器加入到主线程的kCFRunLoopCommonMode模式下即可。但是如果要将定时器加入到子线程那么需要做子线程常驻。

  3. 图片的显示
    可以将图片的显示放到runLoop的kCFRunLoopDefault模式。这样当滑动UITableView时候图片不显示,当停下来时候才会显示。

  4. 循环中手动创建自动释放池
    起到降低内存峰值的作用
    注意:手动创建的自动释放池,在代码块结束后就释放,runLoop创建的自动释放池受runLoop管理。

  5. 代码如下:

  • AFN:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [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;
}

- (void)start {
    [self.lock lock];
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}
  • 定时器线程常驻
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSThread *aThread = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
    [aThread start];
}

- (void)startThread{
    @autoreleasepool{
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timeMethod) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
        
        NSLog(@"%@",[NSThread currentThread]);
    }
}

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

  • 降低内存峰值
for (NSDictionary *record in databaseRecords) {
    @autoreleasepool {
        EOCPerson * person = [[EOCPerson alloc] initWithRecord:record];
        [people addObject:person];
    }
}

参考:https://blog.ibireme.com/2015/05/18/runloop/

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值