玩转iOS开发:装逼技术RunTime的应用(三)

1,552 阅读4分钟

文章分享至我的个人技术博客:https://cainluo.github.io/15074742481003.html


在上一章节里晓得了怎么在Category里关联对象, 以及利用RunTime转换模型的时候预防了三种转换时的情况, 如果没有去看的朋友可以到玩转iOS开发:装逼技术RunTime的应用(二)看看.

转载声明:如需要转载该文章, 请联系作者, 并且注明出处, 以及不能擅自修改本文.


利用Runtime归档

在以前我们在使用归档的时候都会有一个烦恼, 就是写的太多, 不信? 我们来声明一个对象:

#import <Foundation/Foundation.h>

@interface RunTimeCoderModel : NSObject <NSCoding>

@property (nonatomic, copy) NSString *cl_name;
@property (nonatomic, copy) NSString *cl_height;
@property (nonatomic, copy) NSString *cl_age;

@end

常规归档的写法:

- (void)encodeWithCoder:(NSCoder *)aCoder {
    
    [aCoder encodeObject:_cl_name
                  forKey:@"name"];
    [aCoder encodeObject:_cl_height
                  forKey:@"height"];
    [aCoder encodeObject:_cl_age
                  forKey:@"age"];
}

常规解档的写法:

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    self = [super init];
    
    if (self) {
        
        self.cl_name   = [aDecoder decodeObjectForKey:@"name"];
        self.cl_height = [aDecoder decodeObjectForKey:@"height"];
        self.cl_age    = [aDecoder decodeObjectForKey:@"age"];
    }
    
    return self;
}

现在看着好像也不怎么样, 但在实际开发中, 我们要写的属性可不是只有这三个, 如果遇到变态的, 有上百个那怎么办呢?

逐个逐个去写么? 万一写完之后突然要改属性怎么办? 逐个去改? 这样子就会大量的浪费我们的时间, 这是不明智的写法.

回想一下, 每个类都有一个isa的结构体指针, 里面可以拿到所有的每个类的信息, 那我们是否可以通过这个特性, 来给归档解档操作一番呢? 试试看:

RunTime归档的写法:

- (void)cl_runtimeEncoderWithCoder:(NSCoder *)coder {
    
    unsigned int count = 0;
    
    Ivar *ivarList = class_copyIvarList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        
        Ivar ivar = ivarList[i];
        
        const char *name = ivar_getName(ivar);
        
        NSString *key = [NSString stringWithUTF8String:name];
        
        id value = [self valueForKey:key];
        
        [coder encodeObject:value
                     forKey:key];
    }
    
    free(ivarList);
}

RunTime解档写法:

- (void)cl_runtimeDecideWithCoder:(NSCoder *)decoder {
    
    unsigned int count = 0;
    
    Ivar *ivarList = class_copyIvarList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        
        Ivar ivar = ivarList[i];
        
        const char *name = ivar_getName(ivar);
        
        NSString *key = [NSString stringWithUTF8String:name];
        
        id value = [decoder decodeObjectForKey:key];
        
        [self setValue:value
                forKey:key];
    }
    
    free(ivarList);
}

最终的使用:

- (void)encodeWithCoder:(NSCoder *)aCoder {
        
    [self cl_runtimeEncoderWithCoder:aCoder];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    self = [super init];
    
    if (self) {
    
        [self cl_runtimeDecideWithCoder:aDecoder];
    }
    
    return self;
}

最终的效果:

1

这的确是可行的, 这样子我们就把这个写成一个通用的类, 并且遵守<NSCoding>协议, 就可以把所有继承与NSObject的类全部一次性归档.

在这里我就不对归档和解档的方法进行封装了, 都写在RunTimeCoderController这个控制器上, 有想法的朋友可以自行进行封装, 这样子就可以抽成一个通用类.


RunTime黑魔法

前段时间搜了一下关于RunTime的一些博客, 发现有很多人都说RunTime黑魔法, 那什么是黑魔法?

  • 简单的来说, 其实就是进行方法交换.
  • 我们都知道, 在Objective-C中调用一个方法, 其实是向一个对象发送消息, 而查找消息的唯一依据就是selector的名字, 利用Objective-C的动态特性, 我们可以实现在运行时偷偷的换掉selector对应的方法实现.
  • 而我们也逗知道, 每一个类都有一个方法列表, 存放着方法的名字和方法实现的映射关系, selector其实就是方法名, 而IMP类似函数指针, 指向具体的Method实现, 通过selector就可以找到对应的IMP.

2

  • 交换方法的实现方式
    • 利用method_exchangeImplementations来交换两个方法的实现
    • 利用class_replaceMethod替换方法的实现
    • 利用method_setImplementation来直接设置某个方法的IMP

3

除了我们在演示里写过的代码, 在实际上又是怎么运用呢? 这里收集到了几种场景:

  • 替换ViewController的生命周期方法
  • 解决集合类的获取索引, 添加, 删除元素越界崩溃的问题
  • 防止按钮重复暴力点击
  • 全局更换控件初始化的效果
  • App的热修复
  • App空数据时的占位图工具类封装
  • 全局修改UINavigationBarBackButtonItem

代码这里就不写了, 想详细了解的朋友可以到下面的文章去了解.

Runtime Method Swizzling开发实例汇总


推荐文章

runtime 完整总结 objc_msgSend runtime详解 让你快速上手Runtime 利用Runtime 实现自动化归档 runtime那些事(消息机制) OC最实用的runtime总结,面试、工作你看我就足够了! Runtime在实际开发中的应用


工程地址

项目地址: https://github.com/CainRun/iOS-Project-Example/tree/master/RunTime/Five


最后

码字很费脑, 看官赏点饭钱可好

微信

支付宝