主要内容
- 利用 runtime 交换方法
- 利用 runtime 动态添加方法
- 利用 runtime 动态添加属性
- 利用 runtime 字典转模型
- KVO 的实现
- JSPatch 的实现
- 实现 NSCoding 的自动归档和自动解档
交换方法
* *交换自定义方法 ditImangeNamed: 和系统方法 imageNamed: 的implementation。可以在不大量修改项目代码的情况下,添加所需的功能。注:ditImangeNamed: 的方法实现里不要再掉用 imageNamed: 方法,否则会引起循环引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @implementation UIImage (DITImage)
+ (nullable UIImage *)ditImangeNamed:(NSString *)name { UIImage *image = [UIImage ditImangeNamed:name]; if (!image) { NSLog(@"资源图片不存在"); }
return image; }
+ (void)load { Method imageNamedMethod = class_getClassMethod([UIImage class], @selector(imageNamed:)); Method ditImageNamedMethod = class_getClassMethod([UIImage class], @selector(ditImangeNamed:)); method_exchangeImplementations(imageNamedMethod, ditImageNamedMethod); }
@end
|
动态添加方法
* *即是 runtime的理解(一) 的消息转发,动态解析部分,提供新的方法实现来接收消息并处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| - (void)testDynamicMethod { [self performSelector:@selector(justTest:) withObject:@"testStr"]; // [DITRuntimeViewController performSelector:@selector(justClassTest:) withObject:@"classTestStr"]; }
+ (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(justTest:)) { class_addMethod([self class], sel, (IMP)justTestMethod, "v@:@"); return YES; }
return [super resolveInstanceMethod:sel]; }
void justTestMethod(id self, SEL _cmd, id str) { NSLog(@"%@的%@方法动态实现了,参数为:%@", self, NSStringFromSelector(_cmd), str); }
|
动态添加属性
objc_setAssociatedObject()
* *方法全称: objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
给要添加的属性添加关联,参数分别表示如下:
- id object : 给哪个对象添加属性。
- void * key : 属性名,作为被关联属性对象的标示。
- id value : 被关联的属性对象。
- objc_AssociationPolicy policy : 策略,属性以什么形式保存。有以下几种:
1 2 3 4 5 6 7
| typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, // 指定一个弱引用相关联的对象 OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性 OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相关的对象被复制,非原子性 OBJC_ASSOCIATION_RETAIN = 01401, // 指定相关对象的强引用,原子性 OBJC_ASSOCIATION_COPY = 01403 // 指定相关的对象被复制,原子性 };
|
objc_getAssociatedObject()
* *方法全称: objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
,用来通过 key 值获取关联对象,参数分别表示如下:
- id object : 获取哪个对象里面的关联的属性。
- void * id key : 属性名,被关联对象的标示。
代码示例
* *以给 UITextView 的分类添加 placeholder 属性为例,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| /* UITextView+DITPlaceholder.h */ @interface UITextView (DITPlaceholder) @property (nonatomic, copy) NSString *placeholder; @property (nonatomic, weak) UILabel *placeholderLabel; @end
/* UITextView+DITPlaceholder.m */ - (UILabel *)placeholderLabel { UILabel *tempLabel = objc_getAssociatedObject(self, @"placeholderLabel");
if (!tempLabel) { tempLabel = [[UILabel alloc] init]; tempLabel.font = self.font; tempLabel.textColor = [UIColor grayColor]; tempLabel.textAlignment = NSTextAlignmentLeft; tempLabel.numberOfLines = 0;
[self addSubview:tempLabel];
objc_setAssociatedObject(self, @"placeholderLabel", tempLabel, OBJC_ASSOCIATION_RETAIN); }
return tempLabel; }
|
字典转模型
class_copyIvarList()
* *方法 class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
参数释义:
- __unsafe_unretained Class cls: 获取哪个类的成员属性列表。
- unsigned int * outCount : 无符号int型指针,传入一个无符号整型变量的地址,传出来的值为成员属性总数。
- (返回值:Ivar * ) : 返回的是一个Ivar类型的指针 。指针默认指向的是数组的第0个元素,指针+1会向高地址移动一个Ivar单位的字节,也就是指向第一个元素。Ivar表示成员属性。
ivar_getName()
* *方法 ivar_getName(Ivar _Nonnull v)
通过上个方法获得的成员属性 Ivar ,传入此方法,获得成员属性名的C语言字符串。
代码示例
* *带二级转换的简单的字典转模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| /* DITRuntimeViewController.m */ #pragma mark - 字典转模型 - (void)testModelWithDict { DITForwardingTest *forwardingTestModel = [DITForwardingTest modelWithKeyValues:@{ @"testName": @"testName123", @"subModel": @{@"testSubName": @"testSubName123"} }]; NSLog(@"%@", forwardingTestModel.mj_keyValues); }
/* NSObject+DITKeyValues.m */ + (instancetype)modelWithKeyValues:(NSDictionary *)dict { id obj = [[self alloc] init];
unsigned int count = 0; Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) { Ivar ivar = ivarList[i];
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSString *key = [propertyName substringFromIndex:1];
NSString *value = dict[key]; NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // @"@\"NSString\""
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) { // 二级字典转模型 propertyType = [propertyType substringWithRange:NSMakeRange(2, propertyType.length-3)];
Class modelClass = NSClassFromString(propertyType); value = [modelClass modelWithKeyValues:(NSDictionary *)value]; }
if (value) { [obj setValue:value forKey:key]; } }
return obj; }
/* DITSubModel.h */ @interface DITSubModel : NSObject @property (nonatomic, copy) NSString *testSubName; @end
/* DITForwardingTest.h */ @interface DITForwardingTest : NSObject @property (nonatomic, copy) NSString *testName; @property (nonatomic, strong) DITSubModel *subModel; @end
|
KVO实现
* *KVO 的实现用到了 runtime 的方法IMP替换,当观察对象 Person 时,KVO机制动态创建一个名为:NSKVONotifying_ Person 的新类,该类继承自对象 Person 的本类,且 KVO 为 NSKVONotifying_ Person 重写要观察属性的 setter 方法,setter 方法会负责在实现 setter 之前和之后,通知所有观察对象属性值的更改情况。
* *KVO 的键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangeValueForKey: ,在存取数值的前后分别调用这 2 个方法:被观察属性发生改变之前, willChangeValueForKey: 被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该keyPath 的属性值已经变更;之后, observeValueForKey:ofObject:change:context:也会被调用。示例代码如下:
1 2 3 4 5 6
| - (void)setName:(NSString *)newName { [self willChangeValueForKey:@"name"]; // KVO 在调用存取方法之前总调用 [super setValue:newName forKey:@"name"]; // 调用父类的存取方法 [self didChangeValueForKey:@"name"]; // KVO 在调用存取方法之后总调用 }
|
JSPatch的实现
JSPatch 是一个 iOS 动态更新框架,只需在项目中引入极小的引擎,就可以使用 JavaScript 调用任何 Objective-C 原生接口,获得脚本语言的优势:为项目动态添加模块,或替换项目原生代码动态修复 bug。
实现NSCoding的自动归档和自动解档
* *原理描述:用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。核心方法:在Model的基类中重写方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { unsigned int outCount; Ivar * ivars = class_copyIvarList([self class], &outCount); for (int i = 0; i < outCount; i ++) { Ivar ivar = ivars[i]; NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)]; [self setValue:[aDecoder decodeObjectForKey:key] forKey:key]; } } return self; }
- (void)encodeWithCoder:(NSCoder *)aCoder { unsigned int outCount; Ivar * ivars = class_copyIvarList([self class], &outCount); for (int i = 0; i < outCount; i ++) { Ivar ivar = ivars[i]; NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)]; [aCoder encodeObject:[self valueForKey:key] forKey:key]; } }
|