iOS 涨薪: Run Loop 面试题
Run Loop
运行循环
app 程序只有不停地运行, 才能不断响应用户的操作
Run Loop 两大功能:
-
睡眠中,等待消息
-
处理消息
从睡眠中 -> 处理消息, 需要一个唤醒的过程
1、 讲讲 RunLoop, 项目中有用到吗?
RunLoop 的基本作用:
保持程序的持续运行
节省 CPU 的资源,提高程序的性能 ( 没有事情,就请休眠,不要功耗。有事情,就处理)
2、 RunLoop 内部实现逻辑?
RunLoop 里面有很多种模式,他在运行的过程中,只会选择一种来运行
Modes, 就是 RunLoop 平时要做的事情
- Core Foundation 中关于 RunLoop 的 5 个类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
CFRunLoopModeRef
-
CFRunLoopModeRef 代表 RunLoop 的运行模式
-
一个 RunLoop 包含若干个 Modes, 每个 Mode 又包含若干个 Source0 / Source1 / Timer / Observer
-
RunLoop 启动时,只能选择其中一个 Mode, 作为 currentMode
-
如果需要切换 Mode, 只能退出当前 Loop, 再重新选择一个 Mode 进入
不同组的 Source0 / Source1 / Timer / Observer 能分割开来,互不影响
- 如果 Mode 里没有任何 Source0 / Source1 / Timer / Observer , RunLoop 会立刻退出
-
__CFRunLoop
的数据结构
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;
// 这里有一个集合
// 装的是一些 CFRunLoopModeRef
CFMutableSetRef _modes;
// _modes 里面有很多 mode ( CFRunLoopModeRef )
// 其中有一个 mode 是 _currentMode ( 当前模式 )
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
进入 mode
CFRunLoopModeRef 的数据结构
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];
// 这两个集合,装的是 CFRunLoopSourceRef 对象
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
// 对应平时的事情:
// 包括点击事件,刷新 UI 事件,performSelector
// 这个数组,装的是 CFRunLoopObserverRef 对象
CFMutableArrayRef _observers;
// 监听器
// 这个数组,装的是 CFRunLoopTimerRef 对象
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 */
};
3、 RunLoop 和线程的关系?
- 每条线程,都有唯一的一个,与之对应的 RunLoop 对象
runloops[thread] = runloop
-
RunLoop 保存在一个全局的 Dictionary 里,线程作为 Key, RunLoop 作为 Value
-
线程刚创建时,并没有 RunLoop 对象, RunLoop 会在第一次获取它时,创建
-
RunLoop 会在线程结束时,销毁
线程都没有了,runloop 也就没有意义了
子线程,要什么 runloop?
没有 runloop , 就是命令行,调用一次就完结
有了 runloop, 可以反复休眠、唤醒、处理消息
- 主线程的 RunLoop 已经自动获取 ( 创建 ),子线程默认没有开启 RunLoop
子线程中,获取一下 currentRunLoop, 就创建开启了 RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
// 从这里拿
return _CFRunLoopGet0(pthread_self());
}
进入详情
// 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);
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);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
// 从字典里面,获取 runloop 对象
// __CFRunLoops 字典
// pthreadPointer(t) ,键 key
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// runloop 对象 loop 不存在,就新建
if (!loop) {
// 字典中,设置
// __CFRunLoops 字典
// pthreadPointer(t),键 key
// newLoop, 值 value
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)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
4、 timer 和 runloop 的关系?
5、 程序中添加每 3 秒响应一次的 NSTimer, 当拖动 tableView 时 timer 可能无法响应,要怎么解决?
CFRunLoopModeRef
常见的 2 种 Mode:
-
kCFRunLoopDefaultMode ( NSDefaultRunLoopMode ) : App 的默认 Mode, 通常主线程是在这个 Mode 下运行
-
UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滚动,保证界面滚动时,不受其他 Mode 的影响
切换 Mode:
是在 while 的循环中进行,相当于一次 goto, 或者 continue
6、 RunLoop 是怎么响应用户操作的,具体流程是什么样的?
7、 说说 RunLoop 的几种状态
8、 runloop 的 mode 作用是什么?
source 0
处理触摸 ( 点击 )事件
performSelector: onThread: (把方法放在子线程,去执行)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%s", __func__);
// 断点, bt 一下
}
点击一下,进入断点
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
// 我们的 source code
* frame #0: 0x0000000104d2eed4 RunLooper`-[ViewController touchesBegan:withEvent:](self=0x00007fe015509870, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000016998c0) at ViewController.m:43:1
frame #1: 0x00007fff48cb94e2 UIKitCore`forwardTouchMethod + 323
frame #2: 0x00007fff48cb938e UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x00007fff48cc82ab UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
frame #4: 0x00007fff48cca311 UIKitCore`-[UIWindow sendEvent:] + 4501
frame #5: 0x00007fff48ca4755 UIKitCore`-[UIApplication sendEvent:] + 356
frame #6: 0x00007fff48d2f552 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 7628
frame #7: 0x00007fff48d32716 UIKitCore`__handleEventQueueInternal + 6584
// 处理事件
frame #8: 0x00007fff48d28fb9 UIKitCore`__handleHIDEventFetcherDrain + 88
// source 0
frame #9: 0x00007fff23da0d31 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #10: 0x00007fff23da0c5c CoreFoundation`__CFRunLoopDoSource0 + 76
// source 0
frame #11: 0x00007fff23da0434 CoreFoundation`__CFRunLoopDoSources0 + 180
frame #12: 0x00007fff23d9b02e CoreFoundation`__CFRunLoopRun + 974
frame #13: 0x00007fff23d9a944 CoreFoundation`CFRunLoopRunSpecific + 404
frame #14: 0x00007fff38ba6c1a GraphicsServices`GSEventRunModal + 139
frame #15: 0x00007fff48c8b9ec UIKitCore`UIApplicationMain + 1605
frame #16: 0x0000000104d2f182 RunLooper`main(argc=1, argv=0x00007ffeeaecfd30) at main.m:18:12
frame #17: 0x00007fff51a231fd libdyld.dylib`start + 1
source 1
- 基于 port 的线程间通信
- 系统事件捕捉
(例如: 在屏幕上点一下,产生一个点击事件,
这个点击事件,一开始,属于 source 1,
再包装成 source 0 ,来处理 )
屏幕点击, source 1 捕捉,分发到 source 0 来处理
Timers
- NSTimer
- performSelector: withObject: afterDelay:
Observers
- 用于监听 RunLoop 的状态
- UI 刷新 ( before waiting )
监听器一旦监听到, runLoop 要休眠了,
他就会在 runLoop 休眠之前,
刷新 UI 界面
- 自动释放池, autoReleasePool
runLoop 休眠之前, 清理一下自动释放池
使用的代码, 目前最新 1153
上一篇: python爬虫爬取有道翻译
推荐阅读