Runtime 使用

主要包含 字典与模型互转 、对象关联、遍历变量、遍历方法、遍历协议

字典与模型互转

字典转模型的时候:

  1. 根据字典的 key 生成 setter 方法.
  2. 使用 objc_msgSend 调用 setter 方法为 Model 的属性赋值(或者 KVC).

模型转字典的时候:

  1. 调用 class_copyPropertyList 方法获取当前 Model 的所有属性.
  2. 调用 property_getName 获取属性名称.
  3. 根据属性名称生成 getter 方法.
  4. 使用 objc_msgSend 调用 getter 方法获取属性值(或者 KVC).

示例:

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
59
60
61
62
63
64

#import "NSObject+KeyValues.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation NSObject (KeyValues)

//字典转模型
+(id)objectWithKeyValues:(NSDictionary *)aDictionary{
id objc = [[self alloc] init];
for (NSString *key in aDictionary.allKeys) {
id value = aDictionary[key];

/*判断当前属性是不是Model*/
objc_property_t property = class_getProperty(self, key.UTF8String);
unsigned int outCount = 0;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);
objc_property_attribute_t attribute = attributeList[0];
NSString *typeString = [NSString stringWithUTF8String:attribute.value];
if ([typeString isEqualToString:@"@\"TestModel\""]) {
value = [self objectWithKeyValues:value];
}
/**********************/

//生成setter方法,并用objc_msgSend调用
NSString *methodName = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];
SEL setter = sel_registerName(methodName.UTF8String);
if ([objc respondsToSelector:setter]) {
((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
}
}
return objc;
}

//模型转字典
-(NSDictionary *)keyValuesWithObject{
unsigned int outCount = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (int i = 0; i < outCount; i ++) {
objc_property_t property = propertyList[i];

//生成getter方法,并用objc_msgSend调用
const char *propertyName = property_getName(property);
SEL getter = sel_registerName(propertyName);
if ([self respondsToSelector:getter]) {
id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);

/*判断当前属性是不是Model*/
if ([value isKindOfClass:[self class]] && value) {
value = [value keyValuesWithObject];
}
/**********************/

if (value) {
NSString *key = [NSString stringWithUTF8String:propertyName];
[dict setObject:value forKey:key];
}
}

}
return dict;
}
@end

对象关联

对已经存在的类在 Category 中添加自定义的属性:

  • 要取出被关联的对象使用 objc_getAssociatedObject 方法即可,
  • 要删除一个被关联的对象,使用 objc_setAssociatedObject 方法将对应的 key 设置成 nil 即可
  • objc_removeAssociatedObjects 方法将会移除源对象中所有的关联对象.

示例:

为类别添加chineseName属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#import "XiaoMing+MutipleName.h"
#import <objc/runtime.h>
@implementation XiaoMing (MutipleName)
char cName;

-(void)setChineseName:(NSString *) chineseName{
objc_setAssociatedObject(self, &cName, chineseName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)chineseName{
return objc_getAssociatedObject(self, &cName);
}

@end

举个栗子,假如我们要给 UIButton 添加一个监听单击事件的 block 属性,新建 UIButton 的 Category,其.m文件如下:

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

#import "UIButton+ClickBlock.h"
#import <objc/runtime.h>

static const void *associatedKey = "associatedKey";

@implementation UIButton (ClickBlock)

//Category中的属性,只会生成setter和getter方法,不会生成成员变量

-(void)setClick:(clickBlock)click{
objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
if (click) {
[self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
}
}

-(clickBlock)click{
return objc_getAssociatedObject(self, associatedKey);
}

-(void)buttonClick{
if (self.click) {
self.click();
}
}

然后在代码中,就可以使用 UIButton 的属性来监听单击事件了:

1
2
3
4
5
6
7

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.view.bounds;
[self.view addSubview:button];
button.click = ^{
NSLog(@"buttonClicked");
};

遍历变量

  1. 使用 class_copyIvarList 方法获取当前 Model 的所有成员变量.
  2. 使用 ivar_getName 方法获取成员变量的名称.
  3. 通过 KVC 来读取 Model 的属性值

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

#import <objc/runtime.h>
#import <objc/message.h>

...

unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([obj class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
const char *type = ivar_getTypeEncoding(ivar);
NSString *key = [NSString stringWithUTF8String:name];

// 注意kvc的特性是,如果能找到key这个属性的setter方法,则调用setter方法
// 如果找不到setter方法,则查找成员变量key或者成员变量_key,并且为其赋值
// 所以这里不需要再另外处理成员变量名称的“_”前缀
id value = [obj valueForKey:key];
//对value做相应操作
...
}

自动归档

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

#import "TestModel.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation TestModel
- (void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];

// 注意kvc的特性是,如果能找到key这个属性的setter方法,则调用setter方法
// 如果找不到setter方法,则查找成员变量key或者成员变量_key,并且为其赋值
// 所以这里不需要再另外处理成员变量名称的“_”前缀
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
}

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
}
return self;
}
@end

修改变量值

  1. 使用 class_copyIvarList 方法获取当前 Model 的所有成员变量.
  2. 使用 ivar_getName 方法获取成员变量的名称.
  3. 通过 KVC 来读取 Model 的属性值
  4. 修改对应的字段值 object_setIvar(id obj, Ivar ivar, id value)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14

unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([obj class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];

if ([name isEqualToString:@"_shuxingming"]) {
object_setIvar(self, var, @"新的值");
break;
}

}

遍历属性

  1. 通过class_copyPropertyList函数来获取属性列表,其中属性列表是使用@property声明的列表,对于直接使用声明为成员变量的,都不会出现在属性列表中,这也是正常的。
  2. 我们通过runtime提供的property_getName函数来获取属性名称。
  3. 若有获取属性的详细描述,可通过runtime提供的property_getAttributes函数来获取。
  4. 若有获取属性中的objc_property_attribute_t列表,可以通过property_copyAttributeList函数来获取。
  5. 若有获取单独的objc_property_attribute_t的name或者value,直接使用点语法即可,它是一个结构体。

示例:

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

#import <objc/runtime.h>
#import <objc/message.h>

...

unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([obj class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//获取属性名称
const char *name = property_getName(property);
NSString *key = [NSString stringWithUTF8String:name];

const char *propertyAttributes = property_getAttributes(property);
NSLog(@"%s %s", propertyName, propertyAttributes);

unsigned int count = 0;
objc_property_attribute_t *attrbutes = property_copyAttributeList(property, &count);
for (unsigned int j = 0; j < count; ++j) {
objc_property_attribute_t attribute = attrbutes[j];

const char *name = attribute.name;
const char *value = attribute.value;
NSLog(@"name: %s value: %s", name, value);
}

free(attrbutes);
}

遍历方法

  1. 使用 class_copyMethodList 方法获取当前 Model 的所有成员变量.
  2. 使用 method_getName 方法获取成员变量的名称.

示例:

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

#import <objc/runtime.h>
#import <objc/message.h>

...

unsigned int outCount = 0;
Method *methods = class_copyMethodList([obj class], &outCount);
for (int i = 0; i < outCount; i ++) {
Method method = methods[i];
//获取方法
SEL methodSEL = method_getName(method);
const char *name = sel_getName(methodSEL);
NSString *key = [NSString stringWithUTF8String:name];
//获取方法的参数个数
int arguments = method_getNumberOfArguments(method);
// 获取方法的参数类型
char argName[512] = {};
for (unsigned int j = 0; j < arguments; ++j) {
method_getArgumentType(method, j, argName, 512);
NSLog(@"第%u个参数类型为:%s", j, argName);
memset(argName, '\0', strlen(argName));
}
char returnType[512] = {};
method_getReturnType(method, returnType, 512);
NSLog(@"返回值类型:%s", returnType);

// type encoding
NSLog(@"TypeEncoding: %s", method_getTypeEncoding(method));

}
free(methods);

动态交换方法

  1. Method class_getInstanceMethod(Class cls, SEL name) 找到方法实例
  2. void method_exchangeImplementations(Method m1, Method m2) 交换方法

示例

1
2
3
4
5
6
7
8
9
10
11

-(void)answer{
Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(firstSay));
Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(secondSay));

method_exchangeImplementations(m1, m2);
NSString *secondName = [self.xiaoMing firstSay];

self.nameTf.text = secondName;
NSLog(@"XiaoMing:My name is %@",secondName);
}

Method Swizzling 原理

归根结底,都是偷换了selector的IMP

  • method_exchangeImplementations 来交换2个方法中的IMP,
  • 利用 class_replaceMethod 来修改类,
  • 利用 method_setImplementation 来直接设置某个方法的IMP

动态添加方法

  1. void class_addMethods(Class, struct objc_method_list *)添加多个方法
  2. BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)添加方法
    • (IMP)guessAnswer 意思是guessAnswer的地址指针;
    • “v@:” 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;“v@:@@” 意思是,两个参数的没有返回值。

示例:

假设XiaoMing的中没有guess这个方法,后来被Runtime添加一个名字叫guess的方法,最终再调用guess方法做出相应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

-(void)answer{
class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");
if ([self.xiaoMing respondsToSelector:@selector(guess)]) {

[self.xiaoMing performSelector:@selector(guess)];

} else{
NSLog(@"Sorry,I don't know");
}
self.cityTf.text = @"GuangTong";
}

void guessAnswer(id self,SEL _cmd){

NSLog(@"He is from GuangTong");

}

调用:

`[self.xiaoMing performSelector:@selector(guess)];`

遍历协议

  1. 使用 class_copyProtocolList 方法获取当前 Model 的所有成员变量.
  2. 使用 protocol_getName 方法获取成员变量的名称.

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#import <objc/runtime.h>
#import <objc/message.h>

...

unsigned int outCount = 0;
_unsafe_unretained Protocol **protocols = class_copyProtocolList([obj class], &outCount);
for (int i = 0; i < outCount; i ++) {
Protocol *protocol = protocols[i];
const char *name = protocol_getName(protocol);
NSString *key = [NSString stringWithUTF8String:name];


}