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;

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容