Category、load、initialize、Class、关联对象、isa、superClass、方法缓存、方法查找、消息发送(objc_msgSend)、runtime
一 、Category的实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息。在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
有load方法。load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以利用runtime的API间接实现Category有成员变量的效果。
详细原因:因Calss定义内,class_rw_t结构体内有class_ro_t属性的结构体,只读的
class_ro_t结构体内有ivars **成员变量**、instanceSize,是在编译的时候就确定的,
而Category是运行时才将数据合并到类信息中,默认情况下,因为分类底层结构的
限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现。
结构体如下
-
源码解读顺序
objc-os.mm
_objc_init
map_images
map_images_nolock -
objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy -
通过Runtime加载某个类的所有Category数据
把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面
将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
分类中重写类中原有的某个方法,导致原方法被“覆盖”。其实不算覆盖,是因合并到类方法列表的前部,方法查找时,因为分类方法在前,就找到分类的方法响应,类远方法就未轮到响应,造成被覆盖的假象。
二、+load方法
- +load方法会在runtime加载类、分类时调用
- 每个类、分类的+load,在程序运行过程中只调用一次
- 调用顺序
- 先调用类的+load 按照编译先后顺序调用(先编译,先调用) 调用
子类的+load之前会先调用父类的+load - 再调用分类的+load 按
照编译先后顺序调用(先编译,先调用) - +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
objc4源码解读过程
objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list
call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)
三、+initialize方法
- +initialize方法会在类第一次接收到消息时调用 调用顺序 先调用父类的+initialize,再调用子类的+initialize (先
- 初始化父类,再初始化子类,每个类只会初始化1次)
- +initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的。
- 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
- 如果分类实现了+initialize,就覆盖类本身的+initialize调用
四、Class
class的结构
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
method_t是对方法\函数的封装
IMP代表函数的具体实现
SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
可以通过@selector()和sel_registerName()获得
可以通过sel_getName()和NSStringFromSelector()转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的
types包含了函数返回值、参数编码的字符串
iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码
方法缓存
-
Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
-
缓存查找
objc-cache.mm
bucket_t * cache_t::find(cache_key_t k, id receiver)
五、关联对象
-
添加关联对象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy) -
获得关联对象
id objc_getAssociatedObject(id object, const void * key) -
移除所有的关联对象
void objc_removeAssociatedObjects(id object)
key的常见用法
1.静态方法变量 static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
2.静态变量 static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
3.使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
4.使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
关联策略 objc_AssociationPolicy
objc_AssociationPolicy | 对应的修饰符 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
OBJC_ASSOCIATION_RETAIN | strong, atomic |
OBJC_ASSOCIATION_COPY | copy, atomic |
关联对象的原理
- 实现关联对象技术的核心对象有
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation - objc4源码解读:objc-references.mm
- 关联对象并不是存储在被关联对象本身内存中关联对象存储在全局的统一的一个AssociationsManager中,设置关联对象为nil,就相当于是移除关联对象
详见下图:
六、isa、superClass
对象的isa指针指向哪里?
- instance对象的isa指向class对象
- class对象的isa指向meta-class对象
- meta-class对象的isa指向基类的meta-class对象
superClass
-
class的superclass指向父类的class,如果没有父类,superclass指针为nil
-
meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class
instance调用对象方法的轨迹
- isa找到class,方法不存在,就通过superclass找父类
class调用类方法的轨迹
- isa找meta-class,方法不存在,就通过superclass找父类
据以上Class结构体可知有个isa属性,在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息,见下图:
-
nonpointer
0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
1,代表优化过,使用位域存储更多的信息 -
has_assoc
是否有设置过关联对象,如果没有,释放时会更快 -
has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快 -
shiftcls
存储着Class、Meta-Class对象的内存地址信息 -
magic
用于在调试时分辨对象是否未完成初始化 -
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快 -
deallocating
对象是否正在释放 -
extra_rc
里面存储的值是引用计数器减1 -
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
七、objc_msgSend执行流程
OC 的消息发送机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
- 消息发送(当前类、父类中查找)、
- 动态方法解析
- 消息转发
objc_msgSend执行流程 – 源码跟读
objc-msg-arm64.s
ENTRY _objc_msgSend
b.le LNilOrTagged
CacheLookup NORMAL
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup
__class_lookupMethodAndLoadCache3
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod
_objc_msgForward_impcache
objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
Core Foundation
__forwarding__(不开源)
objc_msgSend执行流程01-消息发送
- 如果是从class_rw_t中查找方法
已经排序的,二分查找
没有排序的,遍历查找 - receiver通过isa指针找到receiverClass
- receiverClass通过superclass指针找到superClass
objc_msgSend执行流程02-动态方法解析
- 开发者可以实现以下方法,来动态添加方法实现
+resolveInstanceMethod:
+resolveClassMethod: - 动态解析过后,会重新走“消息发送”的流程
“从receiverClass的cache中查找方法”这一步开始执行
动态添加方法
- Method可以理解为等价于struct method_t *
- @dynamic是告诉编译器不用自动生成getter和setter的实现,等到运行时再添加方法实现
objc_msgSend的执行流程03-消息转发
- 开发者可以在forwardInvocation:方法中自定义任何逻辑
- 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
生成NSMethodSignature
八、super的本质
- super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
struct objc_super2
SEL - receiver是消息接收者
- current_class是receiver的Class对象
九、Runtime
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行。
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数,编写的OC代码,底层都是转换成了Runtime API进行调用
Runtime的应用
-
查看私有成员变量
如:设置UITextField占位文字的颜色 -
字典转模型
1.利用Runtime遍历所有的属性或者成员变量
2.利用KVC设值 -
替换方法实现
1.class_replaceMethod
2.method_exchangeImplementations
**Runtime API 1 – 类 **
-
动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) -
注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls) -
销毁一个类
void objc_disposeClassPair(Class cls) -
获取isa指向的Class
Class object_getClass(id obj) -
设置isa指向的Class
Class object_setClass(id obj, Class cls) -
判断一个OC对象是否为Class
BOOL object_isClass(id obj) -
判断一个Class是否为元类
BOOL class_isMetaClass(Class cls) -
获取父类
Class class_getSuperclass(Class cls)
Runtime API 2 – 成员变量
-
获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name) -
拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) -
设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar) -
动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types) -
获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
Runtime API 3 – 属性
-
获取一个属性
objc_property_t class_getProperty(Class cls, const char *name) -
拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) -
动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount) -
动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount) -
获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
Runtime API 4 – 方法
-
获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name) -
方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) -
拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount) -
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) -
动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) -
获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index) -
选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str) -
用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
以上仅做个人知识回顾使用,有误之处,希望指正。不全之处,还请移步查询。