1. 对runtime理解
Runtime 的四个重要的概念——动态类型,动态绑定,动态方法决议,内省,
动态类型,对象的具体类型在运行时才能确定。
动态绑定,指把消息映射到方法实现的这一过程是在运行时,而不是在编译时完成的。
动态方法决议,是一种能动态的提供方法实现的能力
自省,在运行时检查对象自身信息的能力
应用场景也很多,交换方法、添加属性 objc_set/getAssociatedObject、字典转模型、自动归档和解档等
2. RunLoop的理解
通过管理 内部循环 来 接收和处理 App运行期间 消息事件的 一个对象
1线程的保活、
2处理App中的各种事件(如触摸事件、定时器事件等) 3节省CPU资源,提高程序性能
要处理以下6类事
1、RunLoopObserver 触发
2、消息通知、非延迟 的perform、dispatch调用、block回调、KVO 4、TIMER_CALLBACK - 延迟的perform, 延迟dispatch调用
3、MAIN_DISPATCH_QUEUE - 主调度队列
5、Source0 - 处理App内部事件、App自己负责管理(触发)
,如UIEvent、CFSocket。普通函数调用,系统调用 6、Source1 - 由RunLoop和内核管理,Mach port驱动, 如CFMachPort、CFMessagePort
3.kvo 的底层实现
实际上是采用了 isa——swilling 的方法。
-
利用
Runtime API动态生成一个子类,并且让instance对象的 isa 指向这个全新的子类。并重写了被观察属性的 setter 方法。 -
当修改 instance 对象的属性时,会调用 Foundation 的
_NSSetXXXValueAndNotify函数willChangeValueForKey: 父类原来的setter didChangeValueForKey: -
内部会触发监听器(Oberser)的监听方法
observeValueForKeyPath:ofObject:change:context:。
4. weak的原理
weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址数组.
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针.
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表
3、释放时,调用clearDeallocating函数。根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
5. NSObject对象他占用多少内存。对于64位平台的32位就不考虑了,占用多少内存, 一个指针占用多少字节?
NSObject其内部只有_ Class isa; _Class定义 struct objc_class *Class 可知 isa 就是个指针,所以NSObject *obj = [[NSObject alloc]init];此句代码就相当于分配一块内存存放上边那个结构体,结构体内就只有一个isa指针,指针的地址赋值给obj。一个NSObject对象占用的大小其实就是一个isa指针的大小。在64bit是8字节(32bit是4)__。 但是!!系统真正分配内存的时候是分配了16字节! 系统是分配了16字节给NSObject对象,真正使用的空间其实是一个指针的大小。
@interface Person : NSObject { @public int _no; int _age; } _no的值和_age的值赋值的话各占4个字节。加上_isa指针的8字节,_刚好占满16个字节,对象就没有在开辟新的空间了
@interface Person : NSObject { @public int _no;; },实际上person对象确实只使用了12个字节。但是因为内存对齐的原因。使person对象也占用16个字节。
6. 自己制作的一个静态库,平台库里面包括一些类别文件。引用时他一般会报错嘛,就是说那个找不到方法
Xcode中配置"Build Settings"中的“Other Linker Flags”选项添加“-all_load“
7. MVC 的一些缺点
- 厚重的View Controller
model应包括数据和操作数据的业务逻辑
在实践中,model层往往非常薄
V:视图view通常是UIKit控件或者编码定义的UIKit控件的集合。View的如何构建(PS:IB或者手写界面)何必让Controller知晓,同时View不应该直接引用model(PS:现实中,你懂的!),并且仅仅通过IBAction事件引用controller。业务逻辑很明显不归入view,视图本身没有任何业务。 C:控制器controller。Controller是app的“胶水代码”:协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的loading、appearing、disappearing等等,同时往往也会充满我们不愿暴露的model的模型逻辑以及不愿暴露给视图的业务逻辑。网络数据的请求及后续处理,本地数据库操作,以及一些带有工具性质辅助方法都加大了Massive View Controller的产生。
-
遗失(无处安放)的网络逻辑
苹果使用的MVC的定义是这么说的:所有的对象都可以被归类为一个model,一个view,或是一个controller。
试着把它放在Model对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个_网络请求比持有它的model生命周期更_长,事情将变的复杂。显然View里面做网络请求那就更格格不入了,因此只剩下Controller了。若这样,这又加剧了Massive View Controller的问题。 -
较差的可测试性
由于 C混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。
8. 消息补救的流程
第一步、动态方法解析 通过调用+resolve(Instance/Class)Method 这两个方法,来提供一个函数实现,如果有添加,就会走消息重新发生的过程。如果没有走下一步
第二步、提供后备接受者对象(Fast Forwarding),如果实现了-forwardingTargetForSelector:,会将消息转发给其他对象的机会。只要不返回nil/self,整个消息发送的过程就会被重启。否则下一步
第三部、拿到方法签名,封装invocation更换其他target对象(Normal forwarding),最后一次机会。通过发送-methodSignatureForSelector:获得参数和返回值类型,如果返回函数签名,就会生成一个NSInvocation对象,并发送-forwardInvocation:消息给这个目标对象。
如果返回nil,会发送-doesNotRecognizeSelector:消息。程序挂掉
9. 那你再发使用方法交换的时候需要注意一些什么
我们在做方法替换的时候,最好是能按照继承链的顺序来执行,那么**initialize**和**load**都能达到这个效果;为什么选择load?
- _在子类没有实现
**initialize**时候,父类的**initialize**会执行多次,_假如在这里做替换就会出现偶数次替换,方法替换失效的问题; - 类别中实现了
**initialize**会覆盖类中的方法,如果有多个类别都在**initialize**中做处理的话,那么只有一个会生效其他都会失效,具体哪个生效看compile source中哪个在最后。
以上这两个副作用,load都没有,所以还是选择在load中处理,虽然load会很微弱的影响启动时间。
-
dispatch_once + load保证替换执行一次
-
load保证在继承关系中替换时,按照继承链来替换
-
方法替换时检查类中是否实现了原方法,避免子类中没有实现,替换子类的方法时,将父类的方法替换了
-
之所以选在
load方法中去实现, 是因为load在文件加载的时候就会被调用, 甚至早于main函数, 这样不会出现原方法被调用的时候, 还没交换的情况 -
用
dispatch_once_t就是保证这段代码只执行一次3. 交换方法里要调一下自己
- (void)xx_viewWillAppear:(BOOL)animated { //这里调用自己不会造成死循环, 看似是调用自己, 实则调用的是`viewWillAppear:`的实现 [self xx_viewWillAppear:animated]; NSLog(@"xx_viewWillAppear"); }
-
10. 为什么分类不能使用属性呢?
主要实际上还是内存分配相关。属性是由ivar 、get、set 三部分组成,
分类不能添加实例变量,Class实际上是一个指向objc_class结构体,在编译的时候 objc_class结构体大小已经固定,内存已经分配,不可能往这个结构体中添加数据,只能修改。
成员变量链表指向是固定地址,methodList是二维数组 可以通过修改增加方法,因此,可以动态添加方法,不能添加成员变量。也就可以通过运行时动态加载属性。
11、对象发送消息会经过以下几个步骤
在对象的类中查找selector,如果找到了,执行对应函数的IMP
查找过程会先从方法缓存中查找,然后再沿着类的继承关系链查找,
如果没有找到,就会进行三次消息转发、补救的过程。
第一次是 动态方法决议
第二次是 提供 后备接收者对象
第三次是 以其他形式实现该消息方法(返回方法签名, 直接切换调用目标,也就是该方法的**Target**)获取到的方法签名包装成Invocation,将NSInvocation多次转发到多个对象