RunLoop

RunLoop基础

运行循环,在程序运行过程中循环做一些事情,具体实现如图所示

RunLoop.png
应用范畴

定时器(Timer)、PerformSelector、GCD Async Main Queue、事件响应、手势识别、界面刷新、网络请求、AutoreleasePool

RunLoop的基本作用,保持程序的持续运行,处理App中的各种事件(比如触摸事件、定时器事件等),节省CPU资源,提高程序性能:该做事时做事,该休息时休息

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

内部实现伪代码

RunLoop对象

iOS中有2套API来访问和使用RunLoop:

  • Foundation:NSRunLoop
  • Core Foundation:CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层OC包装,CFRunLoopRef是开源的地址

RunLoop与线程

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
  • RunLoop会在线程结束时销毁
  • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
两个参数 runloop(字典) 和 pthread(key),
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
    __CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

如果没有 runloop则新生成一个

if (!loop) {
    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
    loop = newLoop;
}
获取RunLoop对象

Foundation

  • [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
  • [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

Core Foundation

  • CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
  • CFRunLoopGetMain(); // 获得主线程的RunLoop对象

RunLoop相关的类

Core Foundation中关于RunLoop的5个类

CFRunLoopRef

CFRunLoopModeRef

  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef
CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的运行模式

一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer

RunLoop.png

RunLoop启动时只能选择其中一个Mode,作为currentMode

如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响

如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

常见的2种Mode

  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
RunLoop结构体
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;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
RunLoopMode结构体
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    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 */
};
具体功能模块

Source0

  • 触摸事件处理
  • performSelector:onThread:

Source1

  • 基于Port的线程间通信
  • 系统事件捕捉(捕捉之后包装给Source0去做处理)

Timers

  • NSTimer
  • performSelector:withObject:afterDelay:

Observers

  • 用于监听RunLoop的状态
  • UI刷新(BeforeWaiting)
  • Autorelease pool(BeforeWaiting)
Observers状态
switch (activity) {
    case kCFRunLoopEntry:
         //即将进入RunLoop
        break;
    case kCFRunLoopBeforeTimers:
         //即将处理Timer
        break;
    case kCFRunLoopBeforeSources:
         //即将处理Sources
        break;
    case kCFRunLoopBeforeWaiting:
         //即将进入休眠
        break;
    case kCFRunLoopAfterWaiting:
         //刚从睡眠中唤醒
        break;
    case kCFRunLoopExit:
         //即将退出RunLoop
        break;
    default:
        break;
}
RunLoop的模式切换
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    switch (activity) {
        case kCFRunLoopEntry: {
            CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
            NSLog(@"kCFRunLoopEntry - %@", mode);
            CFRelease(mode);
            break;
        }
            
        case kCFRunLoopExit: {
            CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
            NSLog(@"kCFRunLoopExit - %@", mode);
            CFRelease(mode);
            break;
        }
            
        default:
            break;
    }
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);

休眠过程中并不阻塞主线程,内部使用的是内核态 mach_msg 方法

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
//     NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
//     NSRunLoopCommonModes并不是一个真的模式,它只是一个标记,在runLoop中的_commonModes存储
//     timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

scheduledTimerWithTimeInterval方法内部会管理一个RunLoop所以可以直接执行,但是其他的方法,只是返回了一个timer,需要手动添加RunLoop,启动和销毁

RunLoop的运行逻辑
RunLoop的运行逻辑.png
RunLoop的运行逻辑.png
RunLoop休眠的实现原理

从用户态到内核态就进入了休眠


RunLoop休眠.png
线程保活
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    
    self.stopped = NO;
    self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        
        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        //加上循环是为了避免只执行一次,判断条件就是哪一次要停止,由外面控制,停止的时候就不会再runMode了,相当于继续往下走
        while (weakSelf && !weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"%@----end----", [NSThread currentThread]);
        
        // NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
        //        [[NSRunLoop currentRunLoop] run];
        //所以要想停止RunLoop,开始的时候要调用这个方法:  
        // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        /*
         it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:.
         In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers
         */
        
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (!self.thread) return;
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (IBAction)stop {
    if (!self.thread) return;
    // 在子线程调用stop
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 用于停止子线程的RunLoop
- (void)stopThread {
    // 设置标记为YES
    self.stopped = YES;
    
    // 停止RunLoop,不能写在dealloc里面,因为是要停止子线程的RunLoop,不是主线程的RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    
    // 清空线程
    self.thread = nil;
    
    NSLog(@"%s %@", __func__, [NSThread currentThread]); 
}

- (void)dealloc {
    NSLog(@"%s", __func__);

    //主要原因是 [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
    //这里waitUntilDone:NO意思是不等子线程处理完,就直接返回了,这个时候stop执行完毕,dealloc也执行完了,销毁了self
    //这个时候self已经为nil了,然后子线程里面的调用就是坏内存了,只有把waitUntilDone:YES
    // 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
    [self stop]; 
}
C语言线程保活
- (instancetype)init
{
    if (self = [super init]) {
        self.innerThread = [[MJThread alloc] initWithBlock:^{
            NSLog(@"begin----");
            
            // 创建上下文(要初始化一下结构体)
            CFRunLoopSourceContext context = {0};
            
            // 创建source
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            
            // 往Runloop中添加source
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            
            // 销毁source
            CFRelease(source);
            
            // 启动
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
            
//            while (weakSelf && !weakSelf.isStopped) {
//                第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
//                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
//            }
            
            NSLog(@"end----");
        }];
        
        [self.innerThread start];
    }
    return self;
}

你可能感兴趣的:(RunLoop)