ObjC的runtime只能在Mac OS下才能编译, 代码都是在
x86_64
架构下运行,iOS上是在arm64
,armv7s
,armv7
架构下运行
遇到的问题
最近在做iOS上的闪退防御,最常见的就是防御NSMutableDictionary
的nil
值闪退,使用的方法也很简单,就是swizzleNSMutableDictionary
里的setObject:forKey:
方法,但是试了很多次都没有swizzle到该方法,觉得很诡异,结果找了很多资料才发现,原来在[self class]
上swizzlesetObject:forKey:
这个方法是没有用的,因为在NSMutableDictionary
的扩展中[self class]
返回的是NSMutableDictionary
, 在项目中创建的NSMutableDictionary
其实都是NSMutableDictionary
的子类__NSDictionaryM
,因为NSMutableDictionary
是一个类族。这篇文章就在runtime源码的背景下讲下Objective-C的类和类族
// 不生效的方法
@implementation NSMutableDictionary (YITCrashGuard)
+ (void)swizzleDictionary {
[YITCrashGuardSwizzler swizzleSelector:@selector(setObject:forKey:) withSelector:@selector(swizzled_setObject:forKey:) onClass:[self class]];
}
// 生效的方法
@implementation NSMutableDictionary (YITCrashGuard)
+ (void)swizzleDictionary {
[YITCrashGuardSwizzler swizzleSelector:@selector(setObject:forKey:) withSelector:@selector(swizzled_setObject:forKey:) onClass:objc_getClass("__NSDictionaryM")];
}
Objective-C的类,类的实例的实现方式
-
x86_64
,i386
,arm64
,armv7s
,armv7
的区别-
x86_64
:针对x86架构的64位处理器需要的架构 -
i386
:针对intel通用32位处理器需要的架构 -
x86_64
,i386
是Mac处理器的指令集 -
arm64
:64位iPhone真机上处理器需要的架构,iPhone 5s以上 -
armv7s
:32位iPhone真机上处理器需要的架构,iPhone 5, iPhone 5c -
armv7
:32位iPhone真机上处理器需要的架构,iPhone 5以下 -
arm64
,armv7s
,armv7
是ARM处理器的指令集
-
-
先看下Objective-C中类的实现,在
objc.h
文件中可以找到Objective-C中类跟类的实例的描述- Class(类):
/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class;
- instance of a Class(对象):
/// A pointer to an instance of a class. typedef struct objc_object *id; // id就是我们平时遇到的id类型
- Class(类):
-
由此可见,Objective-C对象都是C语言的结构体(
struct
),现在我们看下NSObject的实现,在NSObject.h
文件可以找到:@interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; }
代码中的
Class
即为objc_class
的结构体 -
那么
objc_class
,objc_object
又是怎么定义的呢?- 在
objc-runtime-new.h
文件中可以找到objc_class
的定义struct objc_class : objc_object { // Class ISA; isa_t isa; // inherited from objc_object Class superclass; cache_t cache; // formerly cache pointer and vtable class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags ... };
-
isa
: 继承于objc_object
-
superclass
: 指向自己父类的指针 -
cache
: 方法缓存 -
bits
: 类的实例方法链表
-
- 在
objc-private.h
文件中可以找到objc_object
的定义struct objc_object { private: isa_t isa; public: // ISA() assumes this is NOT a tagged pointer object Class ISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); ... };
- 在
由此可见,
objc_class
继承于objc_object
, 所以也是包含一个isa
结构体的,总结下来就是说,在Objective-C里,不只是对象的实例包含一个isa
结构体,这个对象的类本身也有这么一个isa
,所以说白了,其实Objective-C里的类也是一个对象
元类(meta-class)
如果每个对象都保存了自己能执行的方法,对内存的占用会有很大的影响,所以在Objective-C中,对象的方法是不会存在对象的结构体(
objc_object
)中的。当一个对象的实例方法被调用时,这个对象的实例需要通过自己持有的isa
来查找它自己对应的类(objc_class
),在objc_class
中的class_data_bits_t
结构体中查找对应方法的实现,找不到的话,objc_class
会通过superclass
查找继承的方法。-
对象的实例方法是通过
class_data_bits_t
结构体来查找,那么类方法又是怎么查找并调用的呢?这个时候就是我们要说的元类了,元类可以保证不管是类还是对象都可以通过相同的机制查找方法的实现
- 实例方法调用时,通过对象的
isa
在Class中获取方法的实现 - 类方法调用时,通过类的
isa
在meta-class中获取方法的实现
- 实例方法调用时,通过对象的
-
[Class class]
,[obj class]
,object_getClass(Class)
的区别,class
方法是得不到isa
的指向链的+ (Class)class { return self; }
- (Class)class { return object_getClass(self); }
Class object_getClass(id obj) { if (obj) { return obj->getIsa(); } else { return Nil; } }
元类(meta-class)之所以重要,是因为它储存着一个类的所有类方法,每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。
-
代码检验:
Son *son = [[Son alloc] init]; NSLog(@"instance: %p", son); NSLog(@"class: %p", object_getClass(son)); NSLog(@"meta class: %p", object_getClass(object_getClass(son))); NSLog(@"root meta: %p", object_getClass(object_getClass(object_getClass(son)))); NSLog(@"root meta's meta: %p", object_getClass(object_getClass(object_getClass(object_getClass(son)))));
Log结果为:
可以验证
isa
的指向链是正确的
类族(class cluster)
-
先看一个NSNumber例子:
NSNumber *integerNumber = [NSNumber numberWithInteger:1]; NSLog(@"class: %p", object_getClass(integerNumber)); NSLog(@"class: %p", [NSNumber class]);
Log结果为:
很明显,两个class返回的指针地址是不一样的。
object_getClass(integerNumber)
返回的应该是integerNumber isa
对应的类NSNumber
,[NSNumber class]
返回的是其自己,也同样是NSNumber
,按道理说这两个方法返回的指针应该是一样的啊。还有一个很奇怪的是如果是我们自己的Son
类, 同样的两个方法返回的指针地址是一致的 原因其实很简单,
[NSNumber numberWithInteger:1]
返回的其实不是一个NSNumber
类,是其的一个子类__NSCFNumber
,为什么?因为NSNumber
是一个类族,那为什么Son
类返回的就一样的呢?因为Son
不是一个类族类族概念: 一个父类有好多子类,父类在返回自身对象的时候,向外界隐藏各种细节,根据不同的需要返回的其实是不同的子类对象,这其实就是抽象类工厂的实现思路
这样就解释了我之前遇到的swizzle
NSMutableDictionary
的setObject:forKey
不生效的问题,我在NSMutableDictionary
扩展中swizzle的是[self class]
返回的类,相当于是[NSMutableDictionary class]
,这个方法返回的指针应该是NSMutableDictionary
,但是我们项目里创建的NSMutableDictionary
的实例其实对应的类是__NSDictionaryM
,所以这就能解释为什么没有swizzle到我们项目里创建的NSMutableDictionary
实例的setObject:forKey
方法
转载请注明出处,原文地址:http://www.kobedai.me/objc-runtime_1/