码迷,mamicode.com
首页 > 其他好文 > 详细

RunLoop

时间:2016-05-06 14:50:31      阅读:149      评论:0      收藏:0      [点我收藏+]

标签:

一、RunLoop的基本概念

RunLoop用中文来翻译就是“跑圈”,“运行循环”。
技术分享

RunLoop的基本作用:

  • 保持程序的运行
  • 处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
  • 节省CPU资源,提高程序性能:该做事的时候做事,该休息的时候休息
  • ……

如果没有RunLoop:
技术分享
程序运行后的现象:刚点击运行,就立刻结束了!

如果有了RunLoop:
技术分享
有了RunLoop后,整个程序启动后并不会马上退出,而是保持持续运行状态!

但是真实情况下的main函数长这样:
技术分享
其实在执行UIApplicationMain函数时,其内部就启动了一个RunLoop,而且该函数一直没有返回,保持了程序的持续运行!当我们点击UIApplicationMain函数里面去,会发现该函数是有返回值(int类型),只不过一直没有返回而已,接下来可以测试下!

int main(int argc, char * argv[]) {
    @autoreleasepool {

        NSLog(@"begin---");

        int result = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

        NSLog(@"end---");

        return result;
    }
}

当我们运行程序后,打印如下:
技术分享

这就表明,确实是在UIApplicationMain函数中执行了RunLoop,而且这个默认启动的RunLoop是跟主线程相关联的!

二、RunLoop对象

在iOS系统中,提供了两个对象:NSRunLoop和CFRunLoopRef。

  • NSRunLoop属于Foundation框架,基于CFRunLoopRef的一层OC包装,提供了面向对象的API,但这些API不是线程安全的
  • CFRunLoopRef是在Core Foundation内部的,它提供了纯C函数的API,这些API都是线程安全的

因此如果想深入学习RunLoop,就需要多了解CFRunLoopRef层面的API(Core Foundation),可以参考:
官方源代码
官方文档

三、RunLoop与线程的关系

1> 每条线程都有唯一的与之对应的RunLoop对象
在RunLoop源代码中,有这样的一个函数:

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) 

这个函数的作用就是:传一条线程来创建一个RunLoop对象,这就对应了上面的这句话

2> 主线程的Runloop已经自动创建好了,子线程的RunLoop需要主动创建

苹果不允许直接创建RunLoop,它只提供了两个自动获取的函数:
Foundation:

[NSRunLoop mainRunLoop];
[NSRunLoop currentRunLoop];

Core Foundation:

CFRunLoopGetMain();
CFRunLoopGetCurrent();

3> RunLoop在第一次获取时创建,在线程结束时销毁

以下是源代码的解读:

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { // 这个函数就是传一条线程进来,就创建一个RunLoop
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    // 判断有没有RunLoop对象
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        // 创建可变Dictionary
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // 获取主线程对应的RunLoop对象
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // 把mainLoop放到Dictionary中,key就是主线程(也就是说在访问其他线程的时候,它会首先把主线程的RunLoop创建好)
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // 根据传进来的线程获取对应的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);

    // 如果没有RunLoop,就创建
    if (!loop) {
        // 根据传进来的线程创建一个新的RunLoop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        // 把newLoop存进Dictionary
        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和线程是一一对应的,其关系会保存在一个Dictionary中。线程刚创建的时候并没有RunLoop,因为它是懒加载的,需要主动获取(在子线程内部通过currentRunLoop获取),第一次获取当前线程对应的RunLoop时就会创建,以后再不会创建!

四、RunLoop的相关类

Core Foundation中关于RunLoop的5个类:

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

如果RunLoop中没有上面的几个类,RunLoop就会马上结束了。
这些类的关系如下:
技术分享
一个RunLoop包含很多Mode,Mode中又包含很多Source、Observer、Timer

CFRunLoopModeRef
CFRunLoopModeRef代表着RunLoop的运行模式。
技术分享
在获取RunLoop后还需要传入一种运行模式,让其跑起来。每次RunLoop启动时,需要指定其中一种Mode,这个Mode被称为CurrentMode,从上面的图看出,一个RunLoop有很多种模式。如果需要切换Mode,只能退出Loop,然后再重新指定一个Mode进入,这样做的目的就是为了分隔开不同组的Source/Observer/Timer,让其互不影响。

系统默认注册了5个Mode:

kCFRunLoopDefaultMode:App的默认Mode,通常主线程都是在这个Mode下运行

UITrackingRunLoopDefaultMode:界面跟踪Mode,用于scrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响,这样做可以使其更加流畅

UIInitializationRunLoopMode:在刚启动App时的第一个Mode,启动完成后就不再使用

GSEventReceiveRunLoopMode:接受系统内部事件的Mode,通常用不到

NSRunLoopCommonMode:这是一个占位用的Mode,不是一种真正的Mode(也就是说启动的时候,是不可以传这种模式的)

CFRunLoopTimerRef

它是基于时间的触发器(时间到了就触发一个事件),基本上就是我们平常所知道的NSTimer。它包含一个时间长度和一个回调(函数指针),当加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调

实用场景案例:
现在屏幕上有一个可滚动的UITextView,此时我们假如我们需要添加一个定时器来执行一些其他操作

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES];

其实这句代码做了很多事情,那就是首先创建一个NSTimer,添加到Mode中,接着RunLoop启动Mode,取出里面的Timer拿出来用。等价代码如下:

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

这时每过2秒就会回调一次doSomeThing方法,但是当我们滚动UITextView时,doSomeThing方法就不再会执行了。因为该NSTimer是添加到NSDefaultRunLoopMode中的,而滚动UITextView是属于UITrackingRunLoopDefaultMode。那么如果想在defaultMode和TrackingMode下都能执行该怎么办呢?

当我们点进头文件,苹果只给我们提供了两种Mode:NSDefaultRunLoopMode/NSRunLoopCommonModes,并没有TrackingMode。因此得用到NSRunLoopCommonMode了,这就表示定时器会跑在标记为common modes的模式下。在我们打印currentRunLoop时:
技术分享
带有common modes的Mode有两个,分别是kCFRunLoopDefaultMode和UITrackingRunLoopDefaultMode,这就表明这两种Mode下,定时器都可以用。

CFRunLoopSourceRef

它是事件源(输入源),意味着一些点击事件、触摸事件都是由它来处理的。

按照函数调用栈,scorce的分类:

  • Source0:非基于Port的
  • Source1:基于Port的,通过内核和其他线程通信,接受、分发系统事件

至于什么是函数栈:

技术分享

比如触发一个点击事件,会按照上图所示,从下到上依次执行函数。很明显,里面就有Source0这个函数,说明点击事件在Source0中进行。虽然函数栈中并没有Source1,但是当触摸屏幕时会被包装成一个Event,首先会被Source1接受,然后把这个事件分发到Source0。

CFRunLoopObserverRef

它是观察者,能够监听RunLoop状态的改变。每一个Observer都包含着一个回调,当RunLoop状态发生改变的时候,Observer就会通过回调接受这个变化。

// 添加observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { // 这里就可以监听状态的改变,然后做出一些操作

    });

// 添加观察着,监听RunLoop的状态
 CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

// 释放observer
CFRelease(observer);

RunLoop的状态有以下几个时间点:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // 1,即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1), // 2,即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 4,即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 32,即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 64,从休眠中被唤醒
    kCFRunLoopExit = (1UL << 7), // 128,退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU // 包括以上所有状态
};

五、RunLoop的内部实现逻辑

一幅图很好的说明了这个实现逻辑的过程
技术分享

内部主要就是一个do-while循环,直到超时或者手动停止才会结束循环,该函数才会有返回值。

六、RunLoop实践案例

1> imageView延迟显示
当我们在滚动tableView时,就会从网络下载图片进行展示,但是图片显示的过程中会有渲染的问题,导致tableView拖动非常卡,这个时候就可以让imageView延迟显示。

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"zly"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

也就是让imageView的显示只在NSDefaultRunLoopMode模式下才能进行,所以拖动tableView时是处于TrackingMode!

2> 常驻线程(让线程不死)
想要让当前线程不死,其实就可以用到RunLoop,在当前线程中开启一个RunLoop,但是需要注意的是此时的RunLoop的Mode中并没有Mode item,所以RunLoop就会立即退出,因此得在里面添加Mode item。

@autoreleasepool {
        // 添加Source
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

        // run
        [[NSRunLoop currentRunLoop] run];
    }

RunLoop

标签:

原文地址:http://blog.csdn.net/ldszw/article/details/51321280

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!