目录

Objc源码分析-runtime-Class

看了一段时间objc的源码,准备开个大坑,写一下了解到的东西,这一时半会是完工不了了,一篇一篇慢慢写吧。

先看一下官方对objc的介绍。

Objective-C is the primary programming language you use when writing software for OS X and iOS. It’s a superset of the C programming language and provides object-oriented capabilities and a dynamic runtime. Objective-C inherits the syntax, primitive types, and flow control statements of C and adds syntax for defining classes and methods. It also adds language-level support for object graph management and object literals while providing dynamic typing and binding, deferring many responsibilities until runtime.

(Objective-C是您在为OS X和iOS编写软件时使用的主要编程语言。 它是C编程语言的超集,提供面向对象的功能和动态运行时。 Objective-C继承了C的语法,原始类型和流控制语句,并添加了用于定义类和方法的语法。 它还为对象图管理和对象文字添加语言级支持,同时提供动态类型和绑定,将许多职责推迟到运行时。)

显然运行时是objc的这个语言非常重要的特性,它可以把很多静态语言(显然objc是动态语言)在编译和链接时期做的事情推迟到运行时期在做。这样写代码就有很大的灵活性,比如把消息转发给我们想要的对象,或者随意交换一个方法的实现等。

所以说objc除了编译器之外,还需要一个运行时系统 (runtime system) 来执行编译后的代码。Objective-C Runtime 就是这个运行时系统了,这个库使得C有了面向对象的能力。

下面我们就分析一下runtime是如何做到的。

由于日常接触的最多的是类与对象,所以我准备就从这里开始写起。

Class

OC的对象都是需要继承NSObject的,那么我们先看看NSObject。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
    // 这个 isa 其实没啥用,已经被废弃了, isa 存在 objc_object 中,并且是 isa_t 结构体类型的
    // 即使写 anObject->isa
    // Xcode 也报错:Direct accesss to Objective-C's isa deprecated in favor of object_getClass()
    // 建议我们用 object_getClass(), 它会返回 isa_t 中存的 Class cls
    // 如果这段注释看不懂可以跳过,最后回顾时自然会明白。
}
...
@end

发现除了常用了一些objc的方法外,有个Class类的isa指针。Class类在平时使用的时候会多多少少遇到过,比如说判断某对象是不是某类的实例。

1
[foobar isMemberOfClass:[SomeClass class]]

通过看源码我们可以看到其实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

// isMemberOfClass 中 [self class]调用的是- (Class)class方法 
- (Class)class {
    return object_getClass(self);
}

// isMemberOfClass 中 [SomeClass class]调用的是+ (Class)class方法 
+ (Class)class {
    return self;
}

由以上代码可以看出,其实判断OC对象的类型,就是判断类型为Class的对象是否相等,那么Class到底是什么呢。

1
typedef struct objc_class *Class;

可以看到Class其实就是指向objc_class结构体的指针(又被称为类对象)。

objc_class结构体里主要的成员如下。

1
2
3
4
5
6
7
8
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
    
    // 省略以下成员
}

现在我们可以说isMemberOfClass方法其实就是判断两个结构体(objc_class)实例的指针地址是否相同。

objc_class 又继承于objc_object,那么我们再看看objc_object的主要结构。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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的名字也可以猜到这个应该和NSObject有关系,那么他们是怎么产生联系的呢?

clang -rewrite-objc (把oc代码转写成c/c++代码)后有这么一段:

1
2
3
4
5
6
7
8
9
#ifndef _REWRITER_typedef_NSObject

#define _REWRITER_typedef_NSObject

typedef struct objc_object NSObject;

typedef struct {} _objc_exc_NSObject;

#endif

这下清楚了。。其实NSObject就是objc_object,一个OC对象其实就是objc_object或继承于objc_object的结构体的指针。。

objc_object结构体中也有一个isa_t类型的isa的成员,这个isa其实才是大家平时经常说的isa,之前在NSObject中的那个,其实是无效的,就是让大家看一下,如同runtime.h中的objc_class结构体定义一样,其实都已经失效了,真正的实现都被隐藏掉了,不过思路是正确的,依旧可以参考。

好,继续看isa_t是哪路神仙。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    // 以下两个数据成员,同一时间只能储存一个
    // 也就是说如果或者是cls或者是bits
    // 这样可以达到节省内存的目的
    Class cls;
    uintptr_t bits;
    
    /// 再再次省略下面的代码,下面代码要展开谈的话,篇幅太大,等下次单开一篇详谈。
}

原来isa_t是个union,对union不了解的同学可以看这篇文章,大概意思可以看上面那段代码的注释。

其中cls就不用多解释了吧,bits不仅仅保存了指针,并对此做了优化,其中还包含了引用计数,析构状态,被其他 weak 变量引用情况,将来一一细表。

到此从整体上浏览了一下Class的结构,其中发现了一个很有意思的现象:

objc_object里有isa_t结构体类型的isa,这个isa包含当前objc_object的类的信息,其中有objc_class的指针,然后

objc_class又继承于objc_object,所以里面又有isa。。绕来绕去有点让人懵逼。别怕,让我们细细的捋一捋,结合后面那张图品味,味道更佳。

前面说过了,OC中的类其实就是一个指向objc_class结构体的指针(Class Object类对象),为了把OC中的类和对象区分开来,引入了一个叫做元类的概念(Meta Class),类对象的isa成员(也就是类对象的类)就是元类。它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。当你发出一个类似 [NSObject alloc] 的消息时,你事实上是把这个消息发给了一个类对象,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 [NSObject alloc] 这条消息发给类对象的时候,objc_msgSend() 会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。

https://anyanf-img-1256234566.cos.ap-beijing.myqcloud.com/2018/class-diagram.png

关于Class的简单介绍就到此为止了,其实简单来说还是我上面说了好几次的,OC的实例对象就是一个指向objc_object的指针,objc_object中有一个isa_t的成员isa,isa包含当前对象指向的类的信息,OC中的类是一个objc_class的结构体,该结构体继承于objc_object,所以OC中的类也是一个对象,类对象中的isa_t结构体包含了类对象指向的元类(meta-class,元类也是objc_class类型)的信息。

如同绕口令一般。

OC对象

我们先定义一个类。

1
2
3
4
5
6
@interface FoobarClass : NSObject {
    
    NSString *foobarStr;
    int foobarInt;

}

还是通过 clang -rewrite-objc (把oc代码转写成c/c++代码)转之后,代码如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif

struct NSObject_IMPL {
	Class isa;
};


#ifndef _REWRITER_typedef_FoobarClass
#define _REWRITER_typedef_FoobarClass
typedef struct objc_object FoobarClass;
typedef struct {} _objc_exc_FoobarClass;
#endif

struct FoobarClass_IMPL {
	struct NSObject_IMPL NSObject_IVARS; // 通过这样的方式达到继承的目的
	NSString *foobarStr;
	int foobarInt;
};

通过以上代码可以看出FoobarClass类实例化的对象在内存中的是什么样子。

1
2
3
Class isa;
NSString *foobarStr;
int foobarInt;

之前说过OC的对象是指向结构体实例的指针,所以有两个结论:

  1. 成员isa的地址就是对象的首地址的值。
  2. 其他成员变量的地址就是对象的首地址地址加上相应的偏移量。

请看下面例子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@implementation FoobarClass

- (instancetype)init
{
    self = [super init];
    if (self) {
        
        foobarStr = @"hehe";
        
        NSLog(@"foobarStr address : %p", &foobarStr);

        foobarInt = 10;
        
        NSLog(@"foobarInt address : %p", &foobarInt);

        
    }
    return self;
}

@end

// 实例化一个对象
FoobarClass *foobar =  [FoobarClass new];
NSLog(@"foobar address : %p", foobar);

// 打印结果
foobarStr address : 0x10064cc68
foobarInt address : 0x10064cc70
foobar address : 0x10064cc60

https://anyanf-img-1256234566.cos.ap-beijing.myqcloud.com/2018/objc_obj_memory.png

在之前已经介绍过类对象和元类,以及他们的关系,在这里需要强调一下的是,一个OC对象的对象(-)方法,是在类对象里保存的;而类(+)方法,是在元类里保存的。

https://anyanf-img-1256234566.cos.ap-beijing.myqcloud.com/2018/objc-runtime-class.png

通过这个巧妙的设计,可以很方面的区分对象方法和类方法,使得OC中的对象和类达到了结构上的统一。方法的调用的逻辑看着也非常简约:

  1. 对象方法调用时,通过OC对象的isa在类中查找方法的实现。
  2. 类方法调用时,通过类对象的isa(OC对象的isa的isa)在元类中获取方法的实现。(OC对象的类方法,就是元类中的对象方法)

总结

为了有个整体的理解,开篇全讲类和对象了,很多东西都是浅尝辄止,后面会进行深入的探讨,一一补全。

参考

  1. Objective-C Runtime Reference

  2. Objective-C Runtime 运行时之一:类与对象

  3. Objective-C对象解析

  4. Objective-C Runtime