ios -- Objc --Runtime(1) :理解 Objective-C Runtime

C/C++ 属于静态的语言;而ObjC属于动态语言。
什么是静态语言?
就是在编译器编译后就调用函数地址,代码结构就固定了,无法在运行的时候改变

什么是动态语言?
该语言将很多静态语言在编译和链接时期做的事放到了运行时来处理,比如: 在运行的时候才知道方法的具体实现在什么地方,程序在运行时可以改变其结构,

什么原因造成了oc 是动态语言?
答案:就是运行时

什么又是运行时?
Objective-C Runtime是一个运行时库, 包含两大组件——编译器和Runtime API ,他们是汇编和c语言编写的, 运行时为c语言提供了面向对象的功能,进而创造了OC 这门语言[ OC运行时本质上就是给OC面向对象编程提供了可能 ] , 也就是说运行时需要提供加载对象, 方法分发/转发等功能

一: runtime编译器

作用: 把任何 Objective-C 的代码编译为 Runtime 的 C 代码。这个过程中,会把 Objective-C 的数据结构编译为 Runtime 的 C 的数据结构。把 Objective-C 的消息传递编译为 Runtime 的 C 函数调用。

下面看一下Objective_c中各种元素在Runtime 中对应的是什么类型【数据结构】?

1: 对象

Runtime 的核心数据结构就是对象,对象在 Runtime 中是一个名为 objc_object 的结构体。也就是说Objective-C 中的任何对象都是用 objc_object 来表示的。

struct objc_object {
private:
       isa_t isa;
public:
       // ...
}

objc_object 中最关键的部分是一个 isa_t 类型的 isa 变量。isa_t 类型使用了 Tagged Pointer 技术来减小内存空间的占用。isa 指针主要保存了对象关联的类的信息。

id 的定义:

typedef struct objc_object *id;

id 是一个指向 objc_object 类型的指针,因此 id 可以代表任何对象。

2: 类

类在runtime 中表示为objc_class 类型

struct objc_class {
       Class isa;
}

其中: 类的 isa 指针指向它的元类。
Class 的定义:

typedef struct objc_class *Class;

也就是说类和他的元类都是同一种类型

3: 元类

元类中保存了这个类的类方法的地址。元类的 isa 指针指向元类对应的类的父类的元类。
提个问题,类方法是通过元类找到, 一般对象方法是根据啥找到? 后续解答

4: 方法

方法在 Runtime 中用 Method 类型来表示,从 Runtime 的源码可以看到,Method 类型是一个指向 method_t 结构体类型的指针。

typedef struct method_t *Method;
struct method_t {
      SEL name;  // 称为方法子,也就是封装了方法名的数据结构objc_selector
      const char *types; //types – 表示该方法参数的类型 比如 
      IMP imp;  // IMP 是一个函数指针,也就是函数实现地址 
      // ...
};
其中: 
      typedef struct objc_selector   *SEL;   
      typedef id (*IMP)(id, SEL, …);

IMP 是一个函数指针,这个函数指针包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id,我们可以通过NSObject 类中的methodForSelector:方法就是这样一个获取指向方法实现IMP 的指针,
例如:

    void (*setter)(id, SEL, BOOL);
    setter = (void(*)(id, SEL, BOOL))[target       
    methodForSelector:@selector(setFilled:)];

函数调用:

    setter(targetList[i], @selector(setFilled:), YES);

注意,methodForSelector:是Cocoa运行时系统的提供的功能,而不是Objective-C语言本身的功能
一般我们源码中不使用IMP,SEL ,但是在源码中使用也是可以的,比如:

     id target = getTheReceiver();

     SEL method = getTheMethod();

     [target performSelector:method]; 
5: 实例变量
typedef struct ivar_t *Ivar;

    struct ivar_t {

        int32_t *offset;

        const char *name;

        const char *type;

        // ...

    };
6 属性
    typedef struct property_t *objc_property_t;

        struct property_t {

            const char *name;

            const char *attributes;

        };
7:协议
        struct protocol_t : objc_object {

            const char *mangledName;

            struct protocol_list_t *protocols;

            method_list_t *instanceMethods;

            method_list_t *classMethods;

            method_list_t *optionalInstanceMethods;

            method_list_t *optionalClassMethods;

              property_list_t *instanceProperties;

            // ...

        };

8:Objective-C 方法和隐含参数

对于每一个 Objective-C 的方法,例如:

- (void)printCount:(NSInteger)count {

    // ...

}

编译器都会将其编译成一个 C 函数,上面的方法会被编译成:

void foo(id self, SEL _cmd, int count) {

    // ...

}

这个 C 函数的第一个和第二个参数就是隐含参数,在 Objective-C 的方法体中,也是可以直接使用的。编译为 C 函数后,需要在函数声明中明确的声明。

9: 消息传递

 [receiver message];

 编译器会把它编译成对 C 函数 objc_msgSend 的调用。

 objc_msgSend(receiver, @selector(message));

上述objc_class是简单描述,下面具体描述一下:

1》 类 具体数据结构类型

struct objc_class {

    struct objc_class * isa; /* 指向元类,元类里面存放这所有的类方法*/

    struct objc_class * super_class;  /*父类*/

    const char *name;                 /*类名字*/

    long version;                   /*版本信息*/

    long info;                        /*类信息*/

    long instance_size;               /*实例大小*/

    struct objc_ivar_list *ivars;     /*实例参数链表*/

    struct objc_method_list **methodLists;  /*方法链表*/

    struct objc_cache *cache;               /*方法缓存*/

    struct objc_protocol_list *protocols;   /*协议链表*/

};//  存放类的结构的对象 isa 也称为元类对象

二: Runtime API

API 主要有下面的类型:

  • objc_

  • class_

  • object_

  • method_

  • property_

  • protocol_

  • ivar_ ,sel_ ,imp_

1.objc_xxx 系列函数

函数名称 函数作用

objc_getClass 获取Class对象

objc_getProtocol 获取某个协议

objc_getMetaClass 获取MetaClass对象

objc_copyProtocolList 拷贝在运行时中注册过的协议列表

objc_allocateClassPair 分配空间,创建类(仅在 创建之后,注册之前 能够添加成员变量)

objc_registerClassPair 注册一个类(注册后方可使用该类创建对象)

objc_disposeClassPair 注销某个类

objc_allocateProtocol 开辟空间创建协议

objc_registerProtocol 注册一个协议

objc_constructInstance 构造一个实例对象(ARC下无效)

objc_destructInstance 析构一个实例对象(ARC下无效)

objc_setAssociatedObject 为实例对象关联对象

objc_getAssociatedObject 获取实例对象的关联对象

objc_removeAssociatedObjects 清空实例对象的所有关联对象

objc_msgSend 发送ObjC消息

objc_ 系列函数关注于宏观使用,如类与协议的空间分配,注册,注销等操作

2.class_xxx 系列函数

函数名称 函数作用

class_addIvar 为类添加实例变量

class_addProperty 为类添加属性

class_addMethod 为类添加方法

class_addProtocol 为类遵循协议

class_replaceMethod 替换类某方法的实现

class_getName 获取类名

class_isMetaClass 判断是否为元类

class_getSuperclass 获取某类的父类

class_setSuperclass 设置某类的父类

class_getProperty 获取某类的属性

class_getInstanceVariable 获取实例变量

class_getClassVariable 获取类变量

class_getInstanceMethod 获取实例方法

class_getClassMethod 获取类方法

class_getMethodImplementation 获取方法的实现

class_getInstanceSize 获取类的实例的大小

class_respondsToSelector 判断类是否实现某方法

class_conformsToProtocol 判断类是否遵循某协议

class_createInstance 创建类的实例

class_copyIvarList 拷贝类的实例变量列表

class_copyMethodList 拷贝类的方法列表

class_copyProtocolList 拷贝类遵循的协议列表

class_copyPropertyList 拷贝类的属性列表

class_系列函数关注于类的内部,如实例变量,属性,方法,协议等相关问题

3.object_xxx 系列函数

函数名称 函数作用

object_copy 对象copy(ARC无效)

object_dispose 对象释放(ARC无效)

object_getClassName 获取对象的类名

object_getClass 获取对象的Class

object_setClass 设置对象的Class

object_getIvar 获取对象中实例变量的值

object_setIvar 设置对象中实例变量的值

object_getInstanceVariable 获取对象中实例变量的值 (ARC中无效,使用object_getIvar)

object_setInstanceVariable 设置对象中实例变量的值 (ARC中无效,使用object_setIvar)

objcet_系列函数关注于对象的角度,如实例变量

4.method_xxx 系列函数

函数名称 函数作用

method_getName 获取方法名

method_getImplementation 获取方法的实现

method_getTypeEncoding 获取方法的类型编码

method_getNumberOfArguments 获取方法的参数个数

method_copyReturnType 拷贝方法的返回类型

method_getReturnType 获取方法的返回类型

method_copyArgumentType 拷贝方法的参数类型

method_getArgumentType 获取方法的参数类型

method_getDescription 获取方法的描述

method_setImplementation 设置方法的实现

method_exchangeImplementations 替换方法的实现

method_系列函数关注于方法内部,如果方法的参数及返回值类型和方法的实现

5.property_xxx 系列函数

函数名称 函数作用

property_getName 获取属性名

property_getAttributes 获取属性的特性列表

property_copyAttributeList 拷贝属性的特性列表

property_copyAttributeValue 拷贝属性中某特性的值

property_系类函数关注与属性*内部,如属性的特性等

6.protocol_xxx 系列函数

函数名称 函数作用

protocol_conformsToProtocol 判断一个协议是否遵循另一个协议

protocol_isEqual 判断两个协议是否一致

protocol_getName 获取协议名称

protocol_copyPropertyList 拷贝协议的属性列表

protocol_copyProtocolList 拷贝某协议所遵循的协议列表

protocol_copyMethodDescriptionList 拷贝协议的方法列表

protocol_addProtocol 为一个协议遵循另一协议

protocol_addProperty 为协议添加属性

protocol_getProperty 获取协议中的某个属性

protocol_addMethodDescription 为协议添加方法描述

protocol_getMethodDescription 获取协议中某方法的描述

7.ivar_xxx 系列函数

函数名称 函数作用

ivar_getName 获取Ivar名称

ivar_getTypeEncoding 获取类型编码

ivar_getOffset 获取偏移量

8.sel_xxx 系列函数

函数名称 函数作用

sel_getName 获取名称

sel_getUid 注册方法

sel_registerName 注册方法

sel_isEqual 判断方法是否相等

9.imp_xxx 系列函数

函数名称 函数作用

imp_implementationWithBlock 通过代码块创建IMP

imp_getBlock 获取函数指针中的代码块

imp_removeBlock 移除IMP中的代码块

还有一些方便的函数

我们可以通过NSObject的一些方法获取运行时信息或动态执行一些消息:

isKindOfClass 和 isMemberOfClass检查对象是否在指定的类继承体系中;

respondsToSelector 检查对象能否相应指定的消息;

conformsToProtocol 检查对象是否实现了指定协议类的方法;

methodForSelector 返回指定方法实现的地址。

performSelector:withObject 执行SEL 所指代的方法。

函数调用举例:

image.png
image.png
image.png
image.png

三: 代码编译实例

因为 Objective-C 的源代码都会被编译成 Runtime 代码来运行,我们一样可以通过直接编写 Runtime 代码的方式来编写程序。

举例:

我们有个类叫 ClassA:

@interface ClassA : NSObject

@property (nonatomic, assign) NSInteger count;

  • (void)printCount;

@end

@implementation ClassA

  • (void)printCount {

    NSLog(@"count = %@", @(self.count));

}

@end

然后来执行调用:

ClassA *a = [[ClassA alloc] init];

a.count = 100;

[a printCount];

下面来看看下面代码转成 Runtime 怎么写【自己写的】?

// 获取到 ClassA 的 Class 对象

Class ClassA = objc_getClass("ClassA");

// 发送 alloc 和 init 消息来创建和初始化实例对象

id a = objc_msgSend(ClassA, @selector(alloc));

a = objc_msgSend(a, @selector(init));

// 获取到属性 count 背后的实例变量

Ivar countIvar = class_getInstanceVariable(ClassA, "_count");

assert(countIvar);

// 通过实例对象首地址 + 实例变量的地址偏移量得到实例变量的指针地址,然后通过 * 取指针值操作符修改指针指向的地址的值。

CFTypeRef aRef = CFBridgingRetain(a);

int *countIvarPtr = (int *)((uint8_t *)aRef + ivar_getOffset(countIvar));

*countIvarPtr = 100;

CFBridgingRelease(aRef);

// 给 a 对象发送 printCount 消息,打印 count 属性的值

objc_msgSend(a, @selector(printCount));

再看看通过clang编译后输出的和我们写的有什么区别?

执行命令: clang -rewrite-objc main.m

结果如下:

int main(int argc, const char * argv[]) {

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

  // 一次性发送消息init和alloc 进而得到ClassA的对象

    ClassA *a = ((ClassA *(*)(id, SEL))(void *)objc_msgSend)((id)((ClassA *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("ClassA"), sel_registerName("alloc")), sel_registerName("init"));

   // 执行setCount 方法进行赋值

    ((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)a, sel_registerName("setCount:"), (NSInteger)100);

   // 执行printCount 方法

    ((void (*)(id, SEL))(void *)objc_msgSend)((id)a, sel_registerName("printCount"));

}

return 0;

}

推荐阅读更多精彩内容