Runloop面试相关及基础应用

147 阅读6分钟

Runloop 运行循环 Runloop 本质是什么? 本质是一个OC对象,内部也有isa指针。

Runloop和线程是什么关系? 一对一的关系,每条线程都有唯一的一个RunLoop对象与之对应的,主线程的RunLoop是自动启动,子线程的RunLoop需要手动启动,获得RunLoop对象后要调用run方法来启动一个运行循环 [[NSRunLoop currentRunLoop] run]; // 第一个参数:指定运行模式 // 第二个参数:指定RunLoop的过期时间,即:到了这个时间后RunLoop就失效了 [[NSRunLoop currentRunLoop] runMode:kCFRunLoopDefaultMode beforeDate:[NSDate distantFuture]];

RunLoop是来管理线程的,当线程的RunLoop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。RunLoop在第一次获取时被创建,在线程结束时被销毁。RunLoop的核心是什么? 就是它如何在没有消息处理时休眠,在有消息时又能唤醒。这样可以提高CPU资源使用

Runloop的底层数据结构是什么样的?

  1. CFRunLoopRef
  2. CFRunLoopModeRef --> 运行模式
  3. CFRunLoopSourceRef -- >事件处理: 点击 、PerformSelector(触摸事件)
  4. CFRunLoopTimeRef --> 定时器
  5. CFRunLoopObserverRef --> 监听器

一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立刻退出 2. RunLoop常见的几有三种运行模式Mode: 3. kCFRunLoopDefaultMode、UITrackingRunLoopMode、kCFRunLoopCommonModes 4. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行 5. UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响 6. kCFRunLoopCommonModes:默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode;并不是一个真的模式,它只是一个标记,如:被标记的 Timer可以在kCFRunLoopDefaultMode模式和UITrackingRunLoopMode模式下运行。 7. UIInitializationRunLoopMode:私有的mode,App启动的时候的状态,加载出第一个页面后,就转成了Default,基本用不到的Mode GSEventReceiveRunLoopMode接受系统事件的内部 Mode,通常用不到 CFRunLoopSource是RunLoop的数据源抽象类,类似Objective-C中的协议protocol,实现了这个protocol就可以充当RunLoop的数据源(几乎没有这么做的),RunLoop自己定义了两个Source:Source0和Source1。 Source0: 处理App内部事件; 比如: - 屏幕响应UIEvent, CFSocket(套接字),我们点击屏幕(touchesBegan:withEvent:)就是Source0事件

  • 开发者主动实现的线程间通信: performSelector:onThread: aSelector: 方法名 onThread: 需要访问的线程 withObject: 实现SEL方法时传递的参数 waitUntilDone:
  • 设置为YES: 表示需要等当前方法选择器中的方法执行完再执行下面的代码,防止还没停止当前实例对象就先释放掉了。- 设置为NO:表示不需要等当前方法选择器中的方法执行完,可以继续执行下面的代码 modes: 一个字符串数组,它标识允许在其中执行指定选择器的模式。此数组必须包含至少一个字符串。如果为该参数指定nil或空数组,则此方法将在不执行指定的选择器的情况下返回。 //给某个线程发送消息 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array

//给主线程发消息 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array

Source1: 由内核管理,比如: 基于mach_port端口的线程或进程间通信、系统事件捕捉 事件是通过Source1来捕捉,然后再分发到Source0来处理的 注意:mach_port是iOS系统中进程间通信的一种方式,如果进程1往一个port中发送一个消息,此时进程2监听了这个port,就会拿到这个消息 NSPort是对CoreFoundation中的CFMachPort和CFMessagePort的封装。

Timers 该方法是拿到RunLoop并且把Timer添加到RunLoop里面,但不会启动RunLoop;

  • (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
  • (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

CFRunLoopObserver 相当于观察者模式的观察者,用来向观察者报告RunLoop当前的状态 · 用于监听RunLoop的状态 · UI刷新(BeforeWaiting) · Autorelease pool(BeforeWaiting)自动释放池 AutoreleasePool在RunLoop的两次sleep之间对AutoreleasePool进行pop和push,将这次loop中产生的Autorelease对象进行释放

Runloop 的监听状态有哪几种? /* Run Loop Observer Activities */typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

  1. kCFRunLoopEntry = (1UL << 0),                 // 即将进入Loop
    
  2. kCFRunLoopBeforeTimers = (1UL << 1),          // 即将处理Timer
    
  3. kCFRunLoopBeforeSources = (1UL << 2),         // 即将处理Source
    
  4. kCFRunLoopBeforeWaiting = (1UL << 5),         // 即将进入休眠
    
  5. kCFRunLoopAfterWaiting = (1UL << 6),          // 刚从休眠中唤醒
    
  6. kCFRunLoopExit = (1UL << 7),                  // 即将退出Loop
    
  7. kCFRunLoopAllActivities = 0x0FFFFFFFU         // 所有状态};
    

Runloop 有哪些应用? 1、保证线程长时间存活, 不希望一些花费时间较长的操作阻塞主线程而导致界面卡顿,就需要创建一个子线程,然后把该操作放在子线程中来可是当子线程中的任务执行完毕后,子线程就会被销毁。处理。如果程序中需要经常在子线程中执行任务,频繁的创建和销毁线程会造成资源的浪费。这时可以使用RunLoop来让该线程长时间存活而不被销毁。 2、 YYKit中使用YYWebImageOperation对网络图片进行下请求使用[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];将任务丢到后台线程的 RunLoop中。

  • (void)_networkThreadMain:(id)object { @autoreleasepool { [[NSThread currentThread] setName:@"com.ibireme.yykit.webimage.request"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; }}

RunLoop如何保证NSTimer在视图滑动时依然能正常运转。 1、如果是在主线程中运行timer,想要timer在某界面有视图滚动时依然能正常运转,那么将timer添加到RunLoop中时,就需要设置mode 为NSRunLoopCommonModes

如果是在子线程中运行timer,那么将timer添加到RunLoop中后,Mode设置为NSDefaultRunLoopMode或NSRunLoopCommonModes均可,但是需要保证RunLoop在运行,且其中有任务。 三、RunLoop如何保证不影响UI卡顿 问题描述:UITableView、UICollectionView等延迟加载图片。 以UITableView 的 cell 上显示网络图片为例,需要两步:1、下载网络图片;2、将网络图片设置到UIImageView上。为了不影响滑动第1步一般都是放在子线程处理,第2步回到主线程设置。model切换调用方法performSelector:withObject:afterDelay:inModes:,如下(方法2): UIImage *downloadedImage = ....;// 方法1// self.myImageView.image = downloadedImage;// 方法2[self.myImageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]]; 一个Cell里有两个Label,和三个imageView,这里的图片是非常高清的。 1、方法1:为imageView设置image,是在UITrackingRunLoopMode中进行的,如果图片很大,图片解压缩和渲染肯定会很耗时,那么卡顿就是必然的。 2、方法2: 切换到NSDefaultRunLoopMode中,一个runloop循环要解压和渲染18张大图(假如一个页面能显示6行,每行3张图),耗时肯定超过50ms(1/60s)。我们可以继续来优化,一次runloop循环,仅渲染一张大图片,分18次来渲染,这样每一次runloop耗时就比较短了,滑动起来就会非常顺畅。这也是 RunLoopWorkDistribution 中的做法,即:首先创建一个单例,单例中定义了几个数组,用来存要在runloop循环中执行的任务,然后为主线程的runloop添加一个CFRunLoopObserver,当主线程在NSDefaultRunLoopMode中执行完任务,即将睡眠前,执行一个单例中保存的一次图片渲染任务。关键代码看 RunLoopWorkDistribution 类即可。Tableview滑动优化,SDWebImage+Runloop,图片延迟加载,滑动不加载图片