ios开发笔记iOS 项目技巧移动开发

RunTime的用法

2016-05-13  本文已影响1384人  PaulLi哥

RunTime简介##

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

RunTime的应用场景##

当我们看一些第三方框架的时候我们可能看到这样的代码,例如在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的值。

大家可能看到过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

在开发中,我们很多时候需要对数据进行归档,但是如果一个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语言的函数,这样我们可以在运行时动态的修改类的具体实现等。
上一篇下一篇

猜你喜欢

热点阅读