RunLoop

2018-03-15  本文已影响19人  CoderHong

概述

iOS项目中RunLoop

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

UIApplicationMain函数内部开启了一个RunLoop。这个RunLoop是系统启动。跟我们的主线程相关联。也就是这个RunLoop处理主线程的各种事件。

RunLoop对象

NSRunLoop是基于CFRunLoop的一层OC包装。Core Foundation是纯C语言的。苹果开源RunLoop源码 官方文档

RunLoop与线程

image

子线程的RunLoop是懒加载的方式,默认没有创建。在子线程第一次获取时创建。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread start];
}

- (void)run
{
    NSRunLoop *rp = [NSRunLoop currentRunLoop];
    NSLog(@"--------------------");
}

获取RunLoop函数的源码

// 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);
    }
    // 1.线程作为Key去__CFRunLoops取对应的loop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    // 1.1 如果没有取到
    if (!loop) {
        // 1.1.1创建RunLoop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            // 1.1.2 key作为当前线程 Value作为RunLoop
            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相关的类

image.png

如果一个RunLoop想要运行起来,里面必须有一个Mode并且Model里面必须有一个Source或者Timer。否则该RunLoop直接退出。

CFRunLoopMode
CFRunLoopTimerRef

scheduledTimerWithTimeInterval 默认将Timer添加进RunLoop中并且对timer有强引用, 控制器引用timer使用weak。其实在Timer没有调用invalidate方法之前timer会强引用这控制器。控制器销毁了timer不一定销毁 。只有调用invalidateTimer才会销毁。

当我们拖动UIScrollView的时候,主线程关联的RunLoop进入`UITrackingRunLoopMode`模式。开发为了在满足两种模式下定时器都能够工作。做如下设置。
CFRunLoopSourceRef
CFRunLoopObserverRef
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
// @param  activities 需要监听RunLoop的哪些状态 kCFRunLoopAllActivities监听RunLoop的所有状态
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即将进入RunLoop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即将处理Timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即将处理Source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即将进入休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"刚从休眠中唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即将退出RunLoop");
                break;
            default:
                break;
        }
        
    });
    // 2.监听当前线程RunLoop的默认模式的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
RunLoop处理逻辑

RunLoop实践

Timer
ImageView的显示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 如果渲染很多图片 需要将显示图片放在default模式下显示 当拖拽ScrollView是不显示
    [self.imageview performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20180316_64"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
}
常驻线程
#import "ViewController.h"
#import <CoreFoundation/CoreFoundation.h>

@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    _thread = thread;
    [thread start];
    
}

// 常驻线程
- (void)run
{
    NSLog(@"--------------------run");
    // 创建子线程的runloop
    NSRunLoop *rp = [NSRunLoop currentRunLoop];
    // 添加Source
    [rp addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    // 运行RunLoop
    [rp run]; // 等价于 [rp runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

- (void)test
{
    NSLog(@"--------------------test");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//    [self performSelector:@selector(run) onThread:self.thread withObject:nil waitUntilDone:NO];
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
上一篇下一篇

猜你喜欢

热点阅读