iOS面试总结

RunLoop解析

2019-06-26  本文已影响0人  Jimmy_L_Wang

什么是RunLoop?

RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象

事件循环 Event Loop

event_loop01.png event_loop02.png

应用程序一般运行在用户态上面,当发生系统调用,需要一些底层API的话,是运行在内核态的。同时用户态的有些内容可以对内核态进行一些线程的调度和管理,包括进程间的通信。

main()函数为何会保持一致运行的状态而不退出?

因为main()函数在内部会调用UIApplicationMain()函数,UIApplicationMain()内部会启动一个主线程的runloop,进行消息的接收,消息的处理,已经处理后的等待这么一个循环,而这个循环不是一个简单的for循环或while循环,他是发生了一个用户态到内核态的切换,以及内核态到用户态的切换。

RunLoop的数据结构

NSRunLoopCFRunLoop的封装,提供了面向对象的API。

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;//--对应(runloop和线程关系)
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};
cfrunloop结构.png

CFRunLoopMode

runloopmode.png

CFRunLoopSource

CFRunLoopTimer

基于事件的定时器

​ 和NSTimertoll-free bridged的。

CFRunLoopOberver

观测时间点:

各个数据结构之间的关系

关系.png

线程与runloop是一对一的关系,runloop与Mode之间是1对多的关系,Mode与source,timer,observer是一对多的关系

Runloop的Mode

runloop_mode.png

当runloop运行在mode1当中时,我们只能处理mode1当中的事件,此时如果mode2,mode3对应的一些回调事件需要处理,如果不是运行在对应mode下面是没办法处理的,这就是mode有多个的原因。

CommonMode的特殊性

NSRunLoopCommonModes

如何将timer添加到两个mode当中?

RunLoop事件循环机制

void CFRunLoopRun()

事件循环机制.png

RunLoop的核心

runloop核心.png

调用mach_msg()函数系统会从核心态到用户态的切换,需要出现source1或Timer事件,或手动唤醒。

RunLoop与NSTimer

为什么在滑动tableView时,banner不会再发生滚动?

当我们滑动tableview的时候mode会由:kCFRunLoopDefaultMode切换为UITrackingRunLoopMode。所以出于之前mode上的timer事件就不会生效。如何解决这个问题,让其生效?可以通过void CFRunLoopAddTimer(runloop,timer, commenMode)函数,把NSTimert添加到commenMode中。

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
    if (NULL == rl->_commonModeItems) {
        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    }
    CFSetAddValue(rl->_commonModeItems, rlt);
    if (NULL != set) {
        CFTypeRef context[2] = {rl, rlt};
        /* add new item to all common-modes */
        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
        CFRelease(set);
    }
    } else {
    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
    if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
    }
    if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
        rlt->_runLoop = rl;
        } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
        return;
        }
        CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                // Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
    }
        if (NULL != rlm) {
        __CFRunLoopModeUnlock(rlm);
    }
    }
    __CFRunLoopUnlock(rl);
}

static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
    CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
    CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == __kCFRunLoopTimerTypeID) {
    CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
    }
}

RunLoop与多线程

关系:

怎样实现一个常驻线程

  1. 为当前线程开启/创建一个RunLoop。

    CFRunloopGetCurrent[NSRunLoop currentRunLoop]都可以为当前线程创建一个RunLoop,因为获取方法本身的过程中,没有它会自己创建。

  2. 向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环。

  3. 启动该RunLoop。

#import "MCObject.h"

@implementation MCObject

static NSThread *thread = nil;
// 标记是否要继续事件循环
static BOOL runAlways = YES;

+ (NSThread *)threadForDispatch
{
    if (thread == nil) {
        @synchronized(self) {
            if (thread == nil) {
                // 线程的创建
                thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
                [thread setName:@"com.imooc.thread"];
                //启动
                [thread start];
            }
        }
    }
    return thread;
}

+ (void)runRequest
{
    // 创建一个Source
    CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    
    // 创建RunLoop,同时向RunLoop的DefaultMode下面添加Source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    
    // 如果可以运行
    while (runAlways) {
        @autoreleasepool {//每次运行循环可以对资源进行一个释放
            // 令当前RunLoop运行在DefaultMode下面
            //运行的mode和上面添加source的mode要一致,不一致就会死循环
            //这个循环在RunLoop运行过程中会由用户态向核心态转换,当前线程会休眠,while就会暂停,所以不会死循环
            //第二个参数是运行时间,给个很大的数,代表遥远的未来
            //资源被处理是否立即返回,这里是立即返回true
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
        }
    }
    
    // 某一时机 静态变量runAlways = NO时 可以保证跳出RunLoop,线程退出
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}

总结

上一篇下一篇

猜你喜欢

热点阅读