Runtime的简单应用

自己最近在研究Runtime,研究好久才知道了一些大概和简单的应用。在这里做一个笔记。RunTime被称为iOS开发的黑魔法,功能之强大,简直就是装逼神器啊。自己也是摸索着前人的步伐,一步一步探索Runtime机制在开发中的使用。

1.什么是Runtime

Runtime是一套底层的C语言API,包含很多强大实用的C语言数据类型和C语言函数。平时我们编写的OC代码,都是基于Runtime实现的。OC是运行时语言,也只有在运行的时候才可以确定对象的类型,并调用类的对象的相应的方法,其中最主要的是消息机制。所以利用Runtime机制可以在程序运行的时候动态修改类的方法、类的对象的属性、方法、创建类别。这些应该是Runtime的基本用法吧,也是我们在平时的开发中用到的。

例如下边的这个方法在运行时会被转化:

/* OC方法调用 */

[obj makeTest];

/* 编译时Runtime会将上面的代码转为下面的消息发送 */

objc_msgSend(obj, @selector(makeText));

iOS的顶层基类NSObject含有一个指向objc_class结构体的isa指针:

@interface NSObject{

Class isa;

}

typedef struct objc_class *Class;

struct objc_class {

Class isa; // 指向metaclass,也就是静态的Class

Class super_class ; // 指向其父类

const char *name ; // 类名

long version ; // 类的版本信息,初始化默认为0

/* 一些标识信息,如CLS_CLASS(0x1L)表示该类为普通class;

CLS_META(0x2L)表示该类为metaclass */

long info;

long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);

struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址

/* 与info的一些标志位有关,如是普通class则存储对象方法,如是metaclass则存储类方法; */

struct objc_method_list **methodLists ;

struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;

struct objc_protocol_list *protocols; // 存储该类遵守的协议

}

在objc_msgSend函数的调用过程:

1.首先通过obj的isa指针找到obj对应的Class。

2.在Class中先去cache中通过SEL查找对应函数method

3.若cache中未找到,再去methodLists中查找

4.若methodLists中未找到,则进入superClass按前面的步骤进行递归查找

5.若找到method,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。

6.如果一直查找到NSObject还没查找到,则会进入消息动态处理流程。

消息动态处理流程:

/* 1. 时机处理之一,在这个方法中我们可以利用runtime的特性动态添加方法来处理 */

+ (BOOL)resolveInstanceMethod:(SEL)sel;

/* 2. 时机处理之二,在这个方法中看代理能不能处理,如果代理对象能处理,则转接给代理对象 */

- (id)forwardingTargetForSelector:(SEL)aSelector;

/* 3. 消息转发之一,该方法返回方法签名,如果返回nil,则转发流程终止,抛出异常 */

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

/* 4. 消息转发之二,在该方法中我们可以对调用方法进行重定向 */

- (void)forwardInvocation:(NSInvocation *)anInvocation;

2.Runtime的应用场景

1>.程序运行过程中动态创建类,如:KVO的实现

2>.程序运行过程中动态修改对象的属性、方法

3>.遍历一个类的所有成员变量、方法

4>.交换方法

5>.运行时创建类

3.场景举例:

(1).动态创建类---实现自己的KVO监听

KVO监听相信大家在平时开发中都有用到过,但是它是怎么监听到属性变化的呢?懒加载大家都有用到吧,自己重写属性的set方法。对,KVO就是监听属性的set方法。让我们先看第一张图:

截图1

图中实例对象p的isa指针指向的是Person类,让我们单步往下走

截图2

现在p的isa指针指向的是NSKVONotifying_Person这个类。在程序运行的时候,苹果利用runtime机制动态的创建了一个继承Person类的NSKVONotifying_Person这个类,并将isa指针动态指向子类方法。苹果在动态创建的NSKVONotifying_Person这个类中重写父类属性的set方法,方法实现中再调用父类的set方法。

重写父类属性的set方法

现在我们自己利用Runtime实现自己的KVO监听,关键代码如下:

-(void)LR_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{

//获取类名

NSString* oldClass = NSStringFromClass([self class]);

NSString* newClass = [@"LR" stringByAppendingString:oldClass];

const char* name = [newClass UTF8String];

//1.动态生成一个类

Class myClass = objc_allocateClassPair([self class], name, 0);

//添加一个方法

class_addMethod(myClass, @selector(setName:), (IMP)setName, "");

//2.注册这个类

objc_registerClassPair(myClass);

//3.修改isa指针

object_setClass(self, myClass);

//4.实现setName方法

void setName(id self, SEL _cmd,NSString* newName){

NSLog(@"我来饿了");

}

(2).程序运行过程中动态修改对象的属性、方法

我们在demo中调用Person类一个没有实现的方法,然后command+R会怎样?Crash!!!

Person* p = [[Person alloc] init];

[p performSelector:@selector(run)];

现在我们可以利用runtime机制实现方法的懒加载。

关键代码如下:

+(BOOL)resolveInstanceMethod:(SEL)sel{

if (sel == @selector(run)) {

//1.cls 类类型

//2.name 方法编号

//3.imp 方法实现。函数指针指向一个实现

//4.types 返回值类型

class_addMethod([Person class], sel, (IMP)haha, "v");

}

/*

第四个参数的含义:

v表示void,@表示id类型,:表示SEL类型

"v@:@":表示返回值为void,接受一个id类型、一个SEL类型、一个id类型的方法

"@@:":表示返回值为id类型,接受一个id类型和一个SEL类型参数的方法

*/

return [super resolveInstanceMethod:sel];

}

实现动态添加的方法的实现

void haha(id self ,SEL _cmd){

NSLog(@"%@===%@",self,NSStringFromSelector(_cmd));

NSLog(@"你说啥");

}

当一个类调用了没有实现的方法,就会来到runtime的这个方法resolveInstanceMethod,进行方法的寻找,如果子类中没有方法的实现,就会在父类中寻找,如果父类也没有,就往父类的父类取寻找。所以在这个方法中我们使用class_addMethod方法动态的为Person类添加方法。

注:在所有的方法中都会隐式接收2个参数----self,_cmd。self调用的类,_cmd方法的编码名。这两个参数只有你传入之后才可以在方法实现中拿到。

(3).遍历一个类的所有成员变量、方法

主要用到的方法如下:

class_copyIvarList--->获取类的成员变量列表-->多用于字典转模型,归解档的操作。(有兴趣的可以研究一下MJExtension的内部实现,受益匪浅)

class_copyPropertyList--->获取类的属性列表

代码如图:

截图3

(4).交换方法

通过runtime的method_exchangeImplementations方法来实现方法的互换(实际是利用runtime改变了两个方法的isa指针指向)。一般用自己写的方法(常用在自己封装的类或写的框架中,添加某些防错措施)来替换系统的方法,如:

在数组中的越界访问或数组使用addObject方法添加元素为nil时导致的程序崩溃。可以新建一个分类实现方法的交换来防止程序的崩溃,如图:

新建分类添加方法交换

(5).动态添加一个类

动态添加一个类

4.Runtime的简单应用

当我们的项目越做越大越复杂的时候,建立的控制器也会越来越多,相应的跳转也会增加。特别是你接收一个大项目的时候,对整体的业务逻辑不熟悉,整体的架构体系也是一头雾水,然而你又要修复某个页面的BUG,估计要找到对应的页面都要找好久。有没有一种方式可以快速找到某个页面对应的ciewController呢?

方案一、在整个项目建立初期构建一个基类viewController,此后创建的VC均继承于基类。我们只需要重写基类的viewWillAppear方法。

- (void)viewWillAppear:(BOOL)animated {

[super viewWillAppear:animated];

NSString *className = NSStringFromClass([self class]);

NSLog(@"%@ will appear", className);

}

方案二、给viewController创建一个分类,在分类里边进行方法的替换。这里所说的方法替换并不是使用method_exchangeImplementations改变两个方法的isa指针指向。而是先拿到系统方法的IMP实现,然后构建一个新的符合我们需求的IMP实现来替代系统方法的IMP实现。如图:

改变方法的IMP实现

具体代码如下:

修改IMP实现

5.Runtime使用心得

Runtime很强大,这只是我的一个初步的了解,对于很多东西不是很了解。这应该算是Runtime的一个基础用法吧。不过黑魔法就是用着酸爽。要理解透彻,并且在开发中熟练应用感觉任重道远啊。

RuntimeDemo传送门

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,636评论 0 9
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,097评论 0 9
  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 570评论 0 2
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,110评论 0 7
  • “你是不是喜欢我?”余知霖擦擦眼泪,擤了擤鼻子。坐在公园的长椅上,侧着脑袋问宋民宇。 “我以为我做的够明显的了。”...
    阿九小叔阅读 380评论 0 0