💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# runtime [TOC] ### Method Method 代表类中某个方法的类型 ``` typedef struct objc_method *Method; struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; } ``` `objc_method` 存储了方法名,方法类型和方法实现: - 方法名类型为 `SEL` - 方法类型 `method_types` 是个 char 指针,存储方法的参数类型和返回值类型 - `method_imp` 指向了方法的实现,本质是一个函数指针 ### Ivar `Ivar` 是表示成员变量的类型。 ``` typedef struct objc_ivar *Ivar; struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; char *ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif } ``` 其中 `ivar_offset` 是基地址偏移字节 ### IMP IMP在objc.h中的定义是: ``` typedef id (*IMP)(id, SEL, ...); ``` 它就是一个**函数指针**,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 `IMP` 这个函数指针就指向了这个方法的实现。 如果得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面 `Cache` 中会提到。 你会发现 `IMP` 指向的方法与 `objc_msgSend` 函数类型相同,参数都包含 `id` 和 `SEL` 类型。每个方法名都对应一个 `SEL` 类型的方法选择器,而每个实例对象中的 `SEL` 对应的方法实现肯定是唯一的,通过一组 `id`和 `SEL` 参数就能确定唯一的方法实现地址。 而一个确定的方法也只有唯一的一组 `id` 和 `SEL` 参数。 ### Cache Cache 定义如下: ``` typedef struct objc_cache *Cache struct objc_cache { unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; unsigned int occupied OBJC2_UNAVAILABLE; Method buckets[1] OBJC2_UNAVAILABLE; }; ``` Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。 Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。 ### Property ``` typedef struct objc_property *Property; typedef struct objc_property *objc_property_t;//这个更常用 ``` 可以通过`class_copyPropertyList` 和 `protocol_copyPropertyList` 方法获取类和协议中的属性: ``` objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) ``` > 注意: > 返回的是属性列表,列表中每个元素都是一个 `objc_property_t` 指针 ``` #import <Foundation/Foundation.h> @interface Person : NSObject /** 姓名 */ @property (strong, nonatomic) NSString *name; /** age */ @property (assign, nonatomic) int age; /** weight */ @property (assign, nonatomic) double weight; @end ``` 以上是一个 Person 类,有3个属性。让我们用上述方法获取类的运行时属性。 ``` unsigned int outCount = 0; objc_property_t *properties = class_copyPropertyList([Person class], &outCount); NSLog(@"%d", outCount); for (NSInteger i = 0; i < outCount; i++) { NSString *name = @(property_getName(properties[i])); NSString *attributes = @(property_getAttributes(properties[i])); NSLog(@"%@--------%@", name, attributes); } ``` 打印结果如下: ``` 2014-11-10 11:27:28.473 test[2321:451525] 3 2014-11-10 11:27:28.473 test[2321:451525] name--------T@"NSString",&,N,V_name 2014-11-10 11:27:28.473 test[2321:451525] age--------Ti,N,V_age 2014-11-10 11:27:28.474 test[2321:451525] weight--------Td,N,V_weight ``` `property_getName` 用来查找属性的名称,返回 c 字符串。`property_getAttributes` 函数挖掘属性的真实名称和 `@encode` 类型,返回 c 字符串。 ``` objc_property_t class_getProperty(Class cls, const char *name) objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty) ``` `class_getProperty` 和 `protocol_getProperty` 通过给出属性名在类和协议中获得属性的引用。 # method swizzling 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。 ![img](http://upload-images.jianshu.io/upload_images/1771779-da34056b24fef205.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/509) Paste_Image.png 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,我们可以利用 class_replaceMethod 来修改类,我们可以利用 method_setImplementation 来直接设置某个方法的IMP,归根结底,都是偷换了selector的IMP,如下图所示: ![img](http://upload-images.jianshu.io/upload_images/1771779-d3fb26be7bfd47c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/483) Paste_Image.png 这就是所谓的method swizzling *** isa 混写 KVO 基于 isa swizzling