category的本质

category的底层结构:

其实是一个cateagory_t的结构体,内存中包含了name, class, 对象方法列表,类方法列表,属性列表,协议列表。 注意:这里面是没有成员变量列表的,因为分类中是不允许添加成员变量的。分类中添加的属性并不会自动帮我们生成成员变量,只会生成set和get方法的声明,实现需要我们自己实现。

分类中的对象方法列表最终会被存放在类对象里面,类方法列表最终会被存放在元类对象里面。

这个合并的过程是: 1.编译器加载所有的类和分类信息 2.在程序运行过程中,找到某一个类的所有分类列表,遍历每一个分类信息,取出他们里面的方法列表, 属性列表,协议列表分别放入对应的新建的二维数组里面,这里注意是从后往前遍历的,所以先编译的分类的信息会被加在对应二维数组的后面位置。 3.通过类对象的data()方法,找到类对象的class_rw_t结构体,(class_rw_t中存放着类对象的方法,属性和协议等数据,rw结构体通过类对象的data方法获取)然后将刚才的二维数组里面的元素一一插入到原来方法列表的最前面。(系统内部是是用了memmove将原来的方法列表后移,然后是用memcpy将分类的方法数组copy到方法数组的前面) 从这里也能得出为什么分类重写本类的方法时,会覆盖本类的方法。本质上并不是覆盖,而是优先调用,本类的方法依然在内存中,至少分类的方法在前面,先找到了分类的方法调用了,而且后编译的分类的方法,会被优先找到。

category的实现原理,以及为什么只能添加方法不能添加属性?

category的实现原理是将catagory里面的方法、属性、协议数据放在categoty_t的结构体中,然后将结构体中的方法列表拷贝到类对象的方法列表中。 catagory可以添加属性,但是并不会自动生成成员变量以及set/get方法的实现。因为catagory_t的结构体中并不存在成员变量。通过OC对象的本质,实例对象底层结构体,alloc后内存空间就申请完了,固定了,内存大小是和成员变量相关的,所以不能增加成员变量,因此分类不可以添加成员变量。但是可以使用Runtime给我们提供的API-关联对象来间接实现这种效果。

和OC 的 extension区别:

extension是在编译时就数据就包含在类信息中了。 category是在运行时,才会将数据合并到类信息中。

load和initialize调用原理

*load方法的调用时机:app启动时,runtime加载类和分类的时候。*调用顺序:

  • 先调用类的load方法,所有类的load方法按照编译顺序调用,先编译的先调用
  • 调用子类的load方法之前,会先调用父类的load方法
  • 最后调用分类的load方法,先编译的分类先调用

每个类、分类的load方法只会调用一次。load方法是通过指针直接调用的,并不是objc_msgSend调用

initialize方法的调用时机:

  • 在类第一次接受到消息的时候会调用。如果这个类没有使用,就不会调用。
  • 调用子类的initialize之前,要先保证调用了父类的initialize方法。
  • 如果之前已经调用了initialize方法,就不会再调用了(父类也是一样)。
  • initialize的调用本质上是objc_msgSend来调用的,子类没有实现+initialize方法,会通过superClass调用父类的+initialize,所以父类的initialize方法有可能会被调用多次(本质是第一次先调用他父类的initialize方法,然后他自己调的时候,本类没有这个方法,又找到它父类的这个方法,再次调用),如果分类实现了+initialize, 会只调用房分类的+initialize方法, 不会调用本类的。

伪代码是这样:
If (自己没有初始化) {
if(父类没有初始化) {
objc_msgSend()
}
objc_msgSend()
}

load方法和initialize方法的区别:调用方式和调用时刻不同\

  • 调用方式:load是根据函数地址,直接调用。initialize是通过runtime的objc_msgSend来调用的。
  • 调用时刻:load是runtime加载类、分类的时候调用的(只会调用一次),initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize可能会被调用多次---子类没有实现initialize方法的情况下)

load方法和initialize在分类重写的时候调用顺序是什么样?

  • load:先调用类的load方法,先编译的类,先调用。在调用load方法之前,会先调用父类的load方法。分类中的load方法不会覆盖本类的load方法,先编译的分类先调用load方法。
  • initialize:先初始化父类,再初始化子类,如果子类没有实现initialize,会调用父类的initialize,所以这种情况下父类的initialize方法会被调用多次。如果分类实现了initialize方法,会覆盖类本身的initialize调用。
作者:乔明

%s 个评论

要回复文章请先登录注册