RunTime原理之一:如何让Objective-C支持面向对象

2020-10-30  本文已影响0人  双鱼子曰1987

一、一切从面向对象开始说起

OC是一门基于C的面向对象的语言,这是如何做到的呢?下面我们简单分析窥探下这背后的密码。

「面向对象」有哪些特性呢?

二、循序渐进深入「面向对象」各种「概念」的封装

1、类(关键字Class

/// 类
typedef struct objc_class *Class;

struct objc_class {
    Class isa;                                // 实现方法调用的关键
    Class super_class;                        // 父类
    const char * name;                        // 类名
    long version;                             // 类的版本信息,默认为0
    long info;                                // 类信息,供运行期使用的一些位标识
    long instance_size;                       // 该类的实例变量大小
    struct objc_ivar_list * ivars;            // 该类的成员变量链表
    struct objc_method_list ** methodLists;   // 方法定义的链表
    struct objc_cache * cache;                // 方法缓存
    struct objc_protocol_list * protocols;    // 协议链表
};

其中关键字段:ivars(属性链表)、methodLists(「类实例方法」链表)、cache(方法缓存)、protocols(协议链表)、isa(指向类的元类)。

OC设计者,将设计成一个对象!从struct objc_class里面有个isa指针,可以推断出这点来。那么又是谁的对象呢?其实是「元类」,关于它将在第二章中说明。

2、对象(关键字id

/// 对象
struct objc_object {
    Class isa;
};

/// id指针
typedef struct objc_object *id;

struct objc_objectisa 指向的是「对象」的「类」;id实质 struct objc_object *指针,因此其可以指向任何对象。
例如NSObject *obj = [[NSObject alloc] init];,对象 obj 离的 isa指针 指向就是NSObject

3、方法(关键字SEL IMP

OC利用 @ selector 可以获取方法,其返回的是SEL,如SEL func = @selector(viewWillAppear:); 那么Runtime又是怎么封装的呢?
还是 struct objc_class 里面的方法列表 struct objc_method_list ** methodLists; 说起,看看结构体struct objc_method_list 是怎么定义的。

// 方法列表
struct objc_method_list {
    struct objc_method_list *obsolete ;

    int method_count;

    /* variable length structure */
    struct objc_method method_list[1];
}

// Method
typedef struct objc_method *Method;
struct objc_method {
    SEL method_name;
    char * method_types;
    IMP method_imp;
};

// SEL
typedef struct objc_selector *SEL;

// IMP
typedef id (*IMP)(id, SEL, ...); 
// 通过打印测试
SEL sel = @selector(viewWillAppear:);
NSLog(@"%s", sel);          // 输出:viewWillAppear:

推断而知,SEL其实就是字符串表示方法的名称。

也可以从NSObject提供的方法- (IMP)methodForSelector:(SEL)aSelector;+ (IMP)instanceMethodForSelector:(SEL)aSelector; 获取到「方法」的具体「实现IMP」。

4、成员变量(关键字Ivar

类结构体 struct objc_class 里面有一个成员变量列表 struct objc_ivar_list * ivars;,保存「类」的所有成员变量,它对应关键字是Ivar,它的结构体是struct objc_ivar

// 成员变量列表
struct objc_ivar_list {
    int ivar_count;

    /* variable length structure */
    struct objc_ivar ivar_list[1];
};

typedef struct objc_ivar *Ivar;
struct objc_ivar {
    char *ivar_name; // 变量名称
    char *ivar_type; // 变量类型名
    int ivar_offset; // 基地址偏移字节
}

结构体 struct objc_ivar 保存了「成员变量」的名称、类型,以及对应的偏移地址。

5、协议,Category

typedef struct category_t *Category;

struct category_t {
    const char *name;                                // 类名
    classref_t cls;                                  // 类,在运行时阶段通过 clasee_name(类名)对应到类对象
    struct method_list_t *instanceMethods;           // Category 中所有添加的对象方法列表
    struct method_list_t *classMethods;              // Category 中所有添加的类方法列表
    struct protocol_list_t *protocols;               // Category 中实现的所有协议列表
    struct property_list_t *instanceProperties;      // Category 中添加的所有属性
};

Category 的结构体定义中也可以看出, Category 可以为类添加对象方法、类方法、协议、属性。但是 Category 无法添加「成员变量」。

6、属性和属性特性(关键字Property

// 属性
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;

// 属性特性
typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

通过 clang 将OC代码转写为.cpp的相关代码可知,本质上 结构体struct objc_property 其实就是 结构体struct _prop_t,其大致结构如下:

struct _prop_t {
    const char *name;  //名称
    const char *attributes;  // readonly、assign的修饰属性
};
成员变量与属性的联系

本质上,一个属性一定对应一个成员变量。但是属性又不仅仅是一个成员变量,属性还会根据自己对应的属性特性的定义来对这个成员变量进行一系列的封装:提供 Getter/Setter 方法、内存管理策略、线程安全机制等等。

7、协议(关键字Protocol

struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};

// 宏__OBJC__保证只有OC文件可以调用.h里面的头文件,一些非OC语言不能调用
#ifdef __OBJC__  
@class Protocol;  // OC当做类
#else
typedef struct objc_object Protocol; 
#endif

实质上 Protocol 对应的结构体 struct objc_object,内部只有一个isa指针,指向对应的类。

8、cache

typedef struct objc_cache *Cache

struct objc_cache {
    unsigned int mask /* total = mask + 1 */  ;
    unsigned int occupied;
    Method buckets[1];
};

方法调用的时候,会现在缓存中查找,找到返回,否则再去方法列表中找。这样设计的目的在于提高方法调用效率。

三、总结

上面学习了Runtime对于面向对象概念的一些封装,其实质用C语言的结构体、指针、函数指针等;大量使用链表、map等作为存储;设计算法职称,如缓存,让Objective-C具有面向对象的能力。

Runtime针对上面的每一个结构体,都提供丰富API接口,允许对其进行操作。也正是如此设计,让Objective-C能在运行时,获取、创建、修改类相关的数据,具备强大的动态能力。

后面章节,我们将继续学习,Runtime是如何利用这些结构体实现面向对象的能力的。

其他

新手也看得懂的 iOS Runtime 教程

上一篇下一篇

猜你喜欢

热点阅读