RunLoop源码分析、基本使用场景和常见问题
2016-12-25 13:57
676 查看
YY大神的链接
goole ASDK的Runloop超流畅UI思路
循环运行 内部核心其实就是一个do while循环
跑圈 就和这个小伙子一样
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/07/7d89e435d8c3617cd15c7434300b1ff5)
基本作用
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
节省CPU资源,提高程序性能,做事的时候跑起来,不做的时候休眠
主线程的runloop自动创建,子线程的runloop需要手动创建,不创建就没有
Runloop在第一次获取时创建,在线程结束时销毁
[NSRunLoop currentRunLoop] // 获取当前线程的
[NSRunLoop mainRunLoop]// 获取主线程
core fundation
CFRunLoopGetCurrent();// 获取当前线程
CFRunLoopGetMain(); // 获取主线程
源代码分析系列,当我们调用上面的代码时,来看看源码源码下载地址
1.入口
2.看到
大致创建逻辑就是上面注释,你不需要显示alloc,你只需要获取就行了,内部已经有创建的逻辑,没错,RunLoop对象就搞定了,下面来看看对象到底是什么结构
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/07/598fcbce85f301fde40fafcb5bd422e3)
每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
2.UITrackingRunLoopMode: 页面跟踪Mode,用于滚动追踪触摸滑动,保证页面滑动式不受其他Mode影响
NSTimer默认是加到DefaultMode里面的,当滚动式切换到这个模式,所以之前的Timer事件不再调用
3.UIInitializationRunLoopMode:在刚启动App时第一次进入就是这个Mode,完成之后不再用
4.GSEventReveiveRunLoopMode:系统事件的内部Mode,通常用不到
5.CFRunLoopCommonModes:这个是一个占位用的Mode,不是一个真正的Mode,
用来标示切换不同Mode时是否加有这个字段的Mode还是能继续接受事件
Port-Based Sources 基于Mach port内核/其他线程事件源
Custom Input Sources 自定义基本不用
Cocoa Perform Selector Sources [Self PerformSele…]
按调用栈分Source:
Source0:非基于Port
Source1:基于Port,通过内核和其他线程通信,接受,分发系统事件
上述提到的Source/Timer/OBserver就是一个ModeItem,如果一个Mode中一个Item都没有,RunLoop会自动退出,这就是后面要提到的常驻线程创建方式,先看看RunLoop和Mode两者的结构,然后再举例子对上面的行为进行分析
个人理解下,RunLoop结构体中有个modes用来存放所有可切换的Mode,例如Default/Tracking等,currentMode就指定当前在运作的Mode,CommandModes就是存放着无论你切换哪个Mode,你都需要同步_commonModeItems下所有事件的ModeName,你可以把Mode看做一个个的属于你自己的任务,而commentMode看做每个人都要做的公共任务,当你有公共任务的时候也就是栈顶有Commentmode的时候,你做自己的子线任务的时候,同时需要做栈顶的公共任务
默认情况下我们创建的Timer只会在Default状态下执行,当我们滚动的时候Timer就会不调用事件,原因就是RunLoop中它默认加到了NSDefaultMode中,当滚动的时候切换到NSTrackingMode的时候,NSTimer自然就不执行了,这是因为RunLoop只会在一个Mode下运行,当切换Mode的时候需要重启Loop,而且事件不会同步,除非你把事件加到栈顶的CommonModes里面去,这样就可以在当前RunLoop下的所有模式下共享执行了
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/07/9f638b2dd227a6d5d967a6ad68c53753)
这里的做法是创建 一个RunLoop,但是如果没有添加任何Obserer/Timer/Source,Runloop默认你是会退出的,要进入循环必须加一个item
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/07/82668016ddef02af876982f8b5d7dbb1)
![](https://oscdn.geek-share.com/Uploads/Images/Content/202005/07/928a214803a7f7228e7346350ba2a5a6)
这个图的前提需要注意的是,需要检查ModeItem是否为空,是的话直接退出Loop
内部就是一个do - While循环,在这个循环内部不断处理各种事件
(Source、Timer、Observer)
一个线程对应一个RunLoop,主线程RunLoop默认已经启动,子线程的RunLoop得手动启动
RunLoop只能选择一个模式,如果当前模式中没有Source、Timer、Obeserver直接突出
你在开发中怎么使用RunLoop?
1.AF2x.和YYKit常驻线程
在子线程中开启定时器
子线程中长期监控一些行为(扫描网络、沙盒、语音监控)
2.可以控制定时器在特定模式下执行
3.可以让某些事件在特定模式下执行(Performance…inmodes(mode参数))
4.可以添加Observer监听RunLoop的状态,比如监听点击事件处理前处理某些事情
自动释放池什么时候释放
在RunLoop睡眠之前释放(kCFRunLoopBeforeWaiting)
(1)保持程序的持续运行,当有事做时做事,
(2)负责监听处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
(3)节省CPU资源,提高程序性能,做事的时候跑起来,不做的时候休眠。
每个线程都有一个Run Loop,主线程的Run Loop会在App运行时自动运行,子线程中需要手动获取运行,创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。当创建的loop,没有任何上面的属性,就会直接退出。
Run的时候必须制定mode,那么这里有个知识点是CommonMode,不能指定该Mode进行运行,但是可以把事件标记到该属性,那么无论切换到哪个Mode都会执行
Apple应用
1.AutoreleasePool
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,第一个 Observer 监视的事件是 Entry(即将进入Loop),执行方法去创建pool,优先级最高,第二个是BeforeWaiting(准备进入休眠) 时调用pop and push,先清除旧pool,然后创建新pool,或者再退出的时候释放自动释放池子,优先级最低
2.启动时注册,处理事件,nstimer,UI,渲染
3.网络请求框架下的线程通信
开发者应用:
1.常驻线程 处理网络请求的回调
2.Common在不同模式下也执行NSTimer
3.添加Observe属性的观察,在即将进入休眠的ASDK的原理,以下就是AS框架的Runloop应用,这里把排版,绘制等操作放到异步做,然后存储起来
在kCFRunLoopBeforeWaiting和kCFRunLoopExit两个activity的回调中将之前异步完成的工作同步到主线程中去。
ASDK goole的UI框架原理
一个自称用cell实现Runloop应用的人
结构:
goole ASDK的Runloop超流畅UI思路
Runloop
从字面上看循环运行 内部核心其实就是一个do while循环
跑圈 就和这个小伙子一样
BOOL running = YES; do{ // 处理各种事件执行各种任务 }while(running)
基本作用
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
节省CPU资源,提高程序性能,做事的时候跑起来,不做的时候休眠
Runloop与线程
每条线程都有唯一的一个与之对应的runloop对象主线程的runloop自动创建,子线程的runloop需要手动创建,不创建就没有
Runloop在第一次获取时创建,在线程结束时销毁
获取runloop对象
Fundation[NSRunLoop currentRunLoop] // 获取当前线程的
[NSRunLoop mainRunLoop]// 获取主线程
core fundation
CFRunLoopGetCurrent();// 获取当前线程
CFRunLoopGetMain(); // 获取主线程
源代码分析系列,当我们调用上面的代码时,来看看源码源码下载地址
1.入口
CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // no retain needed if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; } CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); }
2.看到
_CFRunLoopGet0这个函数没,这就是第二部,内部实现
// 全局字典,key是pthread_t value是CFRunLoopRef static CFMutableDictionaryRef __CFRunLoops = NULL; // 访问__CFRunLoops时的锁 static CFLock_t loopsLock = CFLockInit; // should only be called by Foundation // t==0 is a synonym for "main thread" that always works CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { // 如果传进来的线程为空,默认为主线程 if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); // 第一次进来全局字典里面没有任何东西 if (!__CFRunLoops) { __CFUnlock(&loopsLock); // 初始化全局字典 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); // 根据主线程创建主线程RunLoop CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); // 加入到全局字典 key main_thread value mainLoop CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); // 和全局__CFRunLoops关联上,然后把临时创建的杀死 if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } // 如果全局字典存在,根据线程取RunLoop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); // 娶不到创建一个 if (!loop) { // 创建 CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); // 再取一次 loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // 还是没有 if (!loop) { // 根据线程key把刚创建的关联上 CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFUnlock(&loopsLock); CFRelease(newLoop); } // 注册一个回调,当线程销毁时,通知销毁RunLoop if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; }
大致创建逻辑就是上面注释,你不需要显示alloc,你只需要获取就行了,内部已经有创建的逻辑,没错,RunLoop对象就搞定了,下面来看看对象到底是什么结构
RunLoop相关类
CFRunLoopRefCFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
CFRunLoopModeRef代表RunLoop的运行模式
一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
系统注册了五个Mode(可以打断点看调用栈)
1.kCFRunLoopDefaultMode:App的默认Mode,通常主线程实在这个Mode2.UITrackingRunLoopMode: 页面跟踪Mode,用于滚动追踪触摸滑动,保证页面滑动式不受其他Mode影响
NSTimer默认是加到DefaultMode里面的,当滚动式切换到这个模式,所以之前的Timer事件不再调用
3.UIInitializationRunLoopMode:在刚启动App时第一次进入就是这个Mode,完成之后不再用
4.GSEventReveiveRunLoopMode:系统事件的内部Mode,通常用不到
5.CFRunLoopCommonModes:这个是一个占位用的Mode,不是一个真正的Mode,
用来标示切换不同Mode时是否加有这个字段的Mode还是能继续接受事件
CFRunLoopSourceRef是事件源(输入源)
理论分Source:Port-Based Sources 基于Mach port内核/其他线程事件源
Custom Input Sources 自定义基本不用
Cocoa Perform Selector Sources [Self PerformSele…]
按调用栈分Source:
Source0:非基于Port
Source1:基于Port,通过内核和其他线程通信,接受,分发系统事件
CFRunLoopTimerRef)
这个东西就是一个定时器,通过设置特定的时间间隔来给叫醒RunLoop处理事件CFRunLoopObserverRef
CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0),// 即将进入RunLoop 1 kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer事件 2 kCFRunLoopBeforeSources = (1UL << 2), // 即将处理事件源 4 kCFRunLoopBeforeWaiting = (1UL << 5),// 即将进入休眠 32 kCFRunLoopAfterWaiting = (1UL << 6),// 即将进入从休眠中醒来 64 kCFRunLoopExit = (1UL << 7),// 退出 128 kCFRunLoopAllActivities = 0x0FFFFFFFU // 上述所有模式监听枚举 };
上述提到的Source/Timer/OBserver就是一个ModeItem,如果一个Mode中一个Item都没有,RunLoop会自动退出,这就是后面要提到的常驻线程创建方式,先看看RunLoop和Mode两者的结构,然后再举例子对上面的行为进行分析
struct __CFRunLoop { CFRuntimeBase _base; CFMutableSetRef _commonModes; // 名字Default/Tracking CFMutableSetRef _commonModeItems;// Timer/Source/Observer CFRunLoopModeRef _currentMode;// 当前Mode CFMutableSetRef _modes;//一个RunLoop下有多个Mode }; struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; // 用来存放到RunLoop中_commonModes/_modes的字段 Boolean _stopped; char _padding[3]; CFMutableSetRef _sources0;// 非port事件 CFMutableSetRef _sources1;// port事件 CFMutableArrayRef _observers;// 观察者 CFMutableArrayRef _timers;// timer ...... }
个人理解下,RunLoop结构体中有个modes用来存放所有可切换的Mode,例如Default/Tracking等,currentMode就指定当前在运作的Mode,CommandModes就是存放着无论你切换哪个Mode,你都需要同步_commonModeItems下所有事件的ModeName,你可以把Mode看做一个个的属于你自己的任务,而commentMode看做每个人都要做的公共任务,当你有公共任务的时候也就是栈顶有Commentmode的时候,你做自己的子线任务的时候,同时需要做栈顶的公共任务
实例1Timer
// 方法1 // on the current run loop in the default mode // 创建的时候是加到NSDefaultRunLoopMode中的 // [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; // 方法2 // 其实和方法一是一样的,只是展开来写罢了 NSTimer *timer = [NSTimer timerWithTimeInterval:2.3 target:self selector:@selector(run) userInfo:nil repeats:YES]; // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 为了在滚动的时候和不滚动的时候都进行调用NSTimer // 只需要把Timer加到当前RunLoop中的CommonModes Set容器里面去就好了,无论切换什么模式,都会同步该Timer [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
默认情况下我们创建的Timer只会在Default状态下执行,当我们滚动的时候Timer就会不调用事件,原因就是RunLoop中它默认加到了NSDefaultMode中,当滚动的时候切换到NSTrackingMode的时候,NSTimer自然就不执行了,这是因为RunLoop只会在一个Mode下运行,当切换Mode的时候需要重启Loop,而且事件不会同步,除非你把事件加到栈顶的CommonModes里面去,这样就可以在当前RunLoop下的所有模式下共享执行了
实例2Observer,可以在事件处理前进行一些拦截
// typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { // kCFRunLoopEntry = (1UL << 0),// 即将进入RunLoop 1 // kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer事件 2 // kCFRunLoopBeforeSources = (1UL << 2), // 即将处理事件源 4 // kCFRunLoopBeforeWaiting = (1UL << 5),// 即将进入休眠 32 // kCFRunLoopAfterWaiting = (1UL << 6),// 即将进入从休眠中醒来 64 // kCFRunLoopExit = (1UL << 7),// 退出 128 // kCFRunLoopAllActivities = 0x0FFFFFFFU // 上述所有模式监听枚举 // }; CFRunLoopObserverRef observe = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { if (activity == kCFRunLoopExit) { NSLog(@"即将退出runloop%lu",activity); } else if (activity == kCFRunLoopBeforeWaiting) { NSLog(@"即将进入休眠%lu",activity); } else if (activity == kCFRunLoopAfterWaiting) { NSLog(@"刚从休眠中唤醒%lu",activity); }else if (activity == kCFRunLoopBeforeSources) { NSLog(@"即将处理 Source%lu",activity); }else if (activity == kCFRunLoopBeforeTimers) { NSLog(@"即将处理 Timer%lu",activity); } else if (activity == kCFRunLoopEntry) { NSLog(@"即将进入Loop%lu",activity); } }); CFRunLoopAddObserver(CFRunLoopGetCurrent(), observe, kCFRunLoopDefaultMode); /* CF的内存管理(Core Foundation) 1.凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release * 比如CFRunLoopObserverCreate 2.release函数:CFRelease(对象); */ CFRelease(observe);
实例3 AFN2x和YYKit下 的常驻线程
// YYKit常驻线程 /// Network thread entry point. + (void)_networkThreadMain:(id)object { // 常规做法最外层也用autorelease包起来 @autoreleasepool { [[NSThread currentThread] setName:@"com.ibireme.yykit.webimage.request"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // 给创建的loop添加任意一个item [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } /// Global image request network thread, used by NSURLConnection delegate. + (NSThread *)_networkThread { static NSThread *thread = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ thread = [[NSThread alloc] initWithTarget:self selector:@selector(_networkThreadMain:) object:nil]; if ([thread respondsToSelector:@selector(setQualityOfService:)]) { thread.qualityOfService = NSQualityOfServiceBackground; } [thread start]; }); return thread; }
这里的做法是创建 一个RunLoop,但是如果没有添加任何Obserer/Timer/Source,Runloop默认你是会退出的,要进入循环必须加一个item
RunLoop整体逻辑
这个图的前提需要注意的是,需要检查ModeItem是否为空,是的话直接退出Loop
源码分析
//1.直接调用Run方法 void CFRunLoopRun(void) { /* DOES CALLOUT */ do { result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); } // 通过指定Mode调用Run方法 SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); } // 2.启动 SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ //3. 根据RunloopModeName找到Mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); //4. 如果Mode为空里面没有任何的item直接返回 if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) { return kCFRunLoopRunFinished; } //5. 如果不为空 先通知Observer:即将进入Loop kCFRunLoopEntry = (1UL << 0),// 即将进入RunLoop 1 if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); //6. 进入核心函数进行跑圈loop result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); //end: kCFRunLoopExit = (1UL << 7),// 退出 128 if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; } // 核心跑圈代码 /* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); __CFPortSet waitSet = rlm->_portSet; __CFRunLoopUnsetIgnoreWakeUps(rl); // 8.通知Observer // kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer事件 2 // kCFRunLoopBeforeSources = (1UL << 2), // 即将处理事件源 4 if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); // 任务加入Block处理 __CFRunLoopDoBlocks(rl, rlm); //9. 处理Source0事件 执行事件加入block Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { __CFRunLoopDoBlocks(rl, rlm); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); //10. 如果有source1进来,基于port的消息进来 处理Source1 goto语句跳转到下面 if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI msg = (mach_msg_header_t *)msg_buffer; if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { // 跳转 goto handle_msg; } } didDispatchPortLastTime = false; //11. 通知ObserverseRunloop即将进入休眠kCFRunLoopBeforeWaiting if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI #if USE_DISPATCH_SOURCE_FOR_TIMERS do { //12. 调用mach方法等待消息的接受,线程进入休眠,当port传来source1的时候,当Timer时间到了,当Loop超时了 // 当被显示唤醒的时候 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); } while (1); //13. 通知Runloop线程被刚刚唤醒 if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); //14. 处理消息 handle_msg:; __CFRunLoopSetIgnoreWakeUps(rl); //15. 如果Timer到了 触发Timer回调 CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { __CFArmNextTimerInMode(rlm, rl); } } //16. 如果是由dispatch到main_queue的block,执行block CFRUNLOOP_WAKEUP_FOR_DISPATCH(); __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); //17. 如果一个source1发出事件,处理source1 CFRUNLOOP_WAKEUP_FOR_SOURCE(); // Despite the name, this works for windows handles as well CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop; __CFRunLoopDoBlocks(rl, rlm); //18.处理是否继续循环 if (sourceHandledThisLoop && stopAfterHandle) { // 进入loop时参数说处理完事件就返回。 retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { // 超时 retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rl)) { // 外部强制停止 __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { // source/timer/observer为空一个都没了 retVal = kCFRunLoopRunFinished; } } while (0 == retVal);// 还是0,继续跑 return retVal; }
关于Runloop的一些问题
什么是RunLoop?内部就是一个do - While循环,在这个循环内部不断处理各种事件
(Source、Timer、Observer)
一个线程对应一个RunLoop,主线程RunLoop默认已经启动,子线程的RunLoop得手动启动
RunLoop只能选择一个模式,如果当前模式中没有Source、Timer、Obeserver直接突出
你在开发中怎么使用RunLoop?
1.AF2x.和YYKit常驻线程
在子线程中开启定时器
子线程中长期监控一些行为(扫描网络、沙盒、语音监控)
2.可以控制定时器在特定模式下执行
3.可以让某些事件在特定模式下执行(Performance…inmodes(mode参数))
4.可以添加Observer监听RunLoop的状态,比如监听点击事件处理前处理某些事情
自动释放池什么时候释放
在RunLoop睡眠之前释放(kCFRunLoopBeforeWaiting)
总结
4.什么是Runloop
Runloop他的本质就是一个do,while循环(1)保持程序的持续运行,当有事做时做事,
(2)负责监听处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
(3)节省CPU资源,提高程序性能,做事的时候跑起来,不做的时候休眠。
每个线程都有一个Run Loop,主线程的Run Loop会在App运行时自动运行,子线程中需要手动获取运行,创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。当创建的loop,没有任何上面的属性,就会直接退出。
Run的时候必须制定mode,那么这里有个知识点是CommonMode,不能指定该Mode进行运行,但是可以把事件标记到该属性,那么无论切换到哪个Mode都会执行
Apple应用
1.AutoreleasePool
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,第一个 Observer 监视的事件是 Entry(即将进入Loop),执行方法去创建pool,优先级最高,第二个是BeforeWaiting(准备进入休眠) 时调用pop and push,先清除旧pool,然后创建新pool,或者再退出的时候释放自动释放池子,优先级最低
2.启动时注册,处理事件,nstimer,UI,渲染
3.网络请求框架下的线程通信
开发者应用:
1.常驻线程 处理网络请求的回调
2.Common在不同模式下也执行NSTimer
3.添加Observe属性的观察,在即将进入休眠的ASDK的原理,以下就是AS框架的Runloop应用,这里把排版,绘制等操作放到异步做,然后存储起来
在kCFRunLoopBeforeWaiting和kCFRunLoopExit两个activity的回调中将之前异步完成的工作同步到主线程中去。
ASDK goole的UI框架原理
一个自称用cell实现Runloop应用的人
__unsafe_unretained __typeof__(self) weakSelf = self; void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { [weakSelf processQueue]; }; _runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, handlerBlock); CFRunLoopAddObserver(_runLoop, _runLoopObserver, kCFRunLoopCommonModes);
结构:
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 };
相关文章推荐
- libevent源码分析---基本使用场景和事件流程
- Redis 专栏(使用介绍、源码分析、常见问题...)
- [置顶] Redis 专栏(使用介绍、源码分析、常见问题...)
- 使用逻辑分析仪分析IIC时序常见问题
- Volley源码解析使用方式和使用场景分析
- 常见NoSQL系统使用场景分析
- Nginx源码分析-核心模块剖析及常见问题
- (转)Linux共享内存使用常见陷阱与分析(2)-多次进行shmat会出现什么问题
- ThreadLocal源码分析与使用场景
- Rxjava2源码分析(一):Flowable的创建和基本使用过程分析
- Android Studio 之基本使用和常见问题
- JfreeChart使用经验总结(分析了使用中碰到的一些常见问题)
- 源码使用常见问题及解决方案
- 锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)
- ASP.NET2.0使用Enter Key作为默认提交问题分析(附源码)
- 消息队列(Message Queue)基本概念和使用场景分析
- JfreeChart使用经验总结(分析了使用中碰到的一些常见问题)
- JSP中的常见问题及一些源码分析
- 常见的NoSql系统使用场景分析--转载
- ASP.NET2.0使用Enter Key作为默认提交问题分析(附源码)