参考
Objective-C Runtime 1小时入门教程
Objective-C特性:Runtime
Objc 对象的今生今世
神经病院Objective-C Runtime入院第一天——isa和Class
深入解析 ObjC 中方法的结构
iOS黑魔法-Method Swizzling
玉令天下:Objective-C Method Swizzling
实例对象结构
id就是一个指向类实例的指针
typedef struct objc_object *id;
struct objc_object {
isa_t _Nonnull isa OBJC_ISA_AVAILABILITY;
};
arm64 架构中的 isa_t 结构体
#define ISA_MASK 0x0000000ffffffff8ULL
#define ISA_MAGIC_MASK 0x000003f000000001ULL
#define ISA_MAGIC_VALUE 0x000001a000000001ULL
#define RC_ONE (1ULL<<45)
#define RC_HALF (1ULL<<18)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
uintptr_t indexed : 1; // 0 表示普通的 isa 指针,1 表示使用优化,存储引用计数
uintptr_t has_assoc : 1; // 表示该对象是否包含 associated object,如果没有,则析构时会更快
uintptr_t has_cxx_dtor : 1; // 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快
uintptr_t shiftcls : 33; // 类的指针
uintptr_t magic : 6; // 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化。
uintptr_t weakly_referenced : 1; // 表示该对象是否有过 weak 对象,如果没有,则析构时更快
uintptr_t deallocating : 1; // 表示该对象是否正在析构
uintptr_t has_sidetable_rc : 1; // 表示该对象的引用计数值是否过大无法存储在 isa 指针
uintptr_t extra_rc : 19; // 存储引用计数值减一后的结果
};
};
类结构
类其实也是对象,叫做类对象
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
........
}
NSObject 结构图.png
class_rw_t
运行期拷贝class_ro_t中的部分信息存入此结构体中,并存放运行期添加的信息
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
};
class_ro_t
记录编译期就已经确定的信息
struct class_ro_t {
const char * name; // 类名
uint32_t reserved; // 预留字段
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
property_list_t *baseProperties;
const ivar_list_t * ivars;
const uint8_t * ivarLayout;
const uint8_t * weakIvarLayout;
};
baseMethodList、baseProtocols、baseProperties,ivars编译器确定好的方法、协议、属性、成员变量
flags:各种信息合集
instanceStart、instanceSize
1、instanceStart之所以等于8,是因为每个对象的isa占用了前8个字节。
2、instanceSize = isa + 3个ivar,$6的size只有1,但是为了对齐,也占用了8
继承体系就在父类上面加
// ZNObjectFather,三个成员变量
instanceStart = 8
instanceSize = 32 (instanceStart + 3个ivar)
// ZNObjectSon,也有三个成员变量
instanceStart = 32
instanceSize = 56 (instanceStart + 3个ivar)
size_t objSize = class_getInstanceSize([ZNObjectFather class]);// 32
size_t objSize2 = class_getInstanceSize([ZNObjectSon class]);// 56
ivarLayout和 weakIvarLayout:成员变量的strong与weak信息
1、ivarLayout = "\x01",表示在先有0个弱属性,接着有1个连续的强属性。若之后没有强属性了,则忽略后面的弱属性
2、weakIvarLayout = "\x11",表示先有1个强属性,然后才有1个连续的弱属性,若之后没有弱属性了,则忽略后面的强属性
const uint8_t *
class_getIvarLayout(Class cls)
{
if (cls) return cls->data()->ro->ivarLayout;
else return nil;
}
const uint8_t *
class_getWeakIvarLayout(Class cls)
{
if (cls) return cls->data()->ro->weakIvarLayout;
else return nil;
}
@interface BBObject : NSObject
{
NSString *name1;
__weak NSString *name2;
NSString *name3;
NSString *name4;
NSString *name5;
NSString *name6;
__weak NSString *name7;
NSString *name8;
__weak NSString *name9;
}
const uint8_t *ivarLayout = class_getIvarLayout([BBObject class]);
const uint8_t *weakIvarLayout = class_getWeakIvarLayout([BBObject class]);
// ivarLayout=\x01\x14\x11
// weakIvarLayout =\x11\x31\x11
ivarLayout=\x01\x14\x11
weakIvarLayout =\x11\x31\x11
实例对象的isa的isa...是什么?各层isa是什么?
举例子:
BBObj *obj = [BBObj new];
1、obj->isa是一个objc_class结构对象,存放在普通成员变量、动态方法(“-”开头的方法)、isa(metaclass)、super_class
2、obj->isa->isa也是一个objc_class结构对象,叫做元类metaclass,存放着static类型的成员变量与static类型的方法(“+”开头的方法)
3、obj->isa->super_class也是一个objc_class结构对象:父类实例对象
4、obj->isa->isa->isa(metaclass->isa)是NSObject类对象
5、 obj->isa->isa->super_class(metaclass->super_class)是父类metaclass对象
Student *stu = [[Student alloc]init];
NSLog(@"Student's class is %@", [stu class]);
NSLog(@"Student's meta class is %@", object_getClass([stu class]));
NSLog(@"Student's meta class's superclass is %@", object_getClass(object_getClass([stu class])));
Class currentClass = [Student class];
for (int i = 1; i < 5; i++)
{
NSLog(@"Following the isa pointer %d times gives %p %@", i, currentClass,currentClass);
currentClass = object_getClass(currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
输出如下:
Student's class is Student
Student's meta class is Student
Student's meta class's superclass is NSObject
Following the isa pointer 1 times gives 0x100004d90 Student
Following the isa pointer 2 times gives 0x100004d68 Student
Following the isa pointer 3 times gives 0x7fffba0b20f0 NSObject
Following the isa pointer 4 times gives 0x7fffba0b20f0 NSObject
NSObject's class is 0x7fffba0b2140
NSObject's meta class is 0x7fffba0b20f0
class与meta class关系.jpg
objc_ivar_list *ivars是什么?
objc_ivar_list其实就是一个链表,存储多个objc_ivar
struct objc_ivar_list {
int ivar_count;
#ifdef __LP64__
int space;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1];
}
objc_ivar是什么?
objc_ivar结构体存储类的单个成员变量信息
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name; // 变量名
char *ivar_type; // 变量类型
int ivar_offset; // 基地址偏移字节
#ifdef __LP64__
int space; // 占用空间
#endif
}
使用对象成员变量流程
调用 +alloc 方法来初始化一个对象时,也仅仅在内存中生成了一个objc_object结构体,并根据其instanceSize来分配空间,将其isa指针指向所属的类。
类的成员变量ivar_t存储在class_ro_t中的ivar_list_t * ivars中,
其中offset 是成员变量相对于对象内存地址的偏移量,正是通过它来完成变量寻址。
当我们使用对象的成员变量时,如 myObject.var ,编译器会将其转化为object_getInstanceVariable(myObject, 'var', **value) 找到其ivar_t结构体ivar,然后调用object_getIvar(myObject, ivar)来获取成员变量的内存地址。其计算公式如下:
id *location = (id *)((char *)obj + ivar_offset);
基于此,虽然多个对象的isa指针指向同一个objc_class,但由于对象的内存地址不一样,所以它们的实例变量存储位置也不一样,从而实现对象与类之间的多对一关系。
objc_method_list是什么?
objc_method_list是一个链表,存储多个objc_method,而objc_method结构体存储类的某个方法的信息
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
// 表示类中的某个方法
typedef struct objc_method *Method;
struct objc_method {
SEL method_name; // 方法名
char *method_types; // 方法类型
IMP method_imp; // 方法实现
}
调用对象方法:
当obj_object接收到消息后,通过其isa指针找到对应的objc_class,objc_class又通过其data() 方法,查询class_rw_t的methods列表。
SEL是什么?
SEL是selector在Objective-C中的表示类型
typedef struct objc_selector *SEL;
struct objc_selector {
char *name; // 名称
char *types; // 类型
};
IMP是什么?
IMP本质上就是一个函数指针,指向方法的实现(方法的代码)
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
Cache是什么?
Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。
typedef struct objc_cache *Cache OBJC2_UNAVAILABLE;
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
/*
当对象receiver调用方法message时,
1、先在Cache查找IMP,找到返回IMP
2、如果没有找到,再在类的methodLists中查找,
3、如果没有找到,就在super_class父类重复(1、2),
4、如果找到把方法加入receiver类中的Cache、返回IMP
类的相关操作函数有如下:
1、iOS Class结构分析
2、Objective-C Runtime 运行时之一:类与对象
三、Objective-C的消息传递
1、基本消息传递
对象调用方法叫做发送消息。在编译时,程序的源代码就会从对象发送消息转换成Runtime的objc_msgSend函数调用。
例如某实例变量receiver实现某一个方法oneMethod
[receiver oneMethod];
Runtime会将其转成类似这样的代码
objc_msgSend(receiver, selector);
具体会转换成什么代码呢?Runtime会根据类型自动转换成下列某一个函数:
1、objc_msgSend:普通的消息都会通过该函数发送
2、objc_msgSend_stret:消息中有数据结构作为返回值(不是简单值)时,通过此函数发送和接收返回值
3、objc_msgSendSuper:和objc_msgSend类似,这里把消息发送给父类的实例
4、objc_msgSendSuper_stret:和objc_msgSend_stret类似,这里把消息发送给父类的实例并接收返回值
2、objc_msgSend函数的调用过程:
第一步:检测这个selector是不是要忽略的。
第二步:检测这个target是不是nil对象。nil对象发送任何一个消息都会被忽略掉。
第三步:
1、先在Cache查找IMP,找到返回IMP
2、如果没有找到,再在类的methodLists中查找,
3、如果没有找到,就在super_class父类重复(1、2),
4、如果找到把方法加入Cache、返回IMP
第四步:前三步都找不到就会进入动态方法解析(看下文)。
3、消息转发及动态解析方法
当一个对象能接收一个消息时,会走正常的方法调用流程。但如果一个对象无法接收一个消息时,就会走消息转发机制。
消息转发机制基本上分为三个步骤:
消息转发流程(1)动态增加方法阶段:方法目的是为了给类利用 class_addMethod 添加方法的机会,如下:
//类方法
+(BOOL)resolveClassMethod:(SEL)sel {
// 写法与下面resolveInstanceMethod类似
}
//实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(XXXX)) {
//const char *types:
//"v@:" 返回值void类型的方法,没有参数传入。
//"i@:" 返回值int类型的方法,没有参数传入。
//"i@:@" 返回值int类型的方法,又一个参数传入。
//"s@:@" 返回值string类型的方法,又一个参数传入。
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(AAA)), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
表示方法的参数和返回值,详情请参考Type Encodings
(2)对象转发阶段:询问是否把消息给其他接收者处理(单一),返回id是执行者(非self非nil)
如下:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
MessageForwarding *obj=[MessageForwarding new];// 消息执行者
if ([obj respondsToSelector:aSelector]) {
return obj;
}
return [super forwardingTargetForSelector:aSelector];
}
(3)NSInvocation执行阶段:
//首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息
// methodSignatureForSelector实例方法;instanceMethodSignatureForSelector类方法
//如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
MessageForwarding *messageForwarding1 = [MessageForwarding new];
if ([messageForwarding1 respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding1];
}
//可以多个对象,区别于第二个,步骤越往后,处理消息的代价越大,到最后一个阶段时,都创建了 NSInvocation 对象了
MessageForwarding *messageForwarding2 = [MessageForwarding new];
if ([messageForwarding2 respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:messageForwarding2];
}
}
(4)以上三点都不处理,就doesNotRecognizeSelector
只是程序主动抛出一个-[类 XXX方法]: unrecognized selector sent to instance不能识别方法的异常
消息发送与转发路径流程图.jpglookUpImpOrForward
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver) {
Class curClass;
IMP imp = nil;
Method meth;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 检查是否添加缓存锁,如果没有进行缓存查询。
// 查到便返回IMP指针
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// 通过调用realizeClass方法,分配可读写`class_rw_t`的空间
if (!cls->isRealized()) {
rwlock_writer_t lock(runtimeLock);
realizeClass(cls);
}
// 倘若未进行初始化,则初始化
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}
// 保证方法查询,并进行缓存填充(cache-fill)
retry:
runtimeLock.read();
// 是否忽略GC垃圾回收机制(仅用在macOS中)
if (ignoreSelector(sel)) {
imp = _objc_ignored_method;
cache_fill(cls, sel, imp, inst);
goto done;
}
// 当前类的缓存列表中进行查找
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 从类的方法列表中进行查询
meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
// 从父类中循环遍历
curClass = cls;
while ((curClass = curClass->superclass)) {
// 父类的缓存列表中查询
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 如果在父类中发现方法,则填充到该类缓存列表
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
// 从父类的方法列表中查询
meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
// 进入method resolve过程
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
// 调用_class_resolveMethod,解析没有实现的方法
_class_resolveMethod(cls, sel, inst);
// 进行二次尝试
triedResolver = YES;
goto retry;
}
// 没有找到方法,启动消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
从类的方法列表中进行查询
static method_t *getMethodNoSuper_nolock(Class cls, SEL sel) {
runtimeLock.assertLocked();
// 遍历所在类的methods,这里的methods是List链式类型,里面存放的都是指针
for (auto mlists = cls->data()->methods.beginLists(), end = cls->data()->methods.endLists(); mlists != end; ++mlists) {
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
调用_class_resolveMethod,尝试类是否现实了resolveInstanceMethod或resolveClassMethod
void _class_resolveMethod(Class cls, SEL sel, id inst) {
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 针对于对象方法的操作
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 针对于类方法的操作
_class_resolveClassMethod(cls, sel, inst);
// 再次启动查询,并且判断是否拥有缓存中消息标记_objc_msgForward_impcache
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {
// 说明可能不是 metaclass 的方法实现,当做对象方法尝试
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
四、self与super
- self是类的一个隐藏参数,每个方法的实现的第一个参数即为self。
- super并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法。
下面的代码分别输出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
结果:输出两个Son
解析
当调用[self class]方法时,会转化为objc_msgSend函数。
当调用[super class]方法时,会转化为objc_msgSendSuper。
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"));
简化
__rw_objc_super objc_super = (__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}
class_getSuperclass(objc_super,sel_registerName("class"))
objc_super *super是什么?
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class。
objc_msgSendSuper的工作原理应该是这样的:
从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc_super的receiver(就是self)去调用父类的这个selector。注意,最后的调用者是self,而不是super_class!
五、Method Swizzling
比较简单、常用的方式,方案A
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// originalMethod 已经存在 class_addMethod 方法就会失败
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// 方法存在就替换掉,如果不存在就直接添加
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
1、RSSwizzle 被很多人推荐,它用很复杂的方式解决了 What are the Dangers of Method Swizzling in Objective C? 中提到的一系列问题。不过引入它还是有一些成本的,建议在本文列举的那些极端特殊情况下才使用它,毕竟方案 A 已经能 Cover 到大部分情况了。
2、JRSwizzle 尝试解决在不同平台和系统版本上的 Method Swizzling 与类继承关系的冲突。对各平台低版本系统兼容性较强。
网友评论