objc对象内存布局
对象:所有父类的成员变量和自己的成员变量
类对象:其中存放着普通成员变量列表与动态方法(“-”开头的方法)
元类对象:存放着static类型的成员变量与static类型的方法(“+”开头的方法)
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0,可以通过runtime函数class_setVersion或者class_getVersion进行修改、读取
long info OBJC2_UNAVAILABLE; // 类信息,供运行时期使用的一些位标识,如CLS_CLASS (0x1L) 表示该类为普通 class,其中包含实例方法和变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小(包括从父类继承下来的实例变量)
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量地址列表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法地址列表,与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储实例方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache OBJC2_UNAVAILABLE; // 缓存最近使用的方法地址,用于提升效率;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 存储该类声明遵守的协议的列表
#endif
}
SEL
SEL是selector在Objective-C中的表示类型。selector可以理解为区别方法的ID。
struct objc_selector {
char *name; OBJC2_UNAVAILABLE;// 名称
char *types; OBJC2_UNAVAILABLE;// 类型
};
IMP
终于到IMP了,它在objc.h中得定义如下:
typedef id (*IMP)(id, SEL, ...);
IMP是“implementation”的缩写,它是由编译器生成的一个函数指针。当你发起一个消息后(下文介绍),这个函数指针决定了最终执行哪段代码。
Method
Method代表类中的某个方法的类型。
typedef struct objc_method *Method;
objc_method的定义如下:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE; // 方法类型
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
方法名method_name类型为SEL,上文提到过。
方法类型method_types是一个char指针,存储着方法的参数类型和返回值类型。
方法实现method_imp的类型为IMP,上文提到过。
Selectors, Methods, & Implementations
在 Objective-C 的运行时中,selectors, methods, implementations 指代了不同概念,然而我们通常会说在消息发送过程中,这三个概念是可以相互转换的。 下面是苹果 Objective-C Runtime Reference中的描述:
- Selector(
typedef struct objc_selector *SEL
):在运行时 Selectors 用来代表一个方法的名字。Selector 是一个在运行时被注册(或映射)的C类型字符串。Selector由编译器产生并且在当类被加载进内存时由运行时自动进行名字和实现的映射。- Method(
typedef struct objc_method *Method
):方法是一个不透明的用来代表一个方法的定义的类型。- Implementation(
typedef id (*IMP)(id, SEL,...)
):这个数据类型指向一个方法的实现的最开始的地方。该方法为当前CPU架构使用标准的C方法调用来实现。该方法的第一个参数指向调用方法的自身(即内存中类的实例对象,若是调用类方法,该指针则是指向元类对象metaclass)。第二个参数是这个方法的名字selector,该方法的真正参数紧随其后。
理解 selector, method, implementation 这三个概念之间关系的最好方式是:在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键值是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。 Method swizzling 修改了类的消息分发列表使得已经存在的 selector 映射了另一个实现 implementation,同时重命名了原生方法的实现为一个新的 selector。
发送消息会经过以下几个步骤:
objc_msgSend函数的调用过程:
第一步:检测这个selector是不是要忽略的。
第二步:检测这个target是不是nil对象。nil对象发送任何一个消息都会被忽略掉。
第三步:
0.如果上面两个都过了,那就开始查找这个类的 cache ,完了找得到就跳到对应的函数去执行。
1.cache中找不到时,它会首先在自身isa指针指向的类(class)methodLists中查找该方法,如果找不到则会通过class的super_class指针找到父类的类对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根class;
2.当我们调用某个某个类方法时,它会首先通过自己的isa指针找到metaclass,并从其中methodLists中查找该类方法,如果找不到则会通过metaclass的super_class指针找到父类的metaclass对象结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查找,直至根metaclass;-
第四步:前三部都找不到就会进入动态方法解析(消息转发)。
请参照图片品味以下步骤:
1.通过resolveInstanceMethod:方法决定是否动态添加方法。如果通过class_addMethod动态添加方法并返回Yes则该方法会被调用,这样以来消息得到处理,结束;如果没有动态添加方法或者返回No,则进入下一步;
2.这步会进入forwardingTargetForSelector:方法,用于指定备选对象响应这个selector,不能指定为self。如果返回某个对象则会调用对象的方法,结束。如果返回nil,则进入第三步;
3.这步我们要通过methodSignatureForSelector:方法签名,如果返回nil,则消息无法处理。如果返回methodSignature,则进入下一步;
4.这步调用forwardInvocation:方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等,如果方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector方法,若我们没有实现这个方法,那么就会crash。
+ (void)load;
和 + (void)initialize;
有什么用处?
当类对象被引入项目时, runtime 会向每一个类对象发送 load
消息. load
方法还是非常的神奇的, 因为它会在每一个类甚至分类被引入时仅调用一次, 调用的顺序是父类优先于子类, 子类优先于分类. 而且 load
方法不会被类自动继承, 每一个类中的 load
方法都不需要像 viewDidLoad
方法一样调用父类的方法. 由于 load
方法会在类被 import
时调用一次, 而这时往往是改变类的行为的最佳时机.使用 method swizlling
来修改原有的方法时, 就是在分类 load
中实现的.
initialize
方法和 load
方法有一些不同, 它虽然也会在整个 runtime 过程中调用一次, 但是它是在该类的第一个方法执行之前调用, 也就是说 initialize
的调用是惰性的, 它的实现也与我们在平时使用的惰性初始化属性时基本相同.而不在 initialize 方法中改变方法实现的原因是 initialize 可能会被子类所继承并重新执行最终导致无限递归, 而 load 并不会被继承. 我在实际的项目中并没有遇到过必须使用这个方法的情况, 在该方法中主要做静态变量的设置并用于确保在实例初始化前某些条件必须满足.
1.http://tech.glowing.com/cn/objective-c-runtime/
2.https://www.ianisme.com/ios/2019.html
3.https://blog.ibireme.com/2013/11/26/objective-c-messaging/