Objective-C的动态性是由Runtime API来支撑的,Runtime API提供的接口基本都是C语言的,源代码由C\C++\汇编语言编写。
isa详解 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构isa_t,还使用位域来存储更多的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //arm64架构下,isa_t的结构参考如下: union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; struct { uintptr_t nonpointer : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 33; uintptr_t magic : 6; uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 19 }; };
字段含义如下:
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的类的属性中;
arm64架构下取出shiftcls的掩码ISA_MASK为0x0000000ffffffff8ULL,由此可见,class对象和meta-class对象的地址值最后3位都是0.
Class结构 Class本质上为一个结构体类型:
1 2 typedef struct objc_class *Class;
与该结构体相关的主要定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 struct objc_object { private: isa_t isa; //以下省略 } struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; // 方法缓存 class_data_bits_t bits; // 类的具体信息 class_rw_t *data() { return bits.data(); } //以下省略 } struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; //方法列表 property_array_t properties; //属性信息 protocol_array_t protocols; //协议列表 } struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; //instance对象占用的内存大小 #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; //类名 method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; //成员变量列表 const uint8_t * weakIvarLayout; property_list_t *baseProperties; }
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容。
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容。
method_t是对方法、函数的封装:
1 2 3 4 5 struct method_t { SEL name; //函数名 const char *types; //编码(返回值类型,参数类型) MethodListIMP imp; //指向函数的指针(函数地址) };
IMP代表函数的具体实现:
1 typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
SEL代表方法或函数名,一般叫做选择器,底层结构跟char *类似:
可以通过@selector()和sel_registerName()获得;
可以通过sel_getName()和NSStringFromSelector()转成字符串;
不同类中相同名字的方法,所对应的方法选择器是相同的;
1 typedef struct objc_selector *SEL;
types包含了函数返回值、参数编码的字符串。 相关介绍可以参考:Type Encodings
。
方法缓存 Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。
1 2 3 4 5 6 7 8 9 10 struct cache_t { struct bucket_t *_buckets; //散列表 mask_t _mask; //散列表长度-1 mask_t _occupied; //已经缓存的方法数量 } struct bucket_t { uintptr_t _imp; //函数地址 SEL _sel; //缓存的key }
方法调用 OC中的方法调用,其实都是转换为objc_msgSend函数的调用。objc_msgSend的执行流程可以分为3大阶段:
消息发送
动态方法解析
消息转发
objc_msgSend源码导读
消息发送 从源码归纳出如下流程:
动态方法解析 假如在消息发送过程中,没有查找到方法,那么就会进入动态方法解析。动态方法解析就是在运行时临时添加一个方法实现,来进行消息的处理。
开发者可以实现以下方法,来动态添加方法实现:
resolveInstanceMethod:
resolveClassMethod:
下面是代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void notFound_eat(id self, SEL _cmd) { // implementation .... NSLog(@"%@ - %@", self, NSStringFromSelector(_cmd)); NSLog(@"current in method %s", __func__); } //对象方法解析 + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { // 注意添加到self,此处即类对象 class_addMethod(self, sel, (IMP)notFound_eat, "v16@0:8"); return YES; } return [super resolveInstanceMethod:sel]; } //类方法解析 + (BOOL)resolveClassMethod:(SEL)sel { if (sel == @selector(learn)) { // 第一个参数是object_getClass(self) class_addMethod(object_getClass(self), sel, (IMP)notFound_learn, "v16@0:8"); return YES; } return [super resolveClassMethod:sel]; }
下面是class_addMethod添加的另一种方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - (void)notFound_eat { // implementation .... } + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(test)) { // 获取其他方法 Method method = class_getInstanceMethod(self, @selector(notFound_eat)); // 动态添加test方法的实现 class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method)); // 返回YES代表有动态添加方法 return YES; } return [super resolveInstanceMethod:sel]; }
动态解析过后,会重新进入到“消息发送”的流程,“从receiverClass的cache中查找方法”这一步开始执行。
在动态方法解析完成后,会将标识tridResolver设置为YES,表示已经进行过动态解析,避免消息发送和动态方法解析之间出现死循环。
动态方法解析最佳的一个实践用例就是@dynamic的实现。
消息转发 下面是消息转发阶段的流程图:
super super调用底层会转换为objc_msgSendSuper2()函数调用, 相关定义及注释如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /** * Sends a message with a simple return value to the superclass of an instance of a class. * * @param super A pointer to an \c objc_super data structure. Pass values identifying the * context the message was sent to, including the instance of the class that is to receive the * message and the superclass at which to start searching for the method implementation. * @param op A pointer of type SEL. Pass the selector of the method that will handle the message. * @param ... * A variable argument list containing the arguments to the method. * * @return The return value of the method identified by \e op. * * @see objc_msgSend */ id objc_msgSendSuper(struct objc_super *super, SEL op, ...) struct objc_super2 { id receiver; //消息接收者 Class cls; // the class to search,消息接收者的父类 }
使用super调用时,消息的接收者仍然是self,只是会从父类中开始寻找方法。
Runtime API 类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 动态创建一个类(参数:父类,类名,额外的内存空间) 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)
成员变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 获取一个实例变量信息 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)
属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 获取一个属性 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)
方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 获得一个实例方法、类方法 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)