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传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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