Runtime应用

一、Runtime简介

  1. Runtime简称运行时, OC就是运行时机制, 也就是在运行的时候的一些机制, 最主要的的是消息机制
  2. 对于C语言, 函数的调用在编译阶段的时候就会决定调用哪个函数
  3. 对于OC, 属于动态调用过程, 在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到相应的函数来调用
  4. 事实证明
    在编译阶段, OC可以调用任何函数, 即使这个函数并没有实现,只要声明过就不会报错
    在编译阶段,C语言调用未实现的函数会报错

二、Runtime应用

Xcode配置

从Xcode6苹果不推荐用runtime, 如果项目中使用runtime, 在xcode中做如下配置:
找到buildsetting -> 搜索msg (选中All) -> 找到 Enable Strict Checking of objs_msgSend Call 设置为 NO
设置完成后就可以使用底层Runtime编程了

Runtime消息机制

在前一篇Runtime基础中已经知道, OC的方法调用,其实每次都是一个运行时消息发送过程。
objc_msgSend(receiver, selector)
想要使用Runtime, 必须导入头文件objc/message.h

#import <objc/message.h>

交换方法

应用场景: 当第三方框架 或者 系统原生方法功能不能满足我们需求的时候,我们可以在保持系统原有方法功能的基础上,添加额外的功能。
需求: 加载本地图片用系统方法[UIImage imageNamed:@"image"];是无法知道到底有没有加载成功。给系统的imageNamed添加额外功能(是否成功加载图片。

  • 方法一: 自定义继承系统UIImage的类,重写方法。 弊端是每次都要导入头文件, 项目太大, 没办法实现。

  • 方法二: runtime交换方法

1. 给系统的方法添加分类

UIImage+LVDImage.h
UIImage+LVDImage.m

2. 自己实现一个带有扩展功能的方法

+ (UIImage *)lvd_imageNamed:(NSString *)name
{
//    UIImage *image = [UIImage imageNamed:name];
    /* 交换方法后 imageNamed => lvd_imageNamed
     此处使用imageNamed就是调用lvd_imageNamed, 会陷入死循环
     */
    
    UIImage *image = [UIImage lvd_imageNamed:name];
    /* 交换方法后 lvd_imageNamed => imageNamed
     此处使用lvd_imageNamed, 就是调用系统方法imageNamed
     */
    
    if (image) {
        
        NSLog(@"图片加载成功");
    }
    else {
        NSLog(@"图片加载失败");
    }
    
    return image;
}

3. 使用Runtime交换方法

// 把类加载进内存的时候调用 , swift中没有
+ (void)load
{
    // 获取imageNamed
    Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
    // 获取lvd_imageNamed
    Method lvd_imageNameMethod = class_getClassMethod(self, @selector(lvd_imageNamed:));
    // 交换方法
    method_exchangeImplementations(imageNameMethod, lvd_imageNameMethod);
}

// 会调用多次, swift 中使用
//+ (void)initialize
//{
//    // 使用 dispatch_once 只调用一次
//    static  dispatch_once_t onceToken;
//    dispatch_once(&onceToken, ^{
//        // 交换方法操作
//    });
//}

class_getClassMethod() 获取某个类的方法
method_exchangeImplementations() 交换方法实现方式
4.看下交换结果

UIImage *img = [UIImage imageNamed:@"red_tick.png"];

2018-09-08 20:47:35.287603+0800 Runtime[14303:2376510] 图片加载成功

注意 因为做了方法交换, 调用 imageNamed => lvd_imageNamed,调用 lvd_imageNamed => imageNamed, 不要出现死循环

动态添加方法

OC都是懒加载机制, 只要这个方法实现了, 就会马上添加到方法列表中。
需求: runtime 动态添加方法处理调用一个未实现的方法 和去除报错。

1. 调用未实现方法
创建一个Animal类, 直接调用run方法 (run方法未声明, 也未实现)

Animal *pig = [[Animal alloc] init];
[pig performSelector:@selector(run)];

由于run方法未声明, 为了能编译通过, 使用performSelector调用方法, 运行时才执行。运行一下, 果然崩溃报错
reason: '-[Animal run]: unrecognized selector sent to instance 0x6000000063d0'
2. 使用Runtime动态添加方法

// 两个参数self和_cmd,是默认的,其实苹果每个函数调用都会传self和_cmd,
// 只是编译器帮我们做了.
void run (id self, SEL _cmd)
{
    NSLog(@"小猪在跑");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    // 实现run方法
    if (sel == NSSelectorFromString(@"run")) {
     
        class_addMethod([self class], sel, (IMP)run, "v@:");
        return YES;
    }
    
    return [super resolveClassMethod:sel];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel 不存在实例方法时,会调用这个方法
class_addMethod() 给某个类添加方法
从Xcode打开苹果开文档 (Help -> Developer Documentation) 找到 运行时 class_addMethod函数

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
  • Class : 给哪个类添加方法
  • SEL : 添加的方法
  • IMP :方法实现 => 函数 => 函数入口 => 函数名
  • types : 方法返回类型
    • v 表示 返回值void
    • @ 表示 参数id (an object type)
    • : 表示 参数sel (A method selector)
      更多types 参考 Type Encodings

运行看下, 方法动态添加成功

2018-09-08 22:42:08.583909+0800 Runtime[15412:2538210] 小猪在跑

如果方法带参数呢, 如[pig performSelector:@selector(run) withObject:@20]; 动态添方法如下

// 带参数
void run (id self, SEL _cmd, NSNumber *n)
{
    NSLog(@"小猪跑了%@米", n);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"%@", NSStringFromSelector(sel));
    
    // 实现run方法
    if (sel == NSSelectorFromString(@"run")) {
        /*
         Class : 给那个类添加方法
         SEL : 添加哪个方法
         IMP : 方法实现 => 函数 => 函数入口 => 函数名
         types : 方法类型
         */
        // 没参数
//        class_addMethod([self class], sel, (IMP)run, "v@:");
        // 有参数
        class_addMethod([self class], sel, (IMP)run, "v@:@");
        
        return YES;
    }
    
    return [super resolveClassMethod:sel];
}

动态添加属性

需求让一个NSObject类, 保存一个字符串
也可以理解为, 给category添加属性, 让某个属性和对象关联。 在我的另一篇文章 @property中的关键字 已经实现给分类添加属性

注: 类别中使用@property只会生成setter, getter方法声明,不会生成方法实现,也不会生成带下划线的成员变量。

1. 给NSObject添加分类

NSObject+Property.h
NSObject+Property.m

2. 在分类中声明属性

@interface NSObject (Property)

@property NSString *name;

@end

3. 将属性和NSObject对象关联

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, @"name");
}

字典转模型

  • KVC: 模型中的属性必须与字典中的key一一对应。手动声明属性太麻烦。
  • 优秀的JSON转模型第三方库JSONModel、YYModel等都利用runtime对属性进行获取,赋值等操作,要比KVC进行模型转换更加强大,更有效率。

1. 动态添加属性
创建NSDictionary, 实现一个方法自动生成属性代码

- (void)createPropertyCode
{
    NSMutableString *codes = [NSMutableString string];

    // 遍历字典
    [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        
        // 生成属性
        NSString *code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;", key];
        [codes appendFormat:@"\n%n\n", code];
    }];
    
    NSLog(@"property: \n %@", codes);
}

用NSDictionary对象直接调用createPropertyWithDict方法,
将打印的codes复制到模型即可。

@property (nonatomic, strong) NSString *status;

@property (nonatomic, strong) NSString *memo;

@property (nonatomic, assign) NSInteger money;

@property (nonatomic, assign) BOOL flag;

@property (nonatomic, strong) NSString *name;

2. 字典转换成模型

+ (instancetype)modelWithDict:(NSDictionary *)dict
{
    NSObject *objc = [[[self class] alloc] init];
    
    unsigned int count = 0;
    // 获取Model中的所有成员变量 Ivar成员变量数组
    //之所以用class_copyIvarList 而不用 class_copyPropertyList,因为我们可能忽略成员变量,但不会忽略属性
    Ivar *ivarList = class_copyIvarList([self class], &count);
    
    // 遍历所有的成员变量
    for (int i = 0; i < count; i++) {
        
        // 获取成员变量
        Ivar ivar = ivarList[i];
        // 获取成员变量名字 (c语言字符串转oc字符串转 -- utf-8)
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 去掉下划线
        NSString *key = [ivarName substringFromIndex:1];
        
        // 去字典中查找对应的value
        id value = dict[key];
        if (value) {
            [objc setValue:value forKey:key];
        }
    }
    
    return objc;
}

以上就是几个runtime应用场景, Runtime底层实现还有很多, 可以根据项目需求谨慎仔细的使用。

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,673评论 7 64
  • 引导 对于从事 iOS 开发人员来说,所有的人都会答出「 Runtime 是运行时 」,什么情况下用 Runtim...
    Winny_园球阅读 4,146评论 3 75
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,020评论 8 265
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,635评论 0 9