1.分类的加载处理过程
- 通过Runtime加载某个类的所有Category数据
- 把所有Category的方法、属性、协议数据,合并到一个大的数组中(这个大的数组是一个二维数组),后面参与编译的Category数据会在数组的前面
- 将合并后的分类数据(方法、属性、协议),插入到类的原来的数据的前面。
因为以上所以分类的方法会覆盖类里面的相同名字的方法,但是不是真正的覆盖,只是最先遍历到的是分类里面的方法
2.Category的实现原理
- Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
- 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
3. Category和Class Extension的区别是什么?
- Class Extension在编译的时候,他的数据就已经包含在类信息中(编译期已经合并到类信息中了)
- Category是在运行时,才会将数据合并到类信息中
4. Load方法的调用
- load方法会在runtime加载类,分类的时候调用(也就是在程序运行的时候调用)
- 每个类,分类的load方法在程序运行的过程中只会调用一次
- load方法的调用顺序
1. 先调用类的load方法 * 按照编译先后顺序调用(先编译,先调用) * 调用子类的load方法之前会先调用父类的load方法 2. 再调用分类的load方法 * 按照编译先后顺序调用(先编译,先调用)
- +load方法可以继承,但是一般不会主动调用(系统调用+load方法的时候没有用到消息发送机制,而是直接找到的+load方法的地址来直接调用的)
5. +Initialize的调用
- +Initialize 会在类第一次接收到消息的时候调用
- 先调用父类的+ initalize,再调用子类的+ initialize,但是它也遵循消息发送机制,即如果分类中实现了+ initalize,那么就会覆盖原来类的+ initalize,原来类的+ initalize就不会被调用.
- 先初始化父类,再初始化子类,每个类只会初始化一次
*+ initalize的调用再源码中,会在每次对象调用方法前去判断,当前类的+ initalize方法是否被调用过,如果没有被调用过就判断他的父类的+ initalize方法是否被调用,没有调用就先调用父类的+ initalize,再调用自己的+ initalize方法,如果都调用过了接下来走正常的消息发送机制.
- initalize和+load的很大区别是,+ initalize查找过程后的调用是通过objc_msgSend进行调用的,所以有以下特点
* 如果子类没有实现+ initalize,会调用父类的+ initalize(所以父类的+ initalize可能会被调用多次) * 如果分类实现了+ initalize,就会覆盖类本身的+ initalize的调用
- initalize和+load的很大区别是,+ initalize查找过程后的调用是通过objc_msgSend进行调用的,所以有以下特点
6. Block
- block本质
- block本质上是一个OC对象,它内部也有一个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block捕获变量
- 局部变量(atuo,static)的捕获,auto变量的捕获是值传递,static捕获是地址传递,这样捕获的原因是auto变量可能会销毁,所以只能是值传递
-
全局变量,block内部不会捕获,因为全局变量,任何地方都能访问,所以不需要捕获
WX20201012-112136@2x.png - self 也会被block所捕获,self也是局部变量,因为self本身就是地址,所以他也是值传递
- block 的类型
- 没有访问auto变量的是global类型
- 访问了auto变量是stack类型
-
stack类型调用copy后变为malloc类型
Block调用copy.png
- block的copy
在ARC环境下,编译器会鞥局情况自动将栈上的block复制到堆上,比如以下情况- block作为函数的返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法的参数时
- block作为GCD API的方法参数时
-
block对象类型的auto变量的捕获
对象类型的auto的变量.png - __block修饰符
- __block可以用于解决block内部无法修改auto变量值的问题
- __block 不能修饰全局变量,静态变量
- 编译器会将__block修饰的变量包装成一个对象
- 被__block 修饰的对象类型
- 当__block变量在栈上时,不会对指向的对象产生强引用
- 当__block变量被copy到堆上时
- 会调用__block变量内部的copy函数
- copy 函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据所指向的对象的修饰符(__strong,__weak,__unsafe_unretaind)做出相应的操作,形成强引用或者弱引用(注意: 这里仅限ARC时会retain,MRC时不会retain)
- 如果__block变量从堆上移除
- 会调用__block变量内部的dispose函数
- dispose 函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放指向的对象(release)
- block的循环引用
- 可以用___weak或者__unsafe_unretianed来解决循环引用的问题
- 他们的不同点
__weak: 不会产生强引用,指向的对象销毁时,会自动让指针置为nil __unsafe_unretianed不会产生强引用,不安全,因为当指向的对象销毁时,指针不会置为nil,指针存储的地址不变,会产生野指针的问题
- 使用__block也能解决循环引用的问题,但是必须要调用block,并且要在block内部将变量置为nil,因为__block修饰的变量会被包装成一个对象,这个对象内部对变量有一个强引用,对象本身对被包装的__block对象有一个强引用,会形成一个三角引用,需要调用block后,并且在block的调用内部将变量置为nil,才会打破这种循环引用.
- block的属性修饰词为什么是copy?使用block有哪些注意?
- block一旦没有进行copy操作,就不会在堆上,就不能自己控制他的生命周期
- 使用注意: 要注意循环引用
网友评论