Runtime 探析

2018-12-28  本文已影响0人  浮萍向北

Runtime 简介

因为Objective-C是一门动态语言,这意味着它不仅需要一个编译器,也还需要一个运行时的系统来动态的创建类和对象、进行消息转递和转发。这就是Objective-C Runtime 系统存在的意义,它是整个Objc 运行框架的一块基石。


Runtime 基础数据结构

在我们调用方法是系统会自动为我们转换成 Objc_magSend:函数

id objc_msgSend ( id self,SEL op,...)

id

objc_msgSend 第一个参数的类型为 id ,它是一个指向类实例的指针:

typedef struct objc_object *id;

objc_object 是什么? 参考objc-private.h部分源码:

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_object 结构体包含 一个 isa 指针类型,类型为isa_t的联合体。我们根据 isa 就可以找到对象所属的类。因为isa_t 使用 union 实现,所以可能表示多种形态,即可以当成多种形态 即可以当成指针 也可以存储标志位。
PS: isa 指针不总是指向实例对象的所属类,不能依靠它来确定类型 而是应该用 class 方法来确定实例对象的类。KVO的实现机制就是将被观察者对象的 isa 指针指向了一个中间类而不是真实的类,这种叫做 isa—swizzling 的技术。KVO

SEL

Objc_magSend 函数第二个参数类型为 SEL , 它是 selector 在objc 中表示类型。selector 是方法选择器,可以理解区分方法的ID,而ID的数据结构SEL:

typedef struct objc_selector *SEL

@selector( ) 基本可以等同于C语言的函数指针,只不过C语言中可以把函数名直接赋给一个函数指针,而Objc 的类不能直接应用函数指针。这样只能做一个@selector语法来取 它的结果是一个SEL类型。

Class

Class 是指向 objc_class 结构体的指针:

typedef struct objc_class *Class;

objc_class 包含很多方法,主要都为围绕它的几个成员:

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();
}
... 省略其他方法
}

objc_class 继承于 objc_object,也就是说一个Objc 类本身同时也是一个对象,为了处理类和对象的关系,runtime库创建了一种叫做元类(Meta Class),类对象所属的类型就叫做元类,它用来表述对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象。而每个类仅有一个与之相关的元类。当你发出一个类似 [NSObject alloc] 的消息时,实际上是把这个消息发送给一个类对象,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。 所有的元类最终指向根元类为其超类。所有的元类方法列表都能够响应消息的类方法。当 [NSObject alloc] 这条消息发送给对象的时候 objc_Send() 会去它的元类里面去查找能够响应的消息的方法。

class-diagram.jpg

上图实线是 superclass 的指针 ,虚线是isa 指针。
1.root class 其实就是NSObject NSObject 是没有超类的,所以 root class(class)和superrclass 指向 nil。
2.每个Class都有一个 isa 指针 指向唯一的Meta Class。
3. Root class(meta)的superclass指向Root class,也就是NSObject 形成一个回路。
4.每个Meta class的isa指针都指向Root class (meta)。

cache_t

struct cache_t {
struct bucket_t *_buckets; // 存储Method的链表
mask_t _mask;             //  缓存的bucket的总数
mask_t _occupied;         //  表明目前实际占用的缓存bucket的个数
... 省略其他方法
}

cache 为了方法的调用的性能进行了优化。每当实例对象接收到一个消息时,它不会直接在isa 指向的类的方法列表中遍历查找能够响应消息的方法,因为这样效率太低了 而是在cache 中查找。Runtime 系统会把被调用方法存到 cache 中,下次查找的时候效率更高。

class_data_bits_t

objc_class 中复杂的是 bits class_data_bits_t 结构体所包含的信息太多了,主要包含 class_rw_t,retain/release/autorelease/retainCountalloc 等信息,很多存取方法也是围绕它展开。

struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
class_rw_t* data() {
   return (class_rw_t *)(bits & FAST_DATA_MASK);
}
... 省略其他方法
}

Ivar

Ivar 是一种代表类种实例变量的类型。

typedef struct ivar_t *Ivar;

struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;

uint32_t alignment() const {
    if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
    return 1 << alignment_raw;
}
};

objc_property_t

objc_property_t 是表示Objective-C声明的属性的类型,其实际是指向objc_property结构体的指针,其定义如下:

 typedef struct objc_property *objc_property_t;

objc_property_attribute_t

objc_property_attribute_t 定义了属性的特性(attribute),它是一个结构体,定义如下:

typedef struct {
const char *name;           // 特性名
const char *value;          // 特性值
} 

Method

Method 是一种代表类中的某个方法的类型。

typedef struct method_t *Method;

struct method_t {
SEL name;
const char *types;
IMP imp;

struct SortBySELAddress :
    public std::binary_function<const method_t&,
                                const method_t&, bool>
{
    bool operator() (const method_t& lhs,
                     const method_t& rhs)
    { return lhs.name < rhs.name; }
};
};

方法名类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
方法类型 types 是个char指针,其实存储着方法的参数类型和返回值类型。
imp 指向了方法的实现,本质上是一个函数指针,后面会详细讲到。

IMP

IMP 定义:

typedef void (*IMP)(void /* id, SEL, ... */ );

它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法。

你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id 和 SEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id 和 SEL 参数就能确定唯一的方法实现地址;反之亦然。

消息实现

objc_msgSend 函数

在前面简单对 objc_msgSend 进行了一点介绍。看起来像是objc_msgSend 返回了数据,其实 objc_msgSend 从不返回数据而是你的方法被调用后返回的数据。消息发送过程:

1.检测这个 selector 是不是要忽略的。
2.检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
3.如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
4.如果 cache 找不到就找一下Class中的方法列表。
5.如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
6.如果还找不到就要开始进入消息转发。

方法中的隐藏参数

我们经常在方法中使用 self 关键字来引用实例本身,但没有想过为什么 self 就能取到调用当前方法的对象。其实self 的内容是在方法运行时被偷偷的动态转入的。

objc_msgSend 找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数转递给方法实现,同时还将两个隐藏的参数:
1.接收消息的对象 (self 指向的内容)
2.方法选择器 (_cmd 指向的内容)

在这两个参数中,self 更有用。实际上,它是在方法实现中访问消息接收者对象的实例变量的途径。
而当方法中的super关键字接收到消息时,编译器会创建一个objc_super结构体:

struct objc_super { id receiver; Class class; };

这个结构体指明了消息应该被传递给特定超类的定义。但receiver仍然是self本身,这点需要注意,因为当我们想通过[super class]获取超类时,编译器只是将指向selfid指针和class的SEL传递给了objc_msgSendSuper函数,因为只有在NSObject类才能找到class方法,然后class方法调用object_getClass(),接着调用objc_msgSend(objc_super->receiver, @selector(class)),传入的第一个参数是指向self的id指针,与调用[self class]相同,所以我们得到的永远都是self的类型。

下面这道题考察的点就是上面所说的
下面代码输出什么?🤔

 @implementation Son : Father 
 - (id)init {
self = [super init];
if (self)
{
    NSLog(@"%@", NSStringFromClass([self class]));
    NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end

引用文献:
神经病院 Objective-C Runtime 入院第一天—— isa 和 Class
玉令天下的博客-Objective-C Runtime

上一篇下一篇

猜你喜欢

热点阅读