面试题
1.讲讲runloop,项目中有用?
2.runloop的内部实现?
3.runloop和线程的关系?
4.timer和runloop的关系?
5.程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
6.runloop是怎么响应用户操作的,具体流程是什么样的?
7.说说runLoop的几种状态
8.runloop的mode作用是什么?
在下面的文章里面,我们一一介绍
本文Demo代码可见gitHub_Demo
PS:如果想看更加具体的 详见:深入理解RunLoop
-
什么是runloop?
- 运行循环,在程序运行过程中循环做一些事情
-
应用范畴
- 定 时 器( Timer )、PerformSeIector
- GCDAsynMainQueue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
-
有/没有runloop对象
1.没有runloop int main(int argc, char * argv[]) { @autoreleasepool { NSLog(@"Hello world"); } } //执行完后会退出程序 2.有runloop int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 3.伪代码 int main(int argc, char * argv[]) { @autoreleasepool { int retVal = 0; do{ //睡眠中等待消息 int message = sleep_and_wait(); //处理消息 retVal = process_message(message); }while (retVal == 0); return 0; } }
-
有了runloop之后,程序并不会退出,而是保持运行状态
-
Runloop的基本作用
- 保持程序的持续运行
- 处理app的各种事件
- 手势识别
- 界面更新
- 定时器
- PerformSelector
- AutoreleasePool
- 事件响应
- 关于GCD
- 网络请求
- ...
- 节省CPU资源,提高创新性能,该做事的时候做事,该休息的时候休息
-
RunLoop对象
runloop_01.png
https://opensource.apple.com/tarballs/CF/
-
RunLoop与线程
我们先来看看底层的实现(这里我是根据上面地址下载【CF-1151.16】)
CFRunLoop.c文件 ======================== //全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef static CFMutableDictionaryRef __CFRunLoops = NULL; //访问 loopsDic 时的锁 static CFLock_t loopsLock = CFLockInit; //获取一个 pthread 对应的 RunLoop CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t){ if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); if (!__CFRunLoops) { // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop __CFUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } //直接从 Dictionary 里获取 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) { 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); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { //注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; }
- 从源码中我们可以看出
- 每一条线程都有一个唯一的与之对应的Runloop对象
- Runloop保存在一个全局的Dictionary里,线程作为key,Runloop作为value
- 线程刚创建时并没有Runloop对象,Runloop会在第一次获取时创建
- Runloop会在线程结束时销毁
- 主线程的Runloop已经自动获取(创建),子线程默认没有开启Runloop
- 从源码中我们可以看出
-
获取RunLoop对象
-
Foundation
-
[NSRunLoop currentRunLoop] ;//获取当前线程的RunLoop对象
-
[NSRunLoop mainRunLoop]; //获取主线程的RunLoop对象
-
Core Foundation
-
CFRunLoopGetCu rrent() ;//获取当前线程的RunLoop对象
-
CFRunLoopGetMain(); //获取主线程的RunLoop对象
//获取Runnloop对象 NSRunLoop *runloop = [NSRunLoop currentRunLoop]; //OC CFRunLoopRef runloop2 = CFRunLoopGetCurrent(); //C NSLog(@"%p",runloop); NSLog(@"%p",runloop2); //%p 打印出来的地址不一样:因为OC是C的包装
-
-
-
RunLoop相关的类
//类CFRunLoopRef(在CFRunLoop.h里) typedef struct __CFRunLoop * CFRunLoopRef; //类CFRunLoopSourceRef(在CFRunLoop.h里) typedef struct __CFRunLoopSource * CFRunLoopSourceRef; //类CFRunLoopObserverRef(在CFRunLoop.h里) typedef struct __CFRunLoopObserver * CFRunLoopObserverRef; //类CFRunLoopTimerRef(在CFRunLoop.h里) typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef; //类CFRunLoopModeRef(在CFRunLoop.c里) //(这个类并没有暴露出来,只是通过 CFRunLoopRef的接口进行了封装) typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; /* must have the run loop locked before locking this */ CFStringRef _name; Boolean _stopped; char _padding[3]; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; CFMutableDictionaryRef _portToV1SourceMap; __CFPortSet _portSet; CFIndex _observerMask; #if USE_DISPATCH_SOURCE_FOR_TIMERS dispatch_source_t _timerSource; dispatch_queue_t _queue; Boolean _timerFired; // set to true by the source when a timer has fired Boolean _dispatchTimerArmed; #endif #if USE_MK_TIMER_TOO mach_port_t _timerPort; Boolean _mkTimerArmed; #endif #if DEPLOYMENT_TARGET_WINDOWS DWORD _msgQMask; void (*_msgPump)(void); #endif uint64_t _timerSoftDeadline; /* TSR */ uint64_t _timerHardDeadline; /* TSR */ }; //typedef struct __CFRunLoop * CFRunLoopRef; (暴露在.h文件外面的) struct __CFRunLoop { CFRuntimeBase _base; pthread_mutex_t _lock; /* locked for accessing mode list */ __CFPort _wakeUpPort; // used for CFRunLoopWakeUp Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop pthread_t _pthread; uint32_t _winthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFAbsoluteTime _runTime; CFAbsoluteTime _sleepTime; CFTypeRef _counterpart; };

下面我们具体的分析
-
CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1
- Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件
- Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程
-
CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
-
CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
//CFRunLoop.h LINE--58 /* Run Loop Observer Activities */ 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 kCFRunLoopAllActivities = 0x0FFFFFFFU };
-
CFRunLoopModeRef
CFRunLoopMode 和 CFRunLoop 的结构大致如下: 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 ... }; 这里有个概念叫 “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里 CFRunLoop对外暴露的管理 Mode 接口只有下面2个: 1.CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName); 2.CFRunLoopRunInMode(CFStringRef modeName, ...); Mode 暴露的管理 mode item 的接口有下面几个: 1. CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); 2. void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); 3. void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); 4. Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode); 5. void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode); 6. void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode); 7. Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode); 8. void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode); 9. void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
-
CFRunLoopModeRef代表RunLoop的运行模式
-
一个RunLoop包含若干个Mode,每个Mode又包含若干个 Source0/Source1/Observer/Timer
-
RunLoop启动时只能选择其中一个Mode,作为currentMode
-
如果需要切换Mode,只能退出当前的Loop,再重新选择一个mode进入
- 不同组的Source0/Source1/Observer/Timer能分隔开来,互不影响
-
如果mode里面没有任何Source0/Source1/Observer/Timer,RunLoop会立马退出
-
常见的2种mode
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行的
- UITrackingRunLoopMode:界面跟踪的Mode,用于scrollview追踪触摸滚动,保证界面滑动时不受其他的Mode影响
-
kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
-
下面我们通过代码来看看
- CFRunLoopObserverRef
-(void)creatObserver1{
//创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
//监听的方法
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry--即将进入Loop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers--即将处理 Timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources--即将处理 Source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting-- 即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfter-(void)creatObserver2{Waiting-- 刚从休眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit--即将退出Loop");
break;
default:
break;
}
}
============================================================
-(void)creatObserver2{
//创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - 即将进入Loop %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeTimers: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeTimers - 即将处理 Timer %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeSources: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeSources -即将处理 Source %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeWaiting: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeWaiting - 即将进入休眠 %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopAfterWaiting: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopAfterWaiting - 刚从休眠中唤醒 %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - 即将退出Loop %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
友情链接:
网友评论