美文网首页
Runtime的简单应用

Runtime的简单应用

作者: Sunrain16 | 来源:发表于2017-06-05 15:32 被阅读57次

    自己最近在研究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传送门

    相关文章

      网友评论

          本文标题:Runtime的简单应用

          本文链接:https://www.haomeiwen.com/subject/yfqifxtx.html