@IT·互联网

说到NSRunLoop我们要说点什么

2024-03-05  本文已影响0人  黑炭长

目前正是找工作、换工作的黄金时间,对于iOS开发从业者来说 面试绕不过去的就是runLoop,那么我们说到runLoop我们应该说些什么呢
答题从以下几方面入手
1、runLoop是什么
2、runLoop的作用是什么
3、runLoop和线程的关系
4、runLoop之Modes
5、modes之items(source0,source1,timer、observer)
6、runLoop的应用

1、runLoop是什么

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* 获取modeList的锁 */
    __CFPort _wakeUpPort;            // 唤醒runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData;
    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;
};

首先runloop是一个运行循环,它实际上也是一个对象、这个对象提供了一个入口函数,使程序进入一个do..while的循环中,循环处理各种任务

2、runLoop的作用是什么

1)由于是运行循环,他保持程序持续的运行,即便没有待处理的任务 也不退出程序(可利用这点防止程序崩溃)
2)处理App中的各种事件 包括触摸、滑动、performSelector等
3)节省cpu资源,提高程序的性能,使cpu该做事做事,该休息休息

3、runLoop和线程的关系

1)线程和runLoop是一一对应的关系,
2)主线程runloop程序启动后自动创建,子线程默认不创建runloop,直到在子线程中第一次获取runloop时,才创建runloop,同时runloop存放在一个可变字典中 字典的key-value 分别为pthread-runloop

4、runLoop之Modes

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; // timer开启后为true
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
}

每个runloop包含多个mode,但是 同时一个runloop只能在一中mode下运行,runloop在运行时,是在不断切换mode的,mode的分类

kCFRunLoopDefaultMode:APP的默认mode 通常主线程是在这个model下运行
UITrackingRunLoopMode: 界面跟踪mode,用于scrollview追踪触摸滑动,保证页面滑动时,不受其他mode的影响
UIInitializationRunLoopMode:当app刚启动时,进入的第一个mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
GSEventReceiveRunLoopMode:接收系统事件的内部mode,通常用不到
kCFRunLoopCommonModes: 这是一个占位mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode,用并不是一种真正的model,也可以认为是一种混合模式

5、modes之items(source0,source1,timer、observer)

一个mode持有的source、timer、observer,都是集合类型所以说每个model,可以持有多个source、timer、observer

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;            /* 不可变 */
    CFMutableBagRef _runLoops;
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};
struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;        /* 不可变 */
    CFTimeInterval _tolerance;          /* 可变 */
    uint64_t _fireTSR;            
    CFIndex _order;            
    CFRunLoopTimerCallBack _callout;    
    CFRunLoopTimerContext _context;    
};

运行的时候 会将相应的item添加到对应的mode中,即调用CFSetAddValue方法,之后再在运行循环时会调用CFRunLoopDoBlocks方法,判断当前model后,执行相应的block回调

CFRunLoopSourceRef: 包括source0 和 source1
source0,处理app内部事件,app自己负责管理,uiEvent,CFSocket,仅包含一个函数指针
底层调用 创建source0源,将source0加入当runloop相应的mode中、执行signal信号,标记待执行,执行CFRunLoopWakeUp唤醒runloop,处理相应的事件,取消移除源CFRunLoopRemoveSource
source1,包含一个mach_port和一个回调指针,一般用于通过内核和其他线程进行通讯
CFRunLoopTimerRef:timer的底层是一个CFRunLoopTimerRef,这个timer是受runloop的mode模式影响的,
创建CFRunLoopTimerRef 添加到相应的当前runloop,若是子线程的runloop 需要调用CFRunLoopRun自行开启,否则 timer是不会调用的
CFRunLoopObserverRef:观察者,观察runloop所处的状态
初始化CFRunLoopObserverContext,
创建 CFRunLoopObserverRef,配置回调方法
添加观察者当当前的runloop

6、runLoop的应用

1)线程保活

[[NSRunLoop currentRunLoop] addPort:[NSort port] forMode:NSDefaultRunLoopMode];
while (self.keepAlive) {
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
}

退出线程时

self.keepAlive = NO;
CFRunLoopStop(CFRunLoopGetCurrent());

2)主线程卡顿检测
在主线程穿件观察者,在子线程中观察observer的状态,
主线程的操作是 在 kCFRunLoopBeforeSource 和 kCFRunLoopBeforeWaiting分别记录时间,判断时间差 超过某一个阈值即认为发生了卡顿,此时可以获取堆栈信息,并记录上传,以供分析

@interface Minitor ()

@property(nonatomic, strong) NSThread *monitorThread;

@property(nonatomic, strong) NSDate *startDate;

/// 是否正在执行任务
@property(nonatomic, assign, getter=isExcuting) BOOL excuting;

@end

@implementation Minitor {
    
    CFRunLoopObserverRef _observer;
    CFRunLoopTimerRef _timer;
    
}

+ (instancetype)shareInstatance {
    
    static Minitor *instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[Minitor alloc] init];
        instance.monitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(moniterThreadEntryPoint) object:nil];
        [instance.monitorThread start];
    });
    
    return  instance;
}

+ (void)moniterThreadEntryPoint {
    
    @autoreleasepool {
        [[NSThread currentThread] setName:@"Monitor"];
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runloop run];
    }
    
}

- (void)statrMonitor {
    
    if (_observer) {
        NSLog(@"已经创建了监听");
        return;
    }
    CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
    _observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    
    [self performSelector:@selector(addTimerToMonitorThread) onThread:self.monitorThread withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
    
}


static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    
    Minitor *monitor = (__bridge  Minitor*)info;
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            monitor.startDate = [NSDate date];
            monitor.excuting = YES;
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            monitor.excuting = NO;
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
            
        default:
            break;
    }
    
}



#pragma mark 定时器

- (void)addTimerToMonitorThread {
    if (_timer) {
        return;
    }
    CFRunLoopRef curentRunLoop = CFRunLoopGetCurrent();
    CFRunLoopTimerContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
    _timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.01, 0.01, 0, 0, &runloopTimerCallBack, &context);
    
    CFRunLoopAddTimer(curentRunLoop, _timer, kCFRunLoopCommonModes);
    
}

static void runloopTimerCallBack(CFRunLoopTimerRef timer, void *info) {
    
    Minitor *monitor = (__bridge Minitor*)info;
    if (!monitor.isExcuting) {
//        kCFRunLoopBeforeWaiting
        return;
    }
    NSTimeInterval excuteTime = [[NSDate date] timeIntervalSinceDate:monitor.startDate];
    
    NSLog(@"定时器:当前线程%@,主线程执行时间:%f秒", [NSThread currentThread], excuteTime);
    
    if (excuteTime >= 0.00001) {
        NSLog(@"卡顿了 %f  秒", excuteTime);
        [monitor handleStackInfo];
    }
    
}

- (void)handleStackInfo {
    
    NSArray *callStackSymbls = [NSThread callStackSymbols];
    [callStackSymbls enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@", obj);
    }];
    
}

@end

3)给程序赋予一次回光返照的机会
参开链接传送门
4)大型列表 加载图片时,即图片下载或是显示 根据runloop当前为 空闲状态,才开始下载或是显示图片,以免显示图片造成页面卡顿

上一篇下一篇

猜你喜欢

热点阅读