欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS 涨薪: Run Loop 面试题

程序员文章站 2022-04-08 23:19:21
...

Run Loop

运行循环

app 程序只有不停地运行, 才能不断响应用户的操作

Run Loop 两大功能:


  • 睡眠中,等待消息

  • 处理消息

从睡眠中 -> 处理消息, 需要一个唤醒的过程


1、 讲讲 RunLoop, 项目中有用到吗?

RunLoop 的基本作用:

保持程序的持续运行

节省 CPU 的资源,提高程序的性能 ( 没有事情,就请休眠,不要功耗。有事情,就处理)

iOS 涨薪: Run Loop 面试题


2、 RunLoop 内部实现逻辑?

RunLoop 里面有很多种模式,他在运行的过程中,只会选择一种来运行

Modes, 就是 RunLoop 平时要做的事情

iOS 涨薪: Run Loop 面试题

  • 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

代码 github

相关标签: 面试 ios