RunTime的用法

作者: PaulLi哥 | 来源:发表于2016-05-13 19:14 被阅读1384次

    RunTime简介##

    刚入行的时候,经常听到某些自称大神的人说runtime怎么怎么强大怎么怎么牛逼,我总是被忽悠的一愣一愣。但是当你问他runtime到底是什么的时候?他就只会含含糊糊告诉你三个字:运行时。。。听到这我只能说:你说的好有道理,我竟然无言以对!!!但这个东西到底是什么?到底能用在哪里呢?下面简单讲一下自己的理解。为了不把大家搞懵逼,下面我先通过几个实例讲一下runtime到底用在什么地方。

    RunTime的应用场景##

    • 1.给分类添加一个属性(本质上只是添加了关联,类似于属性的作用)

    当我们看一些第三方框架的时候我们可能看到这样的代码,例如在SDWebImage中

    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_getAssociatedObject(self, &imageURLKey);
    

    SDWebImage是UIImageView的一个分类。我们可以使用方法sd_setImageWithURL根据URL去下载图片,但是如果我们的URL地址在后期要用到,我们如何把URL保存在下来呢?我们都知道通过分类可以添加方法,但是添加属性就无能为力了。这个时候我们的runtime就派上用场了,通过上边的两个函数,我们就能动态的为我们的分类添加属性了。通过objc_setAssociatedObject就能把URL 保存下来,在需要用到的时候通过objc_getAssociatedObject取出URL的值。

    • 2.动态的交换方法的实现

    大家可能看到过runtime中有这么一个函数method_exchangeImplementations,它能在运行时动态的交换两个方法的具体实现。

     + (void)load {
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
    

    但是具体可以用在哪里呢?之前看到一种非常有趣的做法:在我们做项目的后期,我们可能需要在各个页面加上统计事件,大部分时候是通过在每个控制器的viewDidAppear和viewDidDisappear等方法中加入统计。如果我们的项目比较大,加起来就比较麻烦,但是通过runtime我们可以轻松解决这个问题。首先我们需要写一个方法,在此方法中加入统计事件,然后在UIViewController的load方法中通过method_exchangeImplementations交换viewDidAppear的实现,这样我们就能轻松解决这个问题。添加页面的统计以及点击事件的统计可以参考下边这篇文章,作者写的非常棒:http://www.cocoachina.com/ios/20160421/15912.html

    • 3.通过runtime自动实现归档

    在开发中,我们很多时候需要对数据进行归档,但是如果一个Model中的属性比较多的时候,我们可能会被搞得头昏脑涨,十分影响我们的开发效率。这个时候我们可以通过runtime来帮我们解决这个问题。大致实现如下:

    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        if (self = [super init]) {
            unsigned int count = 0;
            Ivar *vars = class_copyIvarList([self class], &count);
            for (int i = 0; i < count; i++) {
                const char *name = ivar_getName(vars[i]);
                NSString *strName = [NSString stringWithUTF8String:name];
                
                id value = [aDecoder decodeObjectForKey:strName];
                [self setValue:value forKey:strName];
            }
        }
        return self;
    }
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        unsigned int count = 0;
        Ivar *vars = class_copyIvarList([self class], &count);
        for (int i = 0; i < count; i++) {
            const char *name = ivar_getName(vars[i]);
            NSString *strName = [NSString stringWithUTF8String:name];
            
            id value = [self valueForKey:strName];
            [aCoder encodeObject:value forKey:strName];
        }
    }
    ```
    
    * 4.通过runtime实现字典转模型
    
    在iOS开发中,我们可能会用到各种字典转模型的框架,如:JSONModel/MJExtension等。其实这些字典转模型的框架正是利用了runtime的特性。首先通过class_copyIvarList和ivar_getName获取属性名称,然后在字典中查找到对应的值,最后通过KVC为model的属性赋值。大致过程如下:
    
    ```
    + (void)modelWithDict:(NSDictionary *)dict {
        id objc = [[self alloc] init];
        unsigned int count;
        // 获取model的所有属性
        Ivar *ivarList = class_copyIvarList(self, &count);
        for (int i = 0; i < count; i++) {
            // 获取成员属性名
            NSString *name = [NSString stringWithUTF8String:ivar_getName(ivarList[i])];
            // 去掉name里的第一个下滑线字符
            NSString *key = [name substringFromIndex:1];
            // 从字典中查找对应属性的值
            id value = dict[key];
            [objc setValue:value forKey:key];
        }
    }
    ```
    
    总结:runtime的使用场景还有动态的给类添加方法、发送消息等等,但个人觉得比较实用的有以上几种,当然更多的使用的场合和技巧还要靠大家来发掘。
    
    ##RunTime能做什么?
    runtime能做什么呢?其实我们只要在runtime.h这个头文件中查看它所提供的API就知道了,下面只是列出一些我们常用的runtime API。
    ```
        //获取cls类对象所有成员ivar结构体
        Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
        //获取cls类对象name对应的实例方法结构体
        Method class_getInstanceMethod(Class cls, SEL name)
        //获取cls类对象name对应类方法结构体
        Method class_getClassMethod(Class cls, SEL name)
        //获取cls类对象name对应方法imp实现
        IMP class_getMethodImplementation(Class cls, SEL name)
        //测试cls对应的实例是否响应sel对应的方法
        BOOL class_respondsToSelector(Class cls, SEL sel)
        //获取cls对应方法列表
        Method *class_copyMethodList(Class cls, unsigned int *outCount)
        //测试cls是否遵守protocol协议
        BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
        //为cls类对象添加新方法
        BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
        //替换cls类对象中name对应方法的实现
        IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
        //为cls添加新成员
        BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
        //为cls添加新属性
        BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
        //获取m对应的选择器
        SEL method_getName(Method m)
        //获取m对应的方法实现的imp指针
        IMP method_getImplementation(Method m)
        //获取m方法的对应编码
        const char *method_getTypeEncoding(Method m)
        //获取m方法参数的个数
        unsigned int method_getNumberOfArguments(Method m)
        //copy方法返回值类型
        char *method_copyReturnType(Method m)
        //获取m方法index索引参数的类型
        char *method_copyArgumentType(Method m, unsigned int index)
        //获取m方法返回值类型
        void method_getReturnType(Method m, char *dst, size_t dst_len)
        //获取方法的参数类型
        void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len)
        //设置m方法的具体实现指针
        IMP method_setImplementation(Method m, IMP imp)
        //交换m1,m2方法对应具体实现的函数指针
        void method_exchangeImplementations(Method m1, Method m2)
        //获取v的名称
        const char *ivar_getName(Ivar v)
        //获取v的类型编码
        const char *ivar_getTypeEncoding(Ivar v)
        //设置object对象关联的对象
        void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
        //获取object关联的对象
        id objc_getAssociatedObject(id object, const void *key)
        //移除object关联的对象
        void objc_removeAssociatedObjects(id object)
    ```
    
    ##到底什么是RunTime?
    就自己的理解而言,runtime就是一套底层的C语言API,我们的程序在运行的时候会将我们编写的OC代码转为底层的C语言函数来执行。于是我们可以利用runtime直接调用这些C语言的函数,这样我们可以在运行时动态的修改类的具体实现等。

    相关文章

      网友评论

      本文标题:RunTime的用法

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