https://www.jianshu.com/p/de752066d0ad
一、基本概念
1、runloop是什么? ———— O
用一个字来形容runloop的话,runloop就是————圈。
或者说是英文字母——O。
这样的形象比喻,想要说明的是runloop的特性——runloop是一个事件循环对象。
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
do {
//接受消息->等待->处理
}while(message != quit)
线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。
RunLoop 对外的接口
在 CoreFoundation 里面关于 RunLoop 有5个类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
Runloop 入门 https://www.cnblogs.com/baxiu/p/7884194.html
经常会有人在面试中被问到:NStimer准吗?谈谈你的看法?如果不准该怎样实现一个精确的NSTimer? 实际上面试官是在看你懂不懂Runloop.
这时候你该这样回答:
1,无论是单次执行的NSTimer
还是重复执行的NSTimer
都不是准时的
2这与当前NSTimer
所处的线程有很大的关系,如果NSTimer
当前所处的线程正在进行大数据处理(假设为一个大循环),NSTimer
本次执行会等到这个大数据处理完毕之后才会继续执行。这期间有可能会错过很多次NSTimer
的循环周期,但是NSTimer
并不会将前面错过的执行次数在后面都执行一遍,而是继续执行后面的循环,也就是在一个循环周期内只会执行一次循环。
所以我们要习惯把NSTimer放在Runloop中去执行, [NSRunLoop currentRunLoop];//获取当前线程的Runloop [NSRunLoop mainRunLoop];//获取主线程的Runloop 见下图
https://juejin.im/post/5c36e369f265da61682b92bb
常见的面试题:
Runloop
和线程是什么关系?
每条线程都有唯一的一个与之对应的RunLoop对象,其关系是保存在一个全局的 Dictionary 里;主线程的RunLoop已经自动创建,子线程的RunLoop需要主动创建;RunLoop在第一次获取时创建,在线程结束时销毁
Runloop
的mode作用是什么?
指定事件在运行循环中的优先级的,
线程的运行需要不同的模式,去响应各种不同的事件,去处理不同情境模式。(比如可以优化tableview的时候可以设置UITrackingRunLoopMode下不进行一些操作,比如设置图片等。)
以+scheduledTimerWithTimeInterval:
的方式触发的timer
,在滑动页面上的列表时,timer
会暂停回调, 为什么?
滑动scrollView时,主线程的
RunLoop
会切换到UITrackingRunLoopMode
这个Mode,执行的也是UITrackingRunLoopMode
下的任务(Mode中的item),而timer是添加在NSDefaultRunLoopMode
下的,所以timer任务并不会执行,只有当UITrackingRunLoopMode
的任务执行完毕,runloop切换到NSDefaultRunLoopMode
后,才会继续执行timer。
如何解决在滑动页面上的列表时,timer会暂停回调?
将
Timer
放到NSRunLoopCommonModes
中执行即可
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
复制代码
NSTImer
使用时需要注意什么?
- 注意timer添加到runloop时应该设置为什么mode
- 注意timer在不需要时,一定要调用invalidate方法使定时器失效,否则得不到释放
RunLoop
有哪些应用?
常驻内存、AutoreleasePool 自动释放池
AutoreleasePool
和 RunLoop
有什么联系?
iOS应用启动后会注册两个 Observer 管理和维护 AutoreleasePool。应用程序刚刚启动时默认注册了很多个Observer,其中有两个Observer的 callout 都是 _ wrapRunLoopWithAutoreleasePoolHandler,这两个是和自动释放池相关的两个监听。
-
第一个 Observer 会监听 RunLoop 的进入,它会回调objc_autoreleasePoolPush() 向当前的 AutoreleasePoolPage 增加一个哨兵对象标志创建自动释放池。这个 Observer 的 order 是 -2147483647 优先级最高,确保发生在所有回调操作之前。
-
第二个 Observer 会监听 RunLoop 的进入休眠和即将退出 RunLoop 两种状态,在即将进入休眠时会调用 objc_autoreleasePoolPop() 和 objc_autoreleasePoolPush() 根据情况从最新加入的对象一直往前清理直到遇到哨兵对象。而在即将退出 RunLoop 时会调用objc_autoreleasePoolPop() 释放自动自动释放池内对象。这个Observer 的 order 是 2147483647 ,优先级最低,确保发生在所有回调操作之后。
NSRunLoop 和 CFRunLoopRef 区别
CFRunLoopRef 基于C 线程安全,NSRunLoop 基于 CFRunLoopRef 面向对象的API 是不安全的
https://juejin.im/post/593f77085c497d006ba389f0#heading-22
Runtime
01 / objc在向一个对象发送消息时,发生了什么? |
参考1:根据对象的 isa 指针找到类对象 id,在查询类对象里面的 methodLists 方法函数列表,如果没有在好到,在沿着 superClass ,寻找父类,再在父类 methodLists 方法列表里面查询,最终找到 SEL ,根据 id 和 SEL 确认 IMP(指针函数),在发送消息; |
02 / 问题:什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步? |
参考1:当发送消息的时候,我们会根据类里面的 methodLists 列表去查询我们要动用的SEL,当查询不到的时候,我们会一直沿着父类查询,当最终查询不到的时候我们会报 unrecognized selector 错误,当系统查询不到方法的时候,会调用 +(BOOL)resolveInstanceMethod:(SEL)sel 动态解释的方法来给我一次机会来添加,调用不到的方法。或者我们可以再次使用 -(id)forwardingTargetForSelector:(SEL)aSelector 重定向的方法来告诉系统,该调用什么方法,一来保证不会崩溃。 |
03 / 问题:能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么? |
参考1:1、不能向编译后得到的类增加实例变量 2、能向运行时创建的类中添加实例变量。 |
分析:1. 编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,runtime会调用 class_setvarlayout 或 class_setWeaklvarLayout 来处理strong weak 引用.所以不能向存在的类中添加实例变量。2. 运行时创建的类是可以添加实例变量,调用class_addIvar函数. 但是的在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上. |
04 / 问题:runtime如何实现weak变量的自动置nil? |
参考1:runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。 |
05 / 问题:给类添加一个属性后,在类结构体里哪些元素会发生变化? |
参考1:instance_size :实例的内存大小;objc_ivar_list *ivars:属性列表 |
RunLoop
01 / 问题:runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢? |
参考1:runloop: 从字面意思看:运行循环、跑圈,其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)事件。runloop和线程的关系:一个线程对应一个RunLoop,主线程的RunLoop默认创建并启动,子线程的RunLoop需手动创建且手动启动(调用run方法)。RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop。 |
02 / 问题:runloop的mode是用来做什么的?有几种mode? |
参考1:model:是runloop里面的运行模式,不同的模式下的runloop处理的事件和消息有一定的差别。系统默认注册了5个Mode:(1)kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。(2)UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。(3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。(4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。(5)kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。注意iOS 对以上5中model进行了封装 NSDefaultRunLoopMode、NSRunLoopCommonModes |
03 / 问题:为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了? |
参考1:nstime对象是在 NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes. |
04 / 问题:苹果是如何实现Autorelease Pool的? |
参考1:Autorelease Pool作用:缓存池,可以避免我们经常写relase的一种方式。其实就是延迟release,将创建的对象,添加到最近的autoreleasePool中,等到autoreleasePool作用域结束的时候,会将里面所有的对象的引用计数器 - autorelease. |