前言:
本文主要介绍一些常用Runtime API的常用场景,用以解决初学者对于Runtime运用上的一些困惑,以便合理的将Runtime运用到项目中
*--- 欢迎指正和补充 ---*
1. 常用场景归纳
- 利用关联对象(AssociatedObject)
给分类添加属性
- 利用关联对象(AssociatedObject)
- 遍历类的所有成员变量或属性,获取私有成员变量信息,以
修改私有属性
(如:修改textfield的占位文字颜色)
- 遍历类的所有成员变量或属性,获取私有成员变量信息,以
- 遍历类的属性(字典转模型、自动归档解档、重写
- description
等)
- 遍历类的属性(字典转模型、自动归档解档、重写
-
- 交换方法实现
- 拦截系统方法,对其进行修改和补充,拓展一些自己的业务逻辑(如监听一些事件等)
- 当三方pod框架不满足使用场景情况下,可以通过交换方法实现来达到
不修改原框架
的情况下,实现对业务场景的支持
-
- 其它不常用使用:
- App唤醒时控制器的万能跳转(动态创建一个控制器然后,传入预定好的参数进行跳转)
- 热点修复(先动态添加一个方法,然后替换有问题方法的实现)
- 利用方法调用过程中的
消息转发机制
,来优化方法找不到的异常
问题
2. 应用案例
🌰 利用关联对象
给TLPerson分类
添加name
和weight
属性
- 👉了解更多关于分类的知识
#import <objc/runtime.h>
@interface TLPerson (ExampleCode)
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end
@implementation TLPerson (ExampleCode)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
// 隐式参数
// _cmd == @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
- (void)setWeight:(int)weight
{
objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)weight
{
// _cmd == @selector(weight)
return [objc_getAssociatedObject(self, _cmd) intValue];
}
@end
🌰 修改UITextView实例对象的
私有属性
- 通过
class_copyIvarList
遍历UITextField
的所有成员变量,
// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
- 可发现它有
_placeholderLabel
和_clearButton
两个私有成员 - 然后可以通过
KVC
来修改其属性
// 修改光标颜色(placeholder)
[textField setValue:UIColorFromRGBA(0xcccdcd, 1.f) forKeyPath:@"_placeholderLabel.textColor"];
// 修改清除按钮的图片
UIButton *clearButton = [textField valueForKey:@"_clearButton"];
[clearButton setImage:[UIImage imageNamed:@"login_icon_clear"] forState:UIControlStateNormal];
🌰 字典转模型、自动归档解档(来自
MJExtension
节选片段,仅作参考)
- 通过
class_copyPropertyList
获取属性列表
- 通过
+ (NSMutableArray *)properties
{
NSMutableArray *cachedProperties = [self propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];
if (cachedProperties == nil) {
MJExtensionSemaphoreCreate
MJExtensionSemaphoreWait
if (cachedProperties == nil) {
cachedProperties = [NSMutableArray array];
[self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
// 1.获得所有的成员变量
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList(c, &outCount);
// 2.遍历每一个成员变量
for (unsigned int i = 0; i<outCount; i++) {
MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
// 过滤掉Foundation框架类里面的属性
if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
property.srcClass = c;
[property setOriginKey:[self propertyKey:property.name] forClass:self];
[property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
[cachedProperties addObject:property];
}
// 3.释放内存
free(properties);
}];
[self propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)] = cachedProperties;
}
MJExtensionSemaphoreSignal
}
return cachedProperties;
}
- 遍历properties将
字典转为模型
- 遍历properties将
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 获得JSON对象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues参数不是一个字典");
Class clazz = [self class];
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
//通过封装的方法回调一个通过运行时编写的,用于返回属性列表的方法。
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
@try {
// 0.检测是否被忽略
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
if ([ignoredPropertyNames containsObject:property.name]) return;
// 1.取出属性值
id value;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
// 值的过滤
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有过滤后的新值
[property setValue:newValue forObject:self];
return;
}
// 如果没有值,就直接返回
if (!value || value == [NSNull null]) return;
// 2.复杂处理
MJPropertyType *type = property.type;
Class propertyClass = type.typeClass;
Class objectClass = [property objectClassInArrayForClass:[self class]];
// 不可变 -> 可变处理
if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
value = [NSMutableArray arrayWithArray:value];
} else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
value = [NSMutableDictionary dictionaryWithDictionary:value];
} else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
value = [NSMutableString stringWithString:value];
} else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
value = [NSMutableData dataWithData:value];
}
if (!type.isFromFoundation && propertyClass) { // 模型属性
value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// string array -> url array
NSMutableArray *urlArray = [NSMutableArray array];
for (NSString *string in value) {
if (![string isKindOfClass:[NSString class]]) continue;
[urlArray addObject:string.mj_url];
}
value = urlArray;
} else { // 字典数组-->模型数组
value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
}
} else {
if (propertyClass == [NSString class]) {
if ([value isKindOfClass:[NSNumber class]]) {
// NSNumber -> NSString
value = [value description];
} else if ([value isKindOfClass:[NSURL class]]) {
// NSURL -> NSString
value = [value absoluteString];
}
} else if ([value isKindOfClass:[NSString class]]) {
if (propertyClass == [NSURL class]) {
// NSString -> NSURL
// 字符串转码
value = [value mj_url];
} else if (type.isNumberType) {
NSString *oldValue = value;
// NSString -> NSNumber
if (type.typeClass == [NSDecimalNumber class]) {
value = [NSDecimalNumber decimalNumberWithString:oldValue];
} else {
value = [numberFormatter_ numberFromString:oldValue];
}
// 如果是BOOL
if (type.isBoolType) {
// 字符串转BOOL(字符串没有charValue方法)
// 系统会调用字符串的charValue转为BOOL类型
NSString *lower = [oldValue lowercaseString];
if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
value = @YES;
} else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
value = @NO;
}
}
}
} else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
// 过滤 NSDecimalNumber类型
if (![value isKindOfClass:[NSDecimalNumber class]]) {
value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
}
}
// value和property类型不匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
value = nil;
}
}
// 3.赋值
[property setValue:value forObject:self];
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
// 转换完毕
if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
[self mj_keyValuesDidFinishConvertingToObject];
}
if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]) {
[self mj_keyValuesDidFinishConvertingToObject:keyValues];
}
return self;
}
- 归档与解档
- (void)mj_encode:(NSCoder *)encoder
{
Class clazz = [self class];
NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
// 遍历归档
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
// 检测是否被忽略
if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
if ([ignoredCodingPropertyNames containsObject:property.name]) return;
id value = [property valueForObject:self];
if (value == nil) return;
[encoder encodeObject:value forKey:property.name];
}];
}
- (void)mj_decode:(NSCoder *)decoder
{
Class clazz = [self class];
NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
// 遍历解档
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
// 检测是否被忽略
if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
if ([ignoredCodingPropertyNames containsObject:property.name]) return;
id value = [decoder decodeObjectForKey:property.name];
if (value == nil) { // 兼容以前的MJExtension版本
value = [decoder decodeObjectForKey:[@"_" stringByAppendingString:property.name]];
}
if (value == nil) return;
[property setValue:value forObject:self];
}];
}
🌰 同交换方法实现
eg1. 给UIViewController分类中使用系统原生API,如:- dealloc
方法
eg2. 解决给数组添加nil时奔溃问题;
// eg1. 给UIViewController的的分类中使用`- dealloc`方法
@implementation UIViewController (TLTransition)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [UIViewController class];
//
// 注意直接使用:@selector(dealloc) 会报错:ARC forbids use of 'dealloc' in a @selector
// Method method = class_getInstanceMethod(cls, @selector(dealloc));
// 使用字符串获取NSSelectorFromString(@"dealloc")
Method method1 = class_getInstanceMethod(cls, NSSelectorFromString(@"dealloc"));
Method method2 = class_getInstanceMethod(cls, @selector(tl_dealloc));
method_exchangeImplementations(method1, method2);
});
}
- (void)tl_dealloc{
// do something
tl_Log(@"%@ %s", [self class], __func__);
[TLTransitionDelegate removeAnimatorForKey:self];
// 回到原来的dealloc方法
[self tl_dealloc];
}
@end
// eg2. 解决给数组添加nil时奔溃问题;
@implementation NSMutableArray (Extension)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型(如:NSMutableArray 真实类型是 __NSArrayM)
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(swizzling__insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)swizzling__insertObject:(id)anObject atIndex:(NSUInteger)index
{
if (anObject == nil) return;
[self swizzling__insertObject:anObject atIndex:index];
}
@end
网友评论