本文主要写一下,runtime中关于类,元类的结构和他们之间的关系。其实应该在上一篇文章面试遇到Runtime的第一天中先写本文的内容,可是写那天恰好在整理category的知识点,因此趁热打铁的就写在了上一篇文章。若是在阅读时遇到有比较难理解的点,不妨能够先阅读本文,再去阅读面试遇到Runtime的第一天中的内容。面试
阅读过面试遇到Runtime的第一天,你确定就已经知道,runtime通俗点说就是一套底层API,那么咱们经过什么方式能够调用到它呢?api
咱们编写的OC代码,在编译阶段会自动转换成运行时代码缓存
NSObject能够看作是全部类的基类(NSProxy除外),NSObject协议中定义了以下这些能够从runtime中获取信息的方法bash
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
复制代码
固然咱们也能够手动调用runtime API,须要导入objc/Runtime.h和objc/message.h两个头文件less
先看NSObject的定义post
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
复制代码
只有一个Class类型的isa,找到Class的定义,是一个objc_class的结构体ui
typedef struct objc_class *Class;
复制代码
这里咱们直接看Objc2.0以后,objc_class的定义(忽略了部分本文不讨论的代码)this
typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface Object {
Class isa;
}
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
isa_t isa;
}
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
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
复制代码
从源码中咱们能够证明如下几点:spa
这张图中的关系咱们须要好好的理解(而且记忆)一下,也有助于咱们以后对runtime的理解指针
元类这个概念比较抽象,他为何存在呢?能够从调用类方法开始提及:
NSStringEncoding defaultStringEncoding = [NSString defaultStringEncoding];
复制代码
上面这句代码能够正确执行,说明OC不只能够给对象发送消息,也一样能够给类发送消息,由于上面咱们也提到了,OC中类也是一个对象。
那么,当给类发送消息的时候,类对象的isa就指向了meta-class,因此要去meta-class中去找到该方法的实现。
能够理解为,meta-class的存在是为了统一OC中全部对象消息查找转发的流程,或者说是引入meta-class来保证不管是类仍是对象都能经过相同的机制查找方法的实现。
这里又引出了几个面试题:
问:对象的实例方法存在哪儿?类方法存在哪儿?
答:当一个对象的实例方法被调用时,会经过isa找到对应的类,而后在该类的class_data_bits_t中去查找方法对应的实现,因此实例方法是存在类中的,而类方法是存在元类中的。
问:类方法在元类中是以什么形式存在?
答:类方法在元类中是以实例方法存在,而且,对象在类中是一个实例,类在元类中也是一个实例。因此,类的类方法,就是元类的实例方法。(这里比较绕,多读几遍,好好理解)
继续看源码
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
typedef unsigned int uint32_t;
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
复制代码
Cache的做用主要是为了加速消息分发, 系统会对方法和对应的地址进行缓存,因此在实际运行中,大部分经常使用的方法都是会被缓存起来的,Runtime系统实际上很是快,接近直接执行内存地址的程序速度。
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
}
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;
char *demangledName;
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
复制代码
这两部分本文不作过多的阐述,后面在写消息查找转发的时候在详细介绍,这里先简单了解一下便可。
上面啰里啰嗦的写了一堆,看文章看到这里可能也是似懂非懂的样子,下面经过咱们最经常使用的几个方法,来实战一下上面讲解的理论知识,帮助咱们理解记忆。
以下代码打印结果是什么?
@interface Sark : NSObject
复制代码
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
复制代码
打印结果:
14:17:56.444058+0800 test[30316:787077] Sark
14:17:56.444177+0800 test[30316:787077] Sark
复制代码
解释一下为何NSLog(@"%@", NSStringFromClass([super class]));也输出了Sark
- self 不必定是当前类, self只是一个形参 objc_msgSend(id self,SEL _cmd) 取决于消息的接收者
- 在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained Class class;
#else
__unsafe_unretained Class super_class;
#endif
/* super_class is the first class to search */
};
复制代码
在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver,一个是当前类的父类super_class。
- objc_msgSendSuper的工做原理应该是这样的: 从objc_super结构体指向的superClass父类的方法列表开始查找selector,找到后以objc->receiver去调用父类的这个selector。注意,最后的调用者是objc->receiver = self,而不是super_class!
以下代码打印结果是什么?
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);// yes no no no
复制代码
查看isKindOfClass和isMemberOfClass的源码:
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return (Class)(isa.bits & ISA_MASK);
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {// 第一次就获取到了class -> meta class 而后再遍历superclass
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {//第一次获取了self -> class 而后再遍历superclass
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
复制代码
分析:
- isKindOfClass 区分实例方法和类方法的实现不一样 类方法循环首先取了isa指针 第一次就获取到了class -> meta class 而后再遍历superclass 而实例方法是第一次获取了self -> class 而后再遍历superclass
- isMemberOfClass 没有遍历 直接比较
所以,第一行代码 BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];的判断流程是:
对NSObject meta-Class取super class用到里图里红圈标识出来的关系,获得结果是NSObject
第二行代码 BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];的分析流程是:
第三行代码 BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]]; Sark取meta-Class而后再一直遍历找super_class,最终也不会找到相等的,返回NO
第四行代码 BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]]; Sark和Sark meta-Class不相等,返回NO
通过上面两个小问题的实战,是否是对对象、类、元类之间的关系有了更深入的理解,简单总结一下:
- 每一个类都有一个惟一对应的元类
- 类对象的isa指向了元类
- 元类中以实例方法的形式保存了类的类方法
- 根元类(Root meta class)的isa指向本身,super class为NSObject ,造成一个闭环