OC-run loop

17 篇文章 0 订阅

什么是run loop

运行回路
就是一个不停处理消息 如果有消息就醒来处理 消息就去休息的循环
可以这么理解 假设RunLoop就是一个对象 这个对象有需要处理的消息和事件 然后他提供了一个入口函数 参数是一个线程 他会放一个线程进来处理 会一直处在 “接受消息->等待->处理”的循环中 直到这个循环结束(比如quit消息)

作用

主要是为了保证程序持续运行和接受用户输入 并且处理各种事件

数据结构

CFRunLoop是NSRunLoop的上层封装 所以一般研究CFRunLoop
在这里插入图片描述
在这里插入图片描述

CFRunLoop是和Thread(线程)一一绑定的

NSRunLoopCommonModes本质是一个字符串 结构上好像类是于一个数组
NSRunLoopCommonModes 包含了NSDefaultRunLoopMode UITrackingRunLoopMode UIInitializationRunLoopMode
可以手动将自己的mode加入到NSRunLoopCommonModes中

RunLoop运行结构

在这里插入图片描述

调用栈上RunLoop流程

{
	//	1. 通知Observers,即将进入runloop
	//此处有Observer会创建AutoreleasePool:_objc_autoreleasePoolPush()
	__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
	do {
		//2. 通知Observers:即将触发Timer回调
		__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
		//3. 通知Observers:即将触发Source0回调
		__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
		
		//4.触发Source0回调
		__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
		
		//6. 通知Observers,即将进入休眠
		//此处Observer释放并新建autoreleasePool:_objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
		__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);

		//7. 休眠,等待唤醒
		mach_msg() -> mach_msg_trap();

		//8. 通知Observers,线程被唤醒
		__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);

		//9. 如果是Timer唤醒的,回调Timer
		__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
		//9. 如果是被dispatch唤醒的,执行所有调用dispatch_async等方法放入main queue 的block
		__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
		//9.如果runloop是被Source1(基于port)的事件唤醒,处理这个事件
		__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);


	} while (...);

	//10. 通知Observers,即将退出runloop
	//此处有Observer释放AutoreleasePool:_objc_autoreleasePoolPop();
	__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);

先根据modeName找到对应mode 然后通知observers:RunLoop即将会进入loop

CFRunLoop(一个结构体)

反正就是有一个RunLoop对象 然后一个进程会对应一个RunLoop对象 再往RunLoop里吗放timer什么

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* locked for accessing mode list */
    __CFPort _wakeUpPort;			// used for CFRunLoopWakeUp 通过这个来唤醒runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread; //该线程和RunLoop是一一对应的关系 线程的底层都是pthread_t
    uint32_t _winthread;
    CFMutableSetRef _commonModes; // 存放的commonModes集合
    CFMutableSetRef _commonModeItems;//timer source oberver作为item被存放在这里
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

CFRunLoopMode 这个和上面那个是不是一个东西?

在这里存放了_sources0和_sources1 ,_timers,_observers

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 */
};

应用从操作系统(窗口服务器)中接受鼠标点击等事件的消息,并将其转到相应的例行程序来处理,如此反复,这样的过程称为运行回路
比如说当button被点击的时候操作系统会发出Action消息 然后UIApplicaion类会根据这个消息选择对应的处理对象发送给他
主运行回路会在启动事件处理方法时生成一个自动释放池

RunLoop结构

一个RunLoop一般包含一个线程,若干个mode 若干个commonMode 和一个当前运行的mode mode包含source timer observer
每一次调用RunLoop主函数只能指定其中一个Mode 这个Mode就是CurrentMode 要切换Mode只能退出Loop 重新制定 目的是分割不同mode的source time oberver

CFRunLoopRef:代表了RunLoop的对象(RunLoop)
CFRunLoopModeRef:RunLoop的运行模式(Mode)
CFRunLoopSourceRef:RunLoop模型图中的输入源/事件源(Source)
CFRunLoopTimerRef:RunLoop模型图中的定时源(Timer)
CFRunLoopObserverRef:观察者,能够监听RunLoop的状态变化

在这里插入图片描述

Source

分为Source0和Source1
source1是基于端口的源 由内核自动发出信号 线程间通讯 系统事件捕获 例如屏幕点击
source0时定制源 必须从另一个线程手动发送信号 主要负责触摸事件 performSelecotr等
source0不具备主动唤醒能力 source0是可以创建API接口什么的 添加到当前runloop中
基于port端口事件 可以主动唤醒线程 并且source1是没有定义API接口供我们操作

Observer

RunLoop的状态变化,和UI刷新(休眠之前),autorelease(休眠之前)

NSRunLoop和CFRunLoopRef

CFRunLoop是NSRunLoop的上层封装

CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API 线程安全
NSRunLoop 是基于 CFRunLoopRef 的封装 线程不安全

线程

线程和RunLoop是一一对应的 这是由于会创建一个全局的Dictionary(static CFMutableDictionaryRef loopsDic) 用来存储RunLoop和thread
比如在通过_CFRunLoopGet获取RunLoop的时候 如果是第一次创建 也就是loopsDic是空的 没有 就会创建一个

loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);//然后将这个loop和主线程绑定

如果有 就会在这个dictionary根据thread作为key 去匹配 RunLoop

系统会自动开启主线程的RunLoop 保证程序不会提出
子线程的RunLoop需要手动开启

通过 pthread_main_thread_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程

dispatch_source

和超时有关
可以通过这个实现更为精准的定时器
定时器可以用来判断RunLoop是否超时

超时

如果超时的时间到了 就会唤醒

RunLoop如何处理主线程的事件

主线程的消息事件是存放在端口上 也就是mach_port_name_t dispatchPort
通过调用_CFRUNLOOP_IS_SERVIVING_THE_MAIN_DIDPATCH_QUEUE__读取并执行消息
call out 执行当前异步任务

RunLoop启动方法

三种启动RunLoop的方法

run,无条件
runUntilDate, 设置时间限制
runMode:before:Date:,在特定模式下
对于上面三种方法,文档中的总结如下

第一种方法,无条件地进入运行循环是最简单的选项,但也是最不理想的选择。无条件地运行runloop将线程放入永久循环,这使您无法控制运行循环本身。停止runloop的唯一方法是杀死它。也没有办法在自定义模式下运行循环。
第二种设置了超时时间,超过这个时间runloop结束,优于第一种
相对比较好的方式,可以指定runloop以哪种模式运行
实际上run方法的实现就是无限调用runMode:before:Date:方法
runUntilDate:也会重复调用runMode:before:Date:方法,区别在于它超时就不会再调用

RunLoop关闭方法

在处理事件之前,有两种方法可以让运行循环退出:

将运行循环配置为使用超时值运行。
手动停止。
这里需要注意,虽然删除runloop的输入源、定时器可能会导致运行循环的退出,但这并不是个可靠的方法,系统可能会添加一些输入源到runloop中,但在我们的代码中可能并不知道这些输入源,因此无法删除它们,导致无法退出runloop。

我们可以通过2、3方法来启动runloop,设置超时时间。但是如果需要对这个线程和它的RunLoop有最精确的控制,而并不是依赖超时机制,这时我们可以通过 CFRunLoopStop() 方法来手动结束一个 RunLoop。
但是 CFRunLoopStop() 方法只会结束当前的runMode:beforeDate: 调用,而不会结束后续的调用

在下面的代码中,因为runMode:beforeDate:方法是单次调用,我们需要给它加上一个循环,否则调用一次runloop就结束了,和不使用runloop的效果一样

这个循环的条件默认设置成yes,当调用stop方法中,执行CFRunLoopStop() 方法结束本次runMode:beforeDate:,同时将循环中的条件设置为NO,使循环停止,runloop退出。

#import "SecondViewController.h"
#import "FTThread.h"


@interface SecondViewController ()

@property (nonatomic, strong) FTThread *thread;  //继承NSThread
@property (nonatomic, assign) BOOL stopped;

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    
    self.view.backgroundColor = [UIColor greenColor];
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:button];
    [button addTarget:self action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"执行任务" forState:UIControlStateNormal];
    button.frame = CGRectMake(100, 200, 100, 20);
    
    
    UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [self.view addSubview:stopButton];
    [stopButton addTarget:self action:@selector(pressStop) forControlEvents:UIControlEventTouchUpInside];
    [stopButton setTitle:@"停止RunLoop" forState:UIControlStateNormal];
    stopButton.frame = CGRectMake(100, 400, 100, 20);
    
    self.stopped = NO;
    //防止循环引用
    __weak typeof(self) weakSelf = self;
    
    self.thread = [[FTThread alloc] initWithBlock:^{
        NSLog(@"ft新线程");
        
        //向当前runloop添加Modeitem,添加timer、observer都可以。因为如果mode没有item,runloop就会退出
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (!weakSelf.stopped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"end");
        
    }];
    
    
    [self.thread start];
    
}

- (void)pressPrint {
    //子线程中调用print
    [self performSelector:@selector(print) onThread:_thread withObject:nil waitUntilDone:NO];
}

//子线程需要执行的任务
- (void)print {
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}

- (void)pressStop {
    
    //子线程中调用stop
    if (_stopped == NO ) {
        [self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];
    }
    
}

//停止子线程的runloop
- (void)stop {
	//设置标记yes
    self.stopped = YES;
    
    //停止runloop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s, %@", __func__, [NSThread currentThread]);
    
    //解除引用, 停止runloop这个子线程就会dealloc
    self.thread = nil;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //退出当前页面
    
    //保证这个vc销毁时,子线程也要销毁
    [self pressStop];
    [self dismissViewControllerAnimated:YES completion:nil];
    
     
}

- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end

参考原文链接:https://blog.csdn.net/qq_45836906/article/details/119578794

方法

//OC方法
NSRunLoop *cRunloop = [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象 此时会创建RunLoop
NSRunLoop *mRunloop = [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
[mRunloop run];

//C语言方法
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

UIApplicationMain() //会开启主线程的runloop
RunLoopRun()//调用这个后 线程会一直停留在循环里
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值