iOS 开发之黑科技—runtime

runtime其实就是oc底层的一套C语音的API

调用方法的本质就是发消息,

1、动态交换两个方法的实现(特别是交换系统自动的方法)

2、动态添加对象的成员变量和成员方法

3、获得某个类的所有成员方法、所有成员变量

注意:

对于一般OC代码的method swizzling, 在load方法中执行即可. 而Swift没有load, 所以要在initialize中执行.

应用

1、block的实现原理

2、拦截系统自带的方法调用

1
2
3
4
5
6
7
8
9
10
+ (void)load {
// 获取两个类的类方法
Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
Method m2 = class_getClassMethod([UIImage class], @selector(xh_imageNamed:));
// 开始交换方法实现
method_exchangeImplementations(m1, m2);
}
+ (UIImage *)xh_imageNamed:(NSString *)name {
return [UIImage xh_imageNamed:name];
}

3、实现类别也可以增加属性

1
2
3
4
5
6
7
-(NSString *)name{
return objc_getAssociatedObject(self, @"nameKey");
}
-(void)setName:(NSString *)name{
objc_setAssociatedObject(self, @"nameKey", name , OBJC_ASSOCIATION_COPY_NONATOMIC);
}

4、实现nscodeing的自动归档和自动接档

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
- (void)decode:(NSCoder *)aDecoder {
// 一层层父类往上查找,对父类的属性执行归解档方法
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(c, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
}
}
- (void)encode:(NSCoder *)aCoder {
// 一层层父类往上查找,对父类的属性执行归解档方法
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int outCount = 0;
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)];
// 如果有实现该方法再去调用
id value = [self valueForKeyPath:key];
[aCoder encodeObject:value forKey:key];
}
}
}

5、实现字典和模型的自动转换

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大专栏  iOS开发黑科技之runtimev>
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
- (void)setDict:(NSDictionary *)dict {
Class c = self.class;
while (c &&c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(c, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 成员变量名转为属性名(去掉下划线 _ )
key = [key substringFromIndex:1];
// 取出字典的值
id value = dict[key];
// 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错
if (value == nil) continue;
// 获得成员变量的类型
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 如果属性是对象类型
NSRange range = [type rangeOfString:@"@"];
if (range.location != NSNotFound) {
// 那么截取对象的名字(比如@"Dog",截取为Dog)
type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
// 排除系统的对象类型
if (![type hasPrefix:@"NS"]) {
// 将对象名转换为对象的类型,将新的对象字典转模型(递归)
Class class = NSClassFromString(type);
value = [class objectWithDict:value];
}else if ([type isEqualToString:@"NSArray"]) {
// 如果是数组类型,将数组中的每个模型进行字典转模型,先创建一个临时数组存放模型
NSArray *array = (NSArray *)value;
NSMutableArray *mArray = [NSMutableArray array];
// 获取到每个模型的类型
id class ;
if ([self respondsToSelector:@selector(arrayObjectClass)]) {
NSString *classStr = [self arrayObjectClass];
class = NSClassFromString(classStr);
}else {
NSLog(@"数组内模型是未知类型");
return;
}
// 将数组中的所有模型进行字典转模型
for (int i = 0; i < array.count; i++) {
[mArray addObject:[class objectWithDict:value[i]]];
}
value = mArray;
}
}
// 将字典中的值设置到模型上
[self setValue:value forKeyPath:key];
}
free(ivars);
c = [c superclass];
}
}
+ (instancetype )objectWithDict:(NSDictionary *)dict {
NSObject *obj = [[self alloc]init];
[obj setDict:dict];
return obj;
}

这两个都是

Ivar * ivars = class_copyIvarList(self.class, &outCount);
就不用一个一个属性写了。
再说一下归档解档

如果不是系统的类,要进行归档解档要遵守协议,和实现协议中的方法

1
2
3
4
5
6
7
8
9
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.name forKey:@"name"];
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
}

⬅️ Go back