iOS 开发 Objective-C iOS底层

iOS 底层 day18 RunLoop 执行流程 NSTime

2020-09-13  本文已影响0人  望穿秋水小作坊

一、RunLoop 的运行逻辑

1. Source0Source1TimersObservers 的作用?
大概了解一下,有个印象就行
2. RunLoop 的运行逻辑
RunLoop 的运行逻辑图(了解即可)
3. 一句话概括上面的流程图?
4. RunLoop 休眠的实现原理?它和代码 while(1); 这种死循环有什么区别?

二、RunLoop 和 NSTimer

1. 为什么默认情况下 NSTimer ,在用户拖拽滚动的时候会停止调用?
2. 如何解决默认情况下 NSTimer ,在用户拖拽滚动的时候会停止调用?
RunLoop结构体

二、RunLoop 用于线程保活

1. 什么是线程保活?
2. 如何用NSThread创建一个一般线程(创建→执行代码→执行完成销毁)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s", __func__);
    NSThread *thread = [[YYThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
    [thread start];
}

- (void)threadStart {
    NSLog(@"%s", __func__);
}
3. 如果我们希望上面的线程一直存活要怎么办?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s", __func__);
    NSThread *thread = [[YYThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
    [thread start];
}

- (void)threadStart {
    NSLog(@"%s", __func__);
    while (1);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s", __func__);
    NSThread *thread = [[YYThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
    [thread start];
}

- (void)threadStart {
    NSLog(@"---------start-------- %s", __func__);
    // 调用获取 currentRunLoop 就会让线程自动创建 RunLoop
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"---------end-------- %s", __func__);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s", __func__);
    NSThread *thread = [[YYThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
    [thread start];
}

- (void)threadStart {
    NSLog(@"---------start-------- %s", __func__);
    // 调用获取 currentRunLoop 就会让线程自动创建 RunLoop
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"---------end-------- %s", __func__);
}
5. 思考下面的代码,当控制器退出销毁时,线程会被销毁吗?
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[YYThread alloc] initWithBlock:^{
        NSLog(@"---------start-------- %s", __func__);
        // 调用获取 currentRunLoop 就会让线程自动创建 RunLoop
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
        NSLog(@"---------end-------- %s", __func__);
    }];;
    [self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s", __func__);
}

- (void)stop {
    [self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone:NO];
    NSLog(@"%s", __func__);
}

- (void)stopRunLoop {
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s", __func__);
}
- (void)dealloc {
    NSLog(@"%s", __func__);
    [self stop];
    self.thread = nil;
}
@end

If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, 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.

6. 思考下面的代码,touchesBegan:withEvent:被调用时,线程会被销毁吗?
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.stop = NO;
    __weak typeof(self) weakSelf = self;
    self.thread = [[YYThread alloc] initWithBlock:^{
        NSLog(@"---------start-------- %s", __func__);
        // 调用获取 currentRunLoop 就会让线程自动创建 RunLoop
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.isStop) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        NSLog(@"---------end-------- %s", __func__);
    }];;
    [self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    [self performSelector:@selector(doSoming) onThread:self.thread withObject:nil waitUntilDone:NO];
    self.stop = YES;
    [self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone:YES];
    self.thread = nil;
}

- (void)doSoming {
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)stopRunLoop {
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end
- (void)dealloc {
    NSLog(@"%s", __func__);
    self.stop = YES;
    [self performSelector:@selector(stopRunLoop) onThread:self.thread withObject:nil waitUntilDone:YES];
    self.thread = nil;
}

三、RunLoop 线程保活 -- 代码封装

我们可以看上,如果上面的线程保活的功能,我们需要在多处调用,那么将会很麻烦,每个要用到的地方都需要添加很多相关代码。基于此,我们可以把它封装成一个迷你工具库

1. 思考,如果我们把上面的线程保活功能封装成一个对象,这个对象应该继承自谁呢?继承自NSThread ? NSThread的分类 ? 继承自NSObject ?
2. 封装代码
// 线程保活.h文件代码
#import <Foundation/Foundation.h>

typedef void (^permanentThreadTask)(void);


@interface YYPermanentThread : NSObject
- (void)run;

- (void)stop;

- (void)executeTask:(permanentThreadTask)task;
@end

// 线程保活.m文件代码
#import "YYPermanentThread.h"

/* 这个对象主要用于监测 NSThread 的释放 */
@interface YYThread : NSThread
@end
@implementation YYThread
- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end

/* 我们封装的线程保活工具 */

@interface YYPermanentThread ()

@property(nonatomic, strong) NSThread *thread;
@property(nonatomic, assign, getter=isStopped) BOOL stopped;
@end
@implementation YYPermanentThread

- (instancetype)init
{
    self = [super init];
    if (self) {
        __weak typeof(self) weakSelf = self;
        self.thread = [[YYThread alloc] initWithBlock:^{
            NSLog(@"-------RunLoop start-------");
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            while (weakSelf && !weakSelf.isStopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
            NSLog(@"-------RunLoop end-------");
        }];
    }
    return self;
}

- (void)run {
    if (self.thread == nil) return;
    [self.thread start];
}

- (void)stop {
    if (self.thread == nil) return;
    [self performSelector:@selector(__stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}


- (void)executeTask:(permanentThreadTask)task {
    if (self.thread == nil || task == nil) return;
    [self performSelector:@selector(__executeTask:) onThread:self.thread withObject:task waitUntilDone:NO];
}

#pragma mark - private method

- (void) __stopThread {
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.thread = nil;
}

- (void) __executeTask:(permanentThreadTask)task {
    task();
}

- (void)dealloc {
    if (self.thread != nil){
        [self stop];
    }
    NSLog(@"%s", __func__);
}

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.permanentThread = [[YYPermanentThread alloc] init];
    [self.permanentThread run];
    
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.permanentThread executeTask:^{
        NSLog(@"我在子线程干活---%@",[NSThread currentThread]);
    }];;
}
@end
3. 我们也可以基于C语言的 RunLoop 来封装,把上面 - (instancetype)init 里面的代码,换成如下代码即可。
- (instancetype)init
{
    self = [super init];
    if (self) {
        __weak typeof(self) weakSelf = self;
        self.thread = [[YYThread alloc] initWithBlock:^{
            NSLog(@"-------RunLoop start-------");
            CFRunLoopSourceContext context = {0};
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            while (weakSelf && !weakSelf.isStopped) {
                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10000, NO);
            }
            NSLog(@"-------RunLoop end-------");
        }];
    }
    return self;
}

四、线程卡顿检测

1、如何打印堆栈信息?

#import <execinfo.h>
- (void)logStack {
    void* callstack[256];
    int frames = backtrace(callstack, 256);
    char **strs = backtrace_symbols(callstack, frames);
    _backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (int i = 0; i < frames; i++) {
        [_backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    NSLog(@"====================堆栈\n %@ \n",_backtrace);
}

2、如何检测卡顿?

#import <Foundation/Foundation.h>

@interface SeMonitorController : NSObject
+ (instancetype) sharedInstance;
- (void) startMonitor;
- (void) endMonitor;
- (void) printLogTrace;
@end
#import "SeMonitorController.h"
#import <libkern/OSAtomic.h>
#import <execinfo.h>

@interface SeMonitorController(){
    CFRunLoopObserverRef _observer;
    dispatch_semaphore_t _semaphore;
    CFRunLoopActivity _activity;
    NSInteger _countTime;
    NSMutableArray *_backtrace;
}

@end

@implementation SeMonitorController

+ (instancetype) sharedInstance{
    static dispatch_once_t once;
    static id sharedInstance;
    dispatch_once(&once, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

- (void) startMonitor{
    [self registerObserver];
}

- (void) endMonitor{
    if (!_observer) {
        return;
    }
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    CFRelease(_observer);
    _observer = NULL;
}

- (void) printLogTrace{
    NSLog(@"====================堆栈\n %@ \n",_backtrace);
}

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    SeMonitorController *instrance = [SeMonitorController sharedInstance];
    instrance->_activity = activity;
    // 发送信号
    dispatch_semaphore_t semaphore = instrance->_semaphore;
    dispatch_semaphore_signal(semaphore);
}

- (void)registerObserver
{
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    _observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                            kCFRunLoopAllActivities,
                                                            YES,
                                                            0,
                                                            &runLoopObserverCallBack,
                                                            &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    
    // 创建信号
    _semaphore = dispatch_semaphore_create(0);
    
    // 在子线程监控时长
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES)
        {
            // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms)
            long st = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
            if (st != 0)
            {
                if (_activity==kCFRunLoopBeforeSources || _activity==kCFRunLoopAfterWaiting)
                {
                    if (++_countTime < 5)
                        continue;
                    [self logStack];
                    NSLog(@"something lag");
                }
            }
            _countTime = 0;
        }
    });
}

- (void)logStack{
    void* callstack[128];
    int frames = backtrace(callstack, 128);
    char **strs = backtrace_symbols(callstack, frames);
    int i;
    _backtrace = [NSMutableArray arrayWithCapacity:frames];
    for ( i = 0 ; i < frames ; i++ ){
        [_backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
}

@end
上一篇 下一篇

猜你喜欢

热点阅读