「这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战」
前言
相信大家在正常开发中, 也会有一些runtime的应用, 好比我原先的那一篇文章<iOS快速归结档> 就是应用了runtime动态获取类中属性名来实现的, 当然水平还是有些次了, runtime博大精深我连个敲门砖估计都算不上, 这里只能列举一些平时常用的东西, 可能有部分同学还是不特别了解的, 可以当个笔记来了解一下就好了
讲解
常用场景
动态获取成员变量
动态获取成员变量 这种方式最具有标志性的功能就是 字典转模型 了, 相信大家项目中这个也是必备选项了, 所以runtime并没有说那么神秘, 毕竟你天天都在用它, 简单来说就是动态获取类中的成员变量, 然后从字典中获取对应名称的数据, 赋值给同名的成员变量. 大概就是这个样子
#import "NSObject+Ex.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation NSObject (Ex)
+ (instancetype)_initWithDictionaryForModel:(NSDictionary *)dic {
id myObj = [[self alloc] init];
unsigned int outCount;
//获取类中的所有成员属性
objc_property_t *arrPropertys = class_copyPropertyList([self class], &outCount);
for (NSInteger i = 0; i < outCount; i ++) {
//获取属性名字符串
objc_property_t property = arrPropertys[i];
//model中的属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
id propertyValue = dic[propertyName];
// 处理服务器字段 与 本地模型 字段不匹配问题
if ([propertyName isEqualToString:@"age3"]) {
propertyValue = dic[@"age"];
}
if (propertyValue != nil) {
[myObj setValue:propertyValue forKey:propertyName];
}
}
//注意在runtime获取属性的时候,并不是ARC Objective-C的对象所有需要释放
free(arrPropertys);
return myObj;
}
@end
动态修改成员变量
动态需改成员变量 这种方式最常用到 获取一些私有成员变量, 因为这些变量没有公布出来, 并不能直接修改数据, 通过runtime就可以动态获取到这些变量, 那么就可修改其中的数据, 好比系统的UITextField中, 我们常会修改占位文字的颜色, 而这个属性又是私有的
#import "UITextField+Ex.h"
#import <objc/runtime.h>
@implementation UITextField (Ex)
- (void)changePlaceHolderTextColor:(UIColor *)color {
//这块就是为了打印出所有的成员变量,获取成员变量的名
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
// NSLog(@"成员变量%s", ivar_getName(ivar));
}
free(ivars);
// iOS 13 通过 KVC 方式修改私有属性,有 Crash 风险,谨慎使用!并不是所有KVC都会Crash,要尝试!
if ([[UIDevice currentDevice].systemVersion floatValue] > 13.0) {
Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
UILabel *placeholderLabel = object_getIvar(self, ivar);
placeholderLabel.textColor = color;
}else{
//kvc赋值,iOS13之前用kvc赋值就可以哦
[self setValue:color forKeyPath:@"_placeholderLabel.textColor"];
}
}
@end
给分类添加属性
给分类添加属性 这种方式的使用场景是, 给一些系统的类增加功能, 比如 我需要再点击这个按钮的时候, 想给你这个按钮增加一个消息的数据, 但是系统的按钮是不支持的, 自己写一个按钮又比较麻烦, 那么就可以动态的给按钮上增加一个属性来绑定这个消息数据
//.h文件
#import <Foundation/Foundation.h>
@interface NSObject (Ex)
@property (nonatomic, copy) NSString *msg;
@end
//.m文件
import "NSObject+Ex.h"
#import <objc/runtime.h>
@implementation NSObject (Ex)
- (void)setMsg:(id)msg {
//注意最后一个参数的类型
objc_setMsg(self, @selector(msg), associatedObject, OBJC_ASSOCIATION_COPY);
}
- (id)msg {
return objc_getMsg(self, _cmd);
}
@end
方法交换
方法交换 这种方式最常见的就是 无侵入埋点, 相信在很多的埋点文章中或多或少的都会有一部分runtime的影子,
还有一种就是错误处理, 比如 你要给数组中加入一个nill数据, 正常情况肯定是会崩溃的, 但是在加入数据之前, 我们能做一次校验, 如果不是nil的数据才能给加入, 否则就不加, 就会避免用户的体验
#import "NSMutableArray+Ex.h"
#import <objc/runtime.h>
//方法交换
@implementation NSMutableArray (Ex)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(jf_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)jf_insertObject:(id)anObject atIndex:(NSUInteger)index {
//如果是空对象,就不添加,防止奔溃
if (anObject == nil) return;
//拦截之后,再调用系统的实现,由于不知道底层实现逻辑,自己实现系统方法能会出各种意想不到的错
//这儿看似死循环,细理逻辑,方法实现已经交换过了的
[self jf_insertObject:anObject atIndex:index];
}
@end
总结
上述对runtime的陈述仅仅是冰山一脚, 有非常的多的大神做出了非常厉害的东西, 如果你对我写的东西有什么疑问或者指教, 请告知我 谢谢