美文网首页
RunLoop随笔

RunLoop随笔

作者: 三国韩信 | 来源:发表于2020-06-23 16:53 被阅读0次

    每条线程都有唯一的一个与之对应的RunLoop对象。
    RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value。
    线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建。
    RunLoop会在线程结束时销毁。
    主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop。

    /*
      从runLoop底层的源码也可以看出,线程和runloop是一一对应的,且是保存
      在一个全局的CFDictionary中的,且第一次获取不到runloop会去创建它。
    */
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        if (!loop) {
            CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
            loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            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);
        }
    
    // 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;
    };
    
    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    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 */
    };
    

    由此可见,我们的RunLoop中主要的结构就2个成员变量_currentMode、_modes。
    struct __CFRunLoop {
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    }
    而_currentMode中的结构主要就是_sources0、_sources1、_observers、_time。简单的如下图所示:


    runloop.png

    CFRunLoopModeRef代表RunLoop的运行模式
    一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0、Source1、Timer、Observer。
    RunLoop启动时只能选择其中一个Mode,作为currentMode。
    如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入。
    不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响。
    如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。

    目前已知的Mode有5种:

    • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行。
    • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
    • UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
    • GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
    • kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode。
    处理流程.png
    例子1:那么来看看下面这道题,运行结果会是啥?打印1、3、2?错,结果是只打印1、3,2不会被打印,也就是说performSelector没有执行,那是为啥呢?
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
             NSLog(@"1");
             [self performSelector:@selector(test) withObject:nil afterDelay:2];
             NSLog(@"3");
    });
    
    -(void)test{
      NSLog(@"2");
    }
    

    那要看一下performSelector:withObject:afterDelay的本质是干了啥。performSelector:withObject:afterDelay:其实就是在内部创建了一个NSTimer,然后会添加到当前线程的Runloop中。然鹅我们通过dispatch_async开辟的子线程,Runloop默认是没有开启的,所以它不会被执行。故,我们要手动的开启Runloop就行了。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
             NSLog(@"1");
             [self performSelector:@selector(test) withObject:nil afterDelay:2];
             [[NSRunLoop currentRunLoop] run];   // 开启子线程的NSRunLoop。
             NSLog(@"3");
    });
    

    例子2:对应着上面的RunLoop的流程,那么我们可以自己来控制一个线程的生命周期么(自己控制线程的死活)?offcourse,show code:

    #import "ViewController1.h"
    #import "MyThread.h"
    
    @interface ViewController1 ()
    @property(nonatomic,strong)MyThread* myThread;
    @property(nonatomic,assign)BOOL stop;
    @end
    
    @implementation ViewController1
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self createUI];
        __weak typeof(self) weakSelf = self;
        self.myThread = [[MyThread alloc]initWithBlock:^{
          /*
            让当前的子线程一直活下去,必须要开启该子线程的RunLoop,
           且给runloop添加一个source/timer/port 等。不然线程执行一次就会自动销毁了。
          */
            NSLog(@"----thread---begin");
            [[NSRunLoop currentRunLoop]addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
            while (weakSelf && !weakSelf.stop) {
                [[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
            NSLog(@"----thread---end");
        }];
        [self.myThread start];
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{   
    // 点击屏幕,让当前子线程做事情
        if (!self.myThread) return;
        [self performSelector:@selector(threadDoSomeThing) onThread:self.myThread withObject:nil waitUntilDone:NO];
    }
    
    -(void)threadDoSomeThing{    // 当前子线程做的事情
        NSLog(@"-------threadDoSomeThing-------%@",[NSThread currentThread]);
    }
    
    -(void)stopThread{   //点击页面按钮的时候去执行停止线程和RunLoop的函数
        if (!self.myThread) return;
        [self performSelector:@selector(stopMyThread) onThread:self.myThread withObject:nil waitUntilDone:YES];
    }
    
    -(void)stopMyThread{   // 停止线程和RunLoop,并把thread置为nil;
        self.stop = YES;
        CFRunLoopStop(CFRunLoopGetCurrent());
        self.myThread = nil;
    }
    
    -(void)dealloc{   // VC销毁的时候,线程和RunLoop也要停止和销毁
        [self stopThread];
        NSLog(@"ViewController1 dealloc");
    }
    
    -(void)createUI{  // 创建页面UI,页面上有个按钮,用于停止线程和RunLoop的
        self.title = @"我的线程";
        self.view.backgroundColor = [UIColor systemPinkColor];
        UIButton* btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 200, 59, 30)];
        [btn setTitle:@"停止" forState:UIControlStateNormal];
        [btn addTarget: self action:@selector(stopThread) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:btn];
    }
    

    运行效果如下:


    运行效果.gif

    相关文章

      网友评论

          本文标题:RunLoop随笔

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