iOS RunLoop 基本概念以及使用场景

xiaoxiao2021-02-27  421

一、RunLoop概念: 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出。 实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。 所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。 二、RunLoop与线程的关系 首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_thread_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。 注意:苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 这两个函数内部的逻辑大概是下面这样 RunLoop 的核心就是一个 mach_msg() ,RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。例如你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方。 自动释放池就是根据runloop的原理实现的 回到开始的疑问,为什么要使用RunLoop,一般情况下我们是没必要去启动线程的RunLoop,除非需要在一个单独的线程长久的检测某个事件,类似微信的语音功能,见一个RunLoop专门负责监听说话的线程。看需求而定了。 1.每条线程都有唯一的一个与之对应的RunLoop对象 2.主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建 3.RunLoop在第一次获取时创建,在线程结束时销毁 4.获取RunLoop对象: 1)Foundation: [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象 [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象 2)Core Foundation: CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象 CFRunLoopGetMain(); // 获得主线程的RunLoop对象 三、RunLoop使用场景 苹果官方文档说明run loop的开启是运用在需要和线程有更多交互的场合上的。 四、概念 1)Foundation 框架          ——>NSRunLoop 2)Core Foundation         ——>CFRunLoopRef NSRunLoopCFRunLoopRef都代表着RunLoop对象 NSRunLoop是基于CFRunLoopRef的一层OC包装, 所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API (Core Foundation 层面) 五、RunLoop相关类  CoreFoundation中关于RunLoop的5个类  1)CFRunLoopRef           ——>NSRunLoop是基于这个类进行封装的  2)CFRunLoopModeRef  ——>代表RunLoop的运行模式 一个 RunLoop包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响 系统默认注册了5个Mode: kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行 UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode 影响 UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用 GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到 kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode  3)CFRunLoopSourceRef  4)CFRunLoopTimerRef  5)CFRunLoopObserverRef 六、RunLoop应用  1.NSTimer  2.ImageView显示  3.PerformSelector  4.常驻线程  5.自动释放池 #import "ViewController.h" @interface ViewController () @property (nonatomic, strong) dispatch_source_t timer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // // NSRunLoop 主线程对应的RunLoop对象 // NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop]; // NSLog(@"mainRunLoop = %@", mainRunLoop); // // NSRunLoop 获得当前方法所在线程对应的RunLoop // NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; // NSLog(@"currentRunLoop = %@", currentRunLoop); // // CFRunLoopRef 主线程对应的RunLoop对象 // CFRunLoopRef cfMainRunLoop = CFRunLoopGetMain(); // NSLog(@"cfMainRunLoop = %@", cfMainRunLoop); // // CFRunLoopRef 获得当前方法所在线程对应的RunLoop // CFRunLoopRef cfCurrentRunLoop = CFRunLoopGetCurrent(); // NSLog(@"cfCurrentRunLoop = %@", cfCurrentRunLoop); // // 开启一条子线程 // NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; // [thread start]; // 创建Observer /** * 参数1: 指定如果给Observer分配存储空间 * 参数2: 需要监听的状态类 * kCFRunLoopEntry = (1UL << 0), 即将启动(进入)的时候 * kCFRunLoopBeforeTimers = (1UL << 1), 即将处理timer事件 * kCFRunLoopBeforeSources = (1UL << 2), 即将处理source事件 * kCFRunLoopBeforeWaiting = (1UL << 5), 即将进入睡眠 * kCFRunLoopAfterWaiting = (1UL << 6), RunLoop被唤醒 * kCFRunLoopExit = (1UL << 7), RunLoop退出 * kCFRunLoopAllActivities = 0x0FFFFFFFU 监听所有状态 * */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"即将进入RunLoop"); break; case kCFRunLoopBeforeTimers: NSLog(@"即将处理timer"); break; case kCFRunLoopBeforeSources: NSLog(@"即将处理source"); break; case kCFRunLoopBeforeWaiting: NSLog(@"即将进入睡眠"); break; case kCFRunLoopAfterWaiting: NSLog(@"RunLoop刚从睡眠中唤醒"); break; case kCFRunLoopExit: NSLog(@"RunLoop即将退出"); break; default: break; } }); // 给主线程的RunLoop添加一个观察者,要监听的是RunLoop的哪种运行模式 /** * 参数1: 需要给哪个RunLoop添加观察者 * 参数2: 需要添加的Observer对象 * 参数3: 在哪种模式下可以监听 kCFRunLoopDefaultMode == NSDefaultRunLoopMode */ CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode); [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES]; } - (void)show{ NSLog(@"-------------%s", __func__); } - (void)run { // 注意: 如果想给子线程添加RunLoop, 不能直接alloc init // [[NSRunLoop alloc] init]; // 错误 // 只要调用currentRunLoop方法, 系统就会自动创建一个RunLoop, 添加到当前线程中 [NSRunLoop currentRunLoop]; // 这个方法是懒加载 }
转载请注明原文地址: https://www.6miu.com/read-1164.html

最新回复(0)