iOS--RunLoop知识点总结(1)

83 篇文章 1 订阅
80 篇文章 0 订阅

RunLoop是多线程的难点. 在实际开发中我们如何使用RunLoop呢? 

先浏览一下RunLoop知识点的大致框架, 这也是本文即将要说明的:


                             RunLoop知识点的大致框架

RunLoop的概念和作用

RunLoop被称为运行循环, 你可以把RunLoop理解为一个死循环, 看一下CFRunLoop的源码就知道了:

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;    do {        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

在这里我还是要推荐下我自己建的iOS开发学习群:680565220,群里都是学ios开发的,如果你正在学习ios ,小编欢迎你加入,今天分享的这个案例已经上传到群文件,大家都是软件开发党,不定期分享干货(只有iOS软件开发相关的),包括我自己整理的一份2018最新的iOS进阶资料和高级开发教程

它的基本作用是:

  • 保持程序的持续运行(保证程序不退出)

  • 处理App中的各种事件(触摸事件, 定时器事件, Selector事件)

但这个死循环是一个很特殊的死循环, 它能够在该做事情的时候做事情, 没事情做的时候休息待命, 以节省CPU资源, 提高程序性能.

在应用程序的入口main函数中, 如下所示

int main(int argc, char * argv[]) {    @autoreleasepool {        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
有一个UIApplicationMain(,,_,_)函数, 这个函数内部就启动了一个RunLoop运行循环

UIApplicationMain(,,_,_)函数是有一个int类型的返回值的, 但是一直不会返回,保持程序的持续运行, 这个默认启动的RunLoop运行循环是跟主线程相关联的.

获取RunLoop对象

苹果官方文档告诉我们:

Both Cocoa and Core Foundation provide run loop objects to help you configure and manage your thread’s run loop

  • Cocoa框架: NSRunLoop

  • Core Foundation框架:CFRunLoopRef
    NSRunLoop的底层其实还是调用的CFRunLoopRef的API, 是对其进行了OC层面上的封装

线程和RunLoop的关系
  • 一条线程对应一个RunLoop,是一一对应的关系

  • 主线程的RunLoop是默认存在的,已经创建好了, 子线程的RunLoop需要手动创建

  • RunLoop的生命周期: 在第一次获取得时候创建, 线程结束时销毁

获取RunLoop对象
  • Cocoa框架: [NSRunLoop currentRunLoop](获得当前线程的RunLoop对象)
    [NSRunLoop mainRunLoop](获得主线程的RunLoop对象)

  • Core Foundation框架:CFRunLoopGetCurrent()(获得当前线程的RunLoop对象)
    CFRunLoopGetMain()(获得主线程的RunLoop对象)

我们来看一下CFRunLoop的源码:
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);
    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)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }    return loop;
}

上面的代码告诉我们: 线程和RunLoop是一一对应的关系, 当线程是主线程时,会创建一个mainRunLoop,然后保存在字典中:

CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

当线程是子线程时, 会先看一下当前线程为key的对应的Value中有没有RunLoop,如果没有, 会先创建一个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) {        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }

RunLoop的相关类

Core Foundation中关于RunLoop的关键类
  • CFRunLoopRef

  • CFRunLoopModeRef

  • CFRunLoopSourceRef

  • CFRunLoopTimerRef

  • CFRunLoopObserverRef

这5个类之间的关系可以用下图来表示:


                      RunLoop的相关类之间的关系

这个关系图其实就是说: 在一个RunLoop中可以存在两个或者两个以上的模式,但RunLoop每次只能选择一个模式运行, 就像空调有制冷和制热模式, 但是运行的时候只能选择制冷或者制热模式, 要保证运行循环RunLoop不退出, 每个模式里面至少存在一个Source或者 一个Timer,Observer可以有也可以没有, 只是监听RunLoop的运行状态.

RunLoop运行模式(一共有5种)
  • Default
    NSDefaultRunLoopMode (Cocoa)
    kCFRunLoopDefaultMode (Core Foundation)

  • Event tracking
    NSEventTrackingRunLoopMode (Cocoa)

  • Common modes
    NSRunLoopCommonModes (Cocoa)
    kCFRunLoopCommonModes (Core Foundation)

  • Connection
    NSConnectionReplyMode (Cocoa)

  • Modal
    NSModalPanelRunLoopMode (Cocoa)

后面两种运行模式就不做过多解读了, 我们需要关注的是前面三种模式

  • 默认模式

- (void)timer1 {    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {        NSLog(@"run1----------%@",[NSRunLoop currentRunLoop].currentMode);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

当定时器以默认模式被添加到运行循环中时, 如果发生例如scrollView拖拽事件等, 运行循环将有默认模式自动切换到事件追踪模式, 这时候, 默认模式中的定时器就暂时不工作了, 当拖拽事件结束之后, 运行循环中的运行模式又由追踪模式切换到默认模式.

  • 事件追踪模式

- (void)timer2 {    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {        NSLog(@"run2----------%@",[NSRunLoop currentRunLoop].currentMode);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
}

跟上面的模式刚好相反, 如果发生例如scrollView拖拽事件等, 事件追踪模式中的定时器就开始工作, 当拖拽事件结束之后, 运行循环中的运行模式又由事件追踪模式切换到默认模式, 那么在事件追踪模式中的定时器就不工作了.

那么问题来了, 如果需要定时器不管添加到哪种模式下都正常工作应该怎么做呢? 有一种方法是, 同时把定时器添加到两种运行模式中, 不过这种方法太笨了, 正确的做法是:

  • 通用模式

- (void)timer3 {    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {        NSLog(@"run3----------%@",[NSRunLoop currentRunLoop].currentMode);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

这种模式其实不是真正的一种运行模式, 而是将两种模式都打上CommonModes的标签,那么就相当于timer可以在只要是带有CommonModes标签的运行模式下工作.

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block类似于这种scheduledTimer开头的方法, 是自动将定时器添加到默认的运行循环模式了, 如果要添加到事件追踪模式和通用模式, 还是需要手动添加.

如果我在子线程创建了定时器, 那么需要创建子线程中的runLoop对象, 然后把timer添加到子线程的runLoop中:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [NSThread detachNewThreadWithBlock:^{        NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {            NSLog(@"run2----------%@",[NSRunLoop currentRunLoop].currentMode);
        }];        NSLog(@"%@",[NSThread currentThread]);        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        [currentRunLoop addTimer:timer forMode:NSRunLoopCommonModes];
        [currentRunLoop run];
    }];
}

需要记住的是:

  • 创建子线程的RunLoop直接调用[NSRunLoop currentRunLoop];, 这个方法是懒加载的

  • 一定要让子线程的runLoop跑起来, 不然的话, 子线程一结束, 运行循环立马销毁, 即时添加了定时器也没个X用. [currentRunLoop run];这句代码不能少!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值