美文网首页@IT·互联网
说到NSRunLoop我们要说点什么

说到NSRunLoop我们要说点什么

作者: 黑炭长 | 来源:发表于2024-03-05 21:14 被阅读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当前为 空闲状态,才开始下载或是显示图片,以免显示图片造成页面卡顿

    相关文章

      网友评论

        本文标题:说到NSRunLoop我们要说点什么

        本文链接:https://www.haomeiwen.com/subject/fuudzdtx.html