深入浅出Runtime (一) 什么是Runtime? 定义?

已更新
深入浅出Runtime (二) Runtime的消息机制
深入浅出Runtime (三) Runtime的消息转发
深入浅出Runtime (四) Runtime的实际应用之一,字典转模型

深入浅出 Runtime详解

Runtime是什么?

  • 运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时
  • Runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API
  • 平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,Runtime是Object-C的幕后工作者
  • Object-C需要Runtime来创建类和对象,进行消息发送和转发

更新(上面描述并不是不对,而是觉得不严谨)

  • 将尽可能多的决策从编译时和链接时推迟到运行时(Apple
  • 运行时系统充当着Object-C语言的操作系统,它使语言能够工作(Apple)

特性: 编写的代码具有运行时、动态特性

Runtime用来干什么?用在哪些地方?

Runtime在Object-C的使用
Objective-C程序在三个不同的层次上与运行时系统交互:

  • 通过Object-C源代码进行交互
  • 通过NSObject类中定义的方法交互
  • 通过直接调用运行时函数

用来干什么 基本作用

  • 在程序运行过程中,动态的创建类,动态添加、修改这个类的属性和方法;
  • 遍历一个类中所有的成员变量、属性、以及所有方法
  • 消息传递、转发

用在哪些地方 Runtime的典型事例

  • 给系统分类添加属性、方法
  • 方法交换
  • 获取对象的属性、私有属性
  • 字典转换模型
  • KVC、KVO
  • 归档(编码、解码)
  • NSClassFromString class<->字符串
  • block
  • 类的自我检测
  • ...

Runtime的定义

Runtime开源代码

在Object-C中的NSObject对象中

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

上述表述Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

由此可见可以看到Classid 前者是类,后者指向类的指针,id是指向objc_object的一个指针,而objc_object有个isa指向objc_class的一个指针
So,不管id,还是Class最后指向的都是objc_class这个结构体
objc_class结构体中的定义如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

在runtime使用当中,我们经常需要用到的字段,它们的定义

  • isa Class对象,指向objc_class结构体的指针,也就是这个ClassMetaClass(元类)
    - 类的实例对象的 isa 指向该类;该类的 isa 指向该类的 MetaClass
    - MetaCalss的isa对象指向RootMetaCalss
  • super_class Class对象指向父类对象
    - 如果该类的对象已经是RootClass,那么这个super_class指向nil
    - MetaCalss的SuperClass指向父类的MetaCalss
    - MetaCalss是RootMetaCalss,那么该MetaClass的SuperClass指向该对象的RootClass

一张图可以完美的解释这个知识点


  • ivars 类中所有属性的列表,使用场景:我们在字典转换成模型的时候需要用到这个列表找到属性的名称,去取字典中的值,KVC赋值,或者直接Runtime赋值

  • methodLists 类中所有的方法的列表,类中所有方法的列表,使用场景:如在程序中写好方法,通过外部获取到方法名称字符串,然后通过这个字符串得到方法,从而达到外部控制App已知方法。

  • cache 主要用于缓存常用方法列表,每个类中有很多方法,我平时不用的方法也会在里面,每次运行一个方法,都要去methodLists遍历得到方法,如果类的方法不多还行,但是基本的类中都会有很多方法,这样势必会影响程序的运行效率,所以cache在这里就会被用上,当我们使用这个类的方法时先判断cache是否为空,为空从methodLists找到调用,并保存到cache,不为空先从cache中找方法,如果找不到在去methodLists,这样提高了程序方法的运行效率

  • protocols 故名思义,这个类中都遵守了哪些协议,使用场景:判断类是否遵守了某个协议上

深入Runtime的运行原理

当我写到深入Runtime的运行原理的时候,脑海中冒出的想法是怎么深入,从哪里开始挖掘runtime的内容:

第一个想法就是

  • 介绍runtime的几个方法
  • runtime的使用方法
  • runtime的实际操作场景、应用
  • runtime的总结

如果说这些就是深入runtime,就是调用下api

然后想了很久,每次都会想就直接介绍使用,总结完事;心里贼不得劲
不能就这么简简单单了事,那么开始想到哪点,深入做哪点深入

大纲(后续会补充):

  • 类底层代码、类的本质?
  • 类底层是如何调用方法?
  • Runtime消息传递
  • Runtime消息转发
  • Runtime起到了什么作用?
  • Runtime实际应用

类底层代码、类的本质?

为了更好的认识类是怎么工作的,我们将要将一段Object-C的代码用clang看下底层的C/C++的写法

typedef enum : NSUInteger {
    ThisRPGGame = 0,
    ThisActionGame = 1,
    ThisBattleFlagGame = 2,
} ThisGameType;


@interface Game : NSObject
@property (copy,nonatomic)NSString *Name;
@property (assign,nonatomic)ThisGameType Type;
@end

@implementation Game
@synthesize Name,Type;

- (void)GiveThisGameName:(NSString *)name{
    Name = name;
}

- (void)GiveThisGameType:(ThisGameType)type{
    Type = type;
}

@end

使用命令,在当前文件夹中会出现Game.cpp的文件

# clang -rewrite-objc Game.m

Game.cpp
由于生成的文件很庞大,可以仔细去研读,受益匪浅

研读方式:如果按照从上往下的顺序去研读,会很不理解,所以我的研读方式从关键点切入首先理解关键的几点,然后在慢慢抛析

/*
 * 顾名思义存放property的结构体
 * 当我们使用perproty的时候,会生成这样一个结构体
 * 具体存储的数据为 
 * 实际内容:"Name","T@\"NSString\",C,N,VName" 
 * 原型:@property (copy,nonatomic)NSString *Name;
 * 这个具体是怎么实现的,我会在后面继续深入研究,本文主要来理解runtime的理解
 **/
struct _prop_t {
    const char *name;        //名字
    const char *attributes;  //属性
};

/*
 *类中方法的结构体,cmd和imp的关系是一一对应的关系
 *创建对象生成isa指针,指向这个对象的结构体时 
 *同时生成了一个表"Dispatch table"通过这个_cmd的编号找到对应方法
 *使用场景:
 *例如方法交换,方法判断。。。
 **/ 
struct _objc_method {
    struct objc_selector * _cmd;   //SEL 对应着OC中的@selector()
    const char *method_type;       //方法的类型
    void  *_imp;                   //方法的地址
}; 


/*
 * method_list_t 结构体:
 * 原型:
 * - (void)GiveThisGameName:(NSString *)name;
 * 实际存储的方式:
 * {(struct objc_selector *)"GiveThisGameName:", "v24@0:8@16", (void *)_I_Game_GiveThisGameName_}
 * 其主要目的是存储一个数组,基本的数据类型是 _objc_method
 * 扩展:当然这其中有你的属性,自动生成的setter、getter方法
 **/
 
static struct _method_list_t {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[6];
}

/*
 * 表示这个类中所遵守的协议对象
 * 使用场景:
 * 判断类是否遵守这个协议,从而动态添加、重写、交换某些方法,来达到某些目的
 * 
 **/

struct _protocol_t {
    void * isa;  // NULL
    const char *protocol_name;
    const struct _protocol_list_t * protocol_list; // super protocols
    const struct method_list_t *instance_methods;  // 实例方法
    const struct method_list_t *class_methods;     //类方法
    const struct method_list_t *optionalInstanceMethods;  //可选的实例方法
    const struct method_list_t *optionalClassMethods;  //可选的类方法
    const struct _prop_list_t * properties;  //属性列表
    const unsigned int size;  // sizeof(struct _protocol_t)
    const unsigned int flags;  // = 0
    const char ** extendedMethodTypes;  //扩展的方法类型
};

/*
 * 类的变量的结构体
 * 原型:
 * NSString *Name;
 * 存储内容:
 * {(unsigned long int *)&OBJC_IVAR_$_Game$Name, "Name", "@\"NSString\"", 3, 8}
 * 根据存储内容可以大概了解这些属性的工作内容
 **/
struct _ivar_t {
    unsigned long int *offset;  // pointer to ivar offset location
    const char *name;  //名字
    const char *type;  //属于什么变量
    unsigned int alignment; //未知
    unsigned int  size;    //大小
};


/*
 *  这个就是类中的各种方法、属性、等等信息
 *  底层也是一个结构体
 *  名称、方法列表、协议列表、变量列表、layout、properties。。
 *  
 **/
struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;  //布局
    const char *name;  //名字
    const struct _method_list_t *baseMethods;//方法列表 
    const struct _objc_protocol_list *baseProtocols; //协议列表
    const struct _ivar_list_t *ivars;  //变量列表
    const unsigned char *weakIvarLayout;  //弱引用布局
    const struct _prop_list_t *properties;  //属性列表
};

/*
 * 类本身
 * oc在创建类的时候都会创建一个 _class_t的结构体
 * 我的理解是在runtime中的object-class结构体在底层就会变成_class_t结构体
 **/
struct _class_t {
    struct _class_t *isa;  //元类的指针
    struct _class_t *superclass; //父类的指针
    void *cache;   //缓存
    void *vtable;  //表信息、未知
    struct _class_ro_t *ro;  //这个就是类中的各种方法、属性、等等信息
};


/*
 * 类扩展的结构体
 * 在OC中写的分类
 **/
struct _category_t {
    const char *name;  //名称
    struct _class_t *cls;  //这个是哪个类的扩展
    const struct _method_list_t *instance_methods;  //实例方法列表
    const struct _method_list_t *class_methods;     //类方法列表
    const struct _protocol_list_t *protocols;       //协议列表
    const struct _prop_list_t *properties;          //属性列表
};

上述是Object-C中类中基本的数据,了解了类的定义,我们基本可以这么理解,类就是多个结构体组合的一个集合体,类中的行为、习惯、属性抽象,按照机器能懂的数据存储到我们底层的结构体当中,在我们需要使用的时候直接获取使用。

那么就开始研究一下,类是如何使用,类的基本使用过程以及过程中runtime所做的事情。

类底层是如何调用方法?

了解了类的组成,那么类是通过什么样的形式去获取方法属性并得到应用?
在Object-C开发中我们经常会说到,对象调用方法,其本质就是想这个对象发送消息,为什么会有这么一说?下面我们来验证一下

Object-C代码

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

    Game *game = [Game alloc];
    [game init];
    [game Play];
    return  0;
}

底层代码的实现

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

    Game *game = ((Game *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Game"), sel_registerName("alloc"));
    game = ((Game *(*)(id, SEL))(void *)objc_msgSend)((id)game, sel_registerName("init"));
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)game, sel_registerName("Play"));
    return 0;
}

代码中使用了

  • objc_msgSend 消息发送
  • objc_getClass 获取对象
  • sel_registerName 获取方法的SEL

因为目前重点是objc_msgSend,其他的Runtime的方法会在后面继续一一道来, So 一个对象调用其方法,在Object-C中就是向这个对象发送一条消息,消息的格式

objc_msgSend("对象","SEL","参数"...)
objc_msgSend( id self, SEL op, ... )

总结

Rumtime是Objective-C语言动态的核心,Objective-C的对象一般都是基于Runtime的类结构,达到很多在编译时确定方法推迟到了运行时,从而达到动态修改、确定、交换...属性及方法

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

推荐阅读更多精彩内容