Objective-C 的 RunTime(一):基础知识

本文深入探讨Objective-C的Runtime机制,包括其实现原理、对象结构、成员变量与方法的存储方式,以及消息传递机制等内容。

RunTime 简介

  • 什么是 RunTime?

    我们都知道,将源代码转换为可执行的程序,通常要经过 5 个步骤:预处理、编译、汇编、链接、运行。不同的编译型语言,在这 5 个步骤中所进行的操作又有些不同

    C 作为一门静态语言,在编译阶段就已经确定了所有变量的数据类型,同时也确定好了要调用的函数,以及函数的实现

    Objective-C 作为一门动态语言,在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的是哪个方法。只有在运行期间才检查变量的数据类型,同时在运行期间才会根据方法名查找要调用的具体方法。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么

    Objective-C 把一些决定性的工作从(编译阶段、链接阶段)推迟到(运行时阶段)的机制,使得 Objective-C 变得更加的灵活。我们甚至可以在程序运行的时候,动态地去修改一个方法的实现,这也为大为流行的『热更新』提供了可能性

    实现 Objective-C 运行时机制的一切基础就是 RunTime。RunTime 实际上是一个动态库,这个动态库使我们可以在程序运行时动态地创建对象、检查对象,修改类和对象的方法

实例对象 && 类对象 && 元类对象

  • Object(实例对象)

    Object(实例对象)被定义为指向 struct objc_object 的指针,其数据结构如下:

    // A pointer to an instance of a class.(指向一个类的一个实例的指针)
    // Objective-C 中的 id 类型,被定义为一个指向 struct objc_object 的指针,即被定义为一个指向实例对象的指针
    typedef struct objc_object *id;
    
    // Represents an instance of a class.(表示一个类的一个实例)
    struct objc_object {
         
         
        Class _Nonnull isa;	// 指向该实例对象所属的类
        // 编译时,根据开发者所定义的类的不同,编译器会以 struct objc_object 为模板,生成不同的 struct XXX_IMPL,并在 struct XXX_IMPL 中添加该类的实例对象所特有的成员变量
        // 成员变量 0
        // 成员变量 1
        // 成员变量 2
        // ...
    };
    

    当对一个实例对象进行成员变量访问时,比如 receiver->_ variable = 10;,RunTime 会根据成员变量在实例对象(struct objc_object)中的偏移量,找到相应的成员变量,然后进行访问

    当对一个实例对象进行方法调用时,比如 [receiver selector];,RunTime 会通过实例对象(struct objc_object)的 isa 指针找到对应的类对象(struct objc_class),然后在类对象(struct objc_class)的 methodLists(方法列表数组)中查找相应方法的地址,然后跳转执行

  • Class(类对象)

    Class(类对象)被定义为指向 struct objc_class 的指针,其数据结构如下:

    // An opaque type that represents an Objective-C class.(一个不透明的类型,用于表示 Objective-C 中的一个类)
    typedef struct objc_class *Class;
    
    struct objc_class {
         
         
        Class _Nonnull isa;	// is-a 指针,对象的 is-a 指针指向类对象,类对象的 is-a 指针指向元类对象,元类对象的 is-a 指针指向根元类对象
    #if !__OBJC2__
        Class _Nullable super_class;									// 父类
        const char * _Nonnull name;										// 类名
        long version;													// 类的版本信息,默认为 0
        long info;														// 类的信息,供运行时使用的一些标识位,如 CLS_CLASS(0x1L) 表示类对象,其中包含对象方法和成员变量。CLS_META(0x2L) 表示元类对象,其中包含类方法
        long instance_size;												// 该类的实例变量大小
        struct objc_ivar_list * _Nullable ivars;						// 该类的成员变量链表(一维)
        struct objc_method_list * _Nullable * _Nullable methodLists;	// 该类的方法列表链表(二维)
        struct objc_cache * _Nonnull cache;								// 该类的方法缓存
        struct objc_protocol_list * _Nullable protocols;				// 该类遵守的协议链表
    #endif
    };
    

    struct objc_class 中存储的是(用于描述类简要信息)与(用于描述实例对象详细信息)的变量,即 struct objc_class 存放的都是元数据(meta-data)

    struct objc_class 的第一个成员变量是 isa 指针,在 Objective-C 体系结构中,isa 指针用于表示一个对象所属的类。换句话说,在 Objective-C 中类的本质也是一个对象,称之为类对象

    在 Objective-C 中成员变量与属性都是依附在实例对象上的,并且在 Objective-C 中不存在所谓的 类成员变量 与 类属性

    当对一个类对象进行方法调用时,比如 [Receiver selector];,RunTime 会通过类对象(struct objc_class)的 isa 指针找到对应的元类对象(struct objc_class),然后在元类对象(struct objc_class)的 methodLists(方法列表数组)中找到相应方法的地址,然后跳转执行

  • Meta-Class(元类对象)

    在 Objective-C 中:
    一个 实例对象 所属的类叫做 类对象,用于描述实例对象本身所具有的特征
    一个 类对象 所属的类叫做 元类对象,用于描述类对象本身所具有的特征

    在 Objective-C 中,isa 指针用于表示一个对象所属的类:
    实例对象(struct objc_object)的 isa 指针指向类对象(struct objc_class
    类对象(struct objc_class)的 isa 指针指向元类对象(struct objc_class
    元类对象(struct objc_class)的 isa 指针指向根元类对象(struct objc_class
    因此,元类对象也用 struct objc_class 表示。与类对象不同的是,元类对象的 struct objc_class 结构体中存储的是(用于描述类对象详细信息)的变量

    对象方法的调用过程为:通过实例对象的 isa 指针找到其所属的类对象,在类对象的方法列表数组中寻找对应的 selector,然后跳转执行
    类方法的调用过程为:通过类对象的 isa 指针找到其所属的元类对象,在元类对象的方法列表数组中寻找对应的 selector,然后跳转执行

    举个例子:

    // stringWithFormat: 消息被发送给了 NSString 的类对象,NSString 的类对象通过自己的 isa 指针找到 NSString 的元类对象
    // 在 NSString 的元类对象的方法列表中找到对应的 stringWithFormat: 方法,然后执行该方法
    NSString* str = [NSString stringWithFormat:@"%@ = %d", @"result", 3];
    
  • 实例对象、类对象、元类对象 之间的关系
    实例对象、类对象、元类对象 之间的关系
    关于 isa 指针:

    1. 实例对象的 isa 指针指向类对象,类对象的 isa 指针指向元类对象
    2. 所有元类对象的 isa 指针都直接指向 NSObject 的元类对象(因此,NSObject 的元类对象也被称为根元类)
    3. 根元类的 isa 指针指向自己

    关于 superclass 指针:

    1. 实例对象没有 superclass 指针
    2. 类对象的 superclass 指针指向(父类的类对象),(父类的类对象)的 superclass 指针指向(根类的类对象),(根类的类对象)的 superclass 指针指向 nil
    3. 元类对象的 superclass 指针指向(父类的元类对象),(父类的元类对象)的 superclass 指针指向(根类的元类对象)
    4. (根类的元类对象)的 superclass 指针指向(根类的类对象),(根类的类对象)的 superclass 指针最终指向 nil

    注意:

    1. 同一个类的所有实例对象,有且仅有一个与之对应的类对象
    2. 每个类对象有且仅有一个与之对应的元类对象
    3. 每一个类对象有且仅有一个父类对象
    4. 每一个元类对象有且仅有一个父元类对象
  • 实例对象的内存布局

    Person 类继承自 NSObjectMan 类继承自 Person 类,Person 类和 Man 类各自有成员变量和属性,如下所示:
    Person 类
    Man 类
    使用 XCode 命令行工具,将 Man.m 转换成 Man.cpp:

    // 如果转换失败,请检查:XCode - Preferences... - Locations - Command Line Tool 选项是否正确地选择了当前的 XCode 版本
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Man.m -o Man.cpp
    

    在 Man.cpp 中得到表示 NSObject 实例对象、Person 实例对象、Man 实例对象 的结构体,如下所示:

    // RunTime 中表示 NSObject 实例对象的结构体
    struct NSObject_IMPL {
         
         
    	Class isa;
    };
    
    // RunTime 中表示 Person 实例对象的结构体
    struct Person_IMPL {
         
         
    	struct NSObject_IMPL NSObject_IVARS;
    	int personA;
    	NSString *personB;
    };
    
    // RunTime 中表示 Man 实例对象的结构体
    struct Man_IMPL {
         
         
    	struct Person_IMPL Person_IVARS;
    	int manA;
    	NSString *manB;
    	int _manC;
    	NSString * _Nonnull _manD;
    };
    
    // Man 实例对象的内存布局等价于
    struct Man_IMPL {
         
         
    	Class isa;					// 继承自 NSObject 类的成员变量
    	int personA;				// 继承自 Person 类的成员变量
    	NSString *personB;			// 继承自 Person 类的成员变量
    	int manA;					// Man 类自身的成员变量
    	NSString *manB;				// Man 类自身的成员变量
    	int _manC;					// Man 类自身的成员变量
    	NSString * _Nonnull _manD;	// Man 类自身的成员变量
    };
    
    // 注意:
    // 1.在 Objective-C 中,实例对象的本质是一个结构体
    // 2.实例对象结构体的第一个元素恒定为 isa 指针,指向其对应的类对象
    // 3.实例对象结构体中包含继承自父类的成员变量(@public、@protected)
    // 4.实例对象结构体中包含其自身所有的成员变量(@public、@protected、@private、@package)
    

成员变量 && 属性 && 方法 && 协议

  • Ivar(成员变量)

    Ivar(成员变量)被定义为指向 struct objc_ivar 的指针,其数据结构如下:

    // An opaque type that represents an instance variable.(一个不透明的类型,用于表示一个成员变量)
    typedef struct objc_ivar *Ivar;
    
    // 用于描述实例对象的单个成员变量
    struct objc_ivar {
         
         
        char * _Nullable ivar_name;	// 成员变量名称
        char * _Nullable ivar_type;	// 成员变量类型(并不是真实的成员变量类型,而是经过类型编码的 C 字符串)
        int ivar_offset;			// 成员变量相对于基地址的偏移量(Byte,基地址指的是实例对象结构体的首地址)
    #ifdef __LP64__
        int space;
    #endif
    };
    
    // 用于描述实例对象所具有的成员变量列表
    struct objc_ivar_list {
         
         
        int ivar_cou
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值