iOS中关于RunLoop的一些认知

​​​​​​​

一、讲讲RunLoop,项目中有用到过吗?

https://github.com/cimain/CoreFoudation/blob/master/

RunLoop的源码地址可以从这个网站进行查看

1.什么是RunLoop

顾名思义,就是运行循环,在程序运行过程中循环做一些事情

2.应用范畴

1)定时器(Timer),PerformSelector

2)GCD Async Main Queue

3)事件响应、手势识别、界面刷新

4)网络请求

5)AutoreleasePool

3.Runloop的实现

int main(int argc,char * argv[]) {
   @autoreleasepool {
   return UIApplicationMain(argc,argv,nil,NSStringFormClass([AppDelegate class]));
   }
}

int main(int argc,char *argv[]) {
      @autoreleasepool {
        int reVal = 0; 
        do{
       //睡眠中等待消息
       int message = sleep_and_wait();
       //处理消息
       reVal = process_message(message);
      }while(reVal == 0)
    return 0;
   }

}

4.RunLoop的作用

程序并不会马上退出,而是保持运行的状态

1)保持程序的持续运行

2)处理App中的各种事件(比如触摸事件、定时器事件等)

3)节省CPU资源,提高程序性能:该做事时做事,该休息时休息

5.获取RunLoop对象

1)使用oc获取对象

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

获取主线程的RunLoop [NSRunLoop mainRunLoop]

2)使用C获取对象

CFRunLoopRef *runLoop = CFRunLoopGetCurrent();

获取主线程的RunLoop CFRunLoopGetMain()

6.RunLoop与线程

1)每条线程都有唯一的一个与之对应的RunLoop对象

2)RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value

3)线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建(主线程中UIApplication方法会获取到RunLoop对象,所以主线程默认是有RunLoop的)

4)RunLoop会在线程结束时销毁

5)主线程的RunLoop已经自动获取,子线程默认没有开启RunLoop

7.RunLoop中的类

Core Foundation中关于RunLoop的5个类

1)CFRunLoopRef

2)CFRunLoopModeRef

3)CFRunLoopSourceRef

4)CFRunLoopTimerRef

5)CFRunLoopObserverRef

8.CFRunLoopModeRef

1)CFRunLoopModeRef代表RunLoop的运行模式

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

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

4)如果需要切换mode,只能退出当前的mode,再重新选择一个mode进入,不同组的Sources0/Sources1/Timer/Observer能分隔开,互不影响

5)如果mode里没有任何Sources0/Sources1/Timer/Observer,RunLoop会立马退出

9.CFRunLoopModeRef

常用的模式有两种

1)kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App默认的mode,通常主线程是在这个mode下运行

2)UITrackRunLoopMode:界面跟踪Mode,用于ScrollView跟踪触摸滑动,保证界面滑动时不受其他mode影响

 NSLog(@"%p  %p",[NSRunLoop mainRunLoop],[NSRunLoop currentRunLoop]);
    NSLog(@"%p  %p",CFRunLoopGetCurrent(),CFRunLoopGetMain());

可以打印出这个对象的地址,但是发现通过C和OC获取到的地址不一样,其他OC是在C上的一层封装,底层还是C的

 NSLog(@"%@",[NSRunLoop mainRunLoop]);

 可以打印出来这个对象里面拥有的东西

切换mode不会导致程序退出

10.RunLoop的运行逻辑

使用bt可以打印出这个函数栈

1)Source0

a.处理触摸事件

b.performSelector : onThread

2)Source1

a.基于Port的线程间通信

b.系统事件捕捉

3)Timers

a.NSTimer

b.performSelector:withObject:afterDelay:

4)Observers

a.用于监听RunLoop的状态

b.UI刷新(BeforeWaiting)

c.Autorelease pool(BeforeWaiting)

运行逻辑:

1)通知Observers:进入RunLoop

2)通知Observers:即将处理Timers

3)通知Observers:即将处理Sources

4)处理Blocks

5)处理Source0(可能会再次处理Blocks)

6)如果存在Source1,就跳转到第8步

7)通知Observers,开始休眠(等待消息唤醒)

8)通知Observers:结束休眠(被某个消息唤醒)

a.处理timer

b.处理GCD、Async To Main Queue

c.处理Source1

9)处理Blocks

10)根据前面的执行结果,决定如何操作

a.回到第二步

b.退出loop

11)通知Observers,退出loop

 11.添加Observers监听RunLoop的所有状态


​​​​​​​

 

 12.RunLoop休眠的实现原理

内核层面的API

mach_msg

应用层面的API

 13.RunLoop在实际开发中的应用

1)控制线程的生命周期(线程保活)

2)解决NSTimer在滑动时停止工作的问题

3)监控应用卡顿

4)性能优化

二、RunLoop内部实现逻辑?

 

程序的入口函数是CFRunLoopRunSpecific

1.通知Observers进入Loop

 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

2.具体要做的事情

 //具体要做的事情
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

3.通知Observers,退出Loop

 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

CFRunLoopRun的具体实现

2)通知Observers,即将处理Timers

 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

3)通知Observers,即将处理Sources

__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

4)处理Blocks

__CFRunLoopDoBlocks(rl, rlm);

5)处理Sources0 如果返回值为Yes的话,再次处理Blocks

  Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
    }

6)处理有无Source1,如果有Source1,就进行跳转handle_msg

 //处理有无Source1
            msg = (mach_msg_header_t *)msg_buffer;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                //如果有Source1,就进行跳转handle_msg
                goto handle_msg;
            }

7)通知Observers,即将休眠

 if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    __CFRunLoopSetSleeping(rl);

8)通知Observers,结束休眠

__CFRunLoopUnsetSleeping(rl);

处理Timers

  //处理Timers
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }

处理GCD

 //处理GCD
 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

处理Source1

        //处理Source1
        sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;

9)再次处理Blocks

__CFRunLoopDoBlocks(rl, rlm);

10)根据前面的执行结果判断执行何种操作

三、RunLoop和线程的关系

1.每条线程都有唯一的一个与之对应的RunLoop对象

2.RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value

3.线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建(主线程中UIApplication方法会获取到RunLoop对象,所以主线程默认是有RunLoop的)

4.RunLoop会在线程结束时销毁

5.主线程的RunLoop已经自动获取,子线程默认没有开启RunLoop

四、timer和RunLoop的关系?

五、程序中添加每3秒响应一次的NSTimer,当拖动tableView时timer可能无法响应要怎么解决?

六、RunLoop是怎么响应用户操作的,具体流程是什么样的?

由Source1把系统事件给捕捉,Source1会将这个事件包装成事件队列,这个事件队列又在Source0里面

七、说说RunLoop的几种状态?

八、RunLoop的mode作用是什么?

1)kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App默认的mode,通常主线程是在这个mode下运行

2)UITrackRunLoopMode:界面跟踪Mode,用于ScrollView跟踪触摸滑动,保证界面滑动时不受其他mode影响

九、RunLoop的其他知识补充

1.RunLoop的概念

1)RunLoop是消息循环,每一个线程内部都有一个消息循环,一一对应,以键值对存储的,只有主线程的消息循环默认开启,子线程的消息循环默认不开启。

2)RunLoop是事件处理循环,类似于while,for,while(1)可以用来描述,但是不准确,因为while(1)是忙等状态,会消耗资源,而RunLoop没有消息的时候会休眠,有消息才会唤醒。

3)RunLoop实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件...),从而保持程序的持续运行

4)RunLoop在没有事件处理的时候,会使线程进入休眠状态,从而节省CPU资源,提高程序性能。

5)RunLoop与线程是息息相关的,我们知道线程的作用是用来执行特定的一个或者是多个任务,在默认情况下,线程执行完之后就会退出,就不能在执行任务了,这时我们就需要采取一种方式来让线程能够不断的处理任务,并不退出,所以我们有了RunLoop。

6)RunLoop并不能保证线程安全,我们只能在当前线程内部操作当前线程的RunLoop对象,而不能在当前线程内部去操作其他线程的RunLoop对象。

7)RunLoop对象在第一次获取RunLoop时创建,销毁是在线程结束的时候。

8)主线程的RunLoop对象系统自动帮助我们创建好了,而子线程的RunLoop对象需要我们主动创建和维护

2.RunLoop的作用

1)保证程序不退出

2)负责处理输入事件

3)如果没有事件发生,会让程序进入休眠状态

3.RunLoop可以做什么?

1)卡顿检测

2)线程保活

3)防止Crash

4)和线程交互的时候,使用RunLoop

4.线程保活

案例一:

在子线程中在创建子线程,实现方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"Hello!%@",[NSThread currentThread]);
        //这个函数注册了一个timer,在子线程
        [self performSelector:@selector(doSomething) withObject:nil afterDelay:0];
        NSLog(@"ZS%@",[NSThread currentThread]);
    });
}

- (void)doSomething {
    NSLog(@"Logic,%@",[NSThread currentThread]);
}

打印结果:

此时doSomething中的方法不执行,因为我们开启了一个异步线程,主线程的消息循环默认开启,可以执行,但是在子线程中添加任务,又开启了一个子线程,子线程的消息循环不开启,所以不执行 。

案例二:

线程保活

我们使用NSThread,或者GCD开启的线程会自动销毁,生命周期由系统来管理

创建一个按钮,点击按钮时执行下面的方法,自定义thread,让他继承自NSThread,然后重新delloc方法

- (IBAction)beginAction:(id)sender {
    thread *ts = [[thread alloc]initWithTarget:self selector:@selector(doThreadSomeThing) object:nil];
    [ts start];
}

- (void)doThreadSomeThing {
    NSLog(@"start");
    NSLog(@"%@",[NSThread currentThread]);
    [NSThread sleepForTimeInterval:2];
    NSLog(@"end");
}

此时的打印结果为:

 线程执行完毕之后,自动销毁。如果我们再进行点击会重新创建一个新的线程。(不会自动创建线程,会自动销毁线程)

猜你喜欢

转载自blog.csdn.net/qq_43658148/article/details/125320637