Objective-C Runtime 学习(二)

来源:ian(@ianisme)
链接:http://www.ianisme.com/ios/2019.html

本篇描述的内容主要是如何运用runtime机制,调用一些底层的实现方法,动态的获取类的成员变量,属性,方法,以及一些更高级的操作,从而加深对runtime机制的了解。上一篇是基础内容的了解,【传送门-Objective-C Runtime 学习(一)

获取类的成员变量和属性篇

苍老师在大家心目中应该有很多特征吧,下面我们通过代码来获取苍老师的特征。

People.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface People : NSObject
{
NSString *_occupation;
NSString *_nationality;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
- (NSDictionary *)allProperties;
- (NSDictionary *)allIvars;
- (NSDictionary *)allMethods;
@end

People.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
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People
- (NSDictionary *)allProperties
{
unsigned int count = 0;
// 获取类的所有属性,如果没有属性count就为0
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableDictionary *resultDict = [<a href="http://www.jobbole.com/members/www821839432">@{}</a> mutableCopy];
for (NSUInteger i = 0; i < count; i ++) {
// 获取属性的名称和值
const char *propertyName = property_getName(properties[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
id propertyValue = [self valueForKey:name];
if (propertyValue) {
resultDict[name] = propertyValue;
} else {
resultDict[name] = @"字典的key对应的value不能为nil哦!";
}
}
// 这里properties是一个数组指针,我们需要使用free函数来释放内存。
free(properties);
return resultDict;
}
- (NSDictionary *)allIvars
{
unsigned int count = 0;
NSMutableDictionary *resultDict = [<a href="http://www.jobbole.com/members/www821839432">@{}</a> mutableCopy];
Ivar *ivars = class_copyIvarList([self class], &count);
for (NSUInteger i = 0; i < count; i ++) {
const char *varName = ivar_getName(ivars[i]);
NSString *name = [NSString stringWithUTF8String:varName];
id varValue = [self valueForKey:name];
if (varValue) {
resultDict[name] = varValue;
} else {
resultDict[name] = @"字典的key对应的value不能为nil哦!";
}
}
free(ivars);
return resultDict;
}
- (NSDictionary *)allMethods
{
unsigned int count = 0;
NSMutableDictionary *resultDict = [<a href="http://www.jobbole.com/members/www821839432">@{}</a> mutableCopy];
// 获取类的所有方法,如果没有方法count就为0
Method *methods = class_copyMethodList([self class], &count);
for (NSUInteger i = 0; i < count; i ++) {
// 获取方法名称
SEL methodSEL = method_getName(methods[i]);
const char *methodName = sel_getName(methodSEL);
NSString *name = [NSString stringWithUTF8String:methodName];
// 获取方法的参数列表
int arguments = method_getNumberOfArguments(methods[i]);
resultDict[name] = @(arguments-2);
}
free(methods);
return resultDict;
}
@end

在main.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
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *cangTeacher = [[People alloc] init];
cangTeacher.name = @"苍井空";
cangTeacher.age = 18;
[cangTeacher setValue:@"老师" forKey:@"occupation"];
NSDictionary *propertyResultDic = [cangTeacher allProperties];
for (NSString *propertyName in propertyResultDic.allKeys) {
NSLog(@"propertyName:%@, propertyValue:%@",propertyName, propertyResultDic[propertyName]);
}
NSDictionary *ivarResultDic = [cangTeacher allIvars];
for (NSString *ivarName in ivarResultDic.allKeys) {
NSLog(@"ivarName:%@, ivarValue:%@",ivarName, ivarResultDic[ivarName]);
}
NSDictionary *methodResultDic = [cangTeacher allMethods];
for (NSString *methodName in methodResultDic.allKeys) {
NSLog(@"methodName:%@, argumentsCount:%@", methodName, methodResultDic[methodName]);
}
}
return 0;
}

最后的输出结果如下:
这里写图片描述

是不是有点失望,我没有加一些特殊的技能,留给下文了。此实战内容是通过苍老师的一些特征学习:如何获取对象所有的属性名称和属性值、获取对象所有成员变量名称和变量值、获取对象所有的方法名和方法参数数量。

6.3 苍老师增加新技能篇

苍老师要通过Category和Associated Objects增加技能了,快看!
创建People+Associated.h文件如下:

1
2
3
4
5
6
7
8
9
10
#import "People.h"
typedef void (^CodingCallBack)();
@interface People (Associated)
@property (nonatomic, strong) NSNumber *associatedBust; // 胸围
@property (nonatomic, copy) CodingCallBack associatedCallBack; // 写代码
@end

People+Associated.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
#import "People+Associated.h"
#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif
@implementation People (Associated)
- (void)setAssociatedBust:(NSNumber *)bust
{
// 设置关联对象
objc_setAssociatedObject(self, @selector(associatedBust), bust, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSNumber *)associatedBust
{
// 得到关联对象
return objc_getAssociatedObject(self, @selector(associatedBust));
}
- (void)setAssociatedCallBack:(CodingCallBack)callback {
objc_setAssociatedObject(self, @selector(associatedCallBack), callback, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (CodingCallBack)associatedCallBack {
return objc_getAssociatedObject(self, @selector(associatedCallBack));
}
@end

在main.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
28
29
#import "People.h"
#import "People+Associated.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *cangTeacher = [[People alloc] init];
cangTeacher.name = @"苍井空";
cangTeacher.age = 18;
[cangTeacher setValue:@"老师" forKey:@"occupation"];
cangTeacher.associatedBust = @(90);
cangTeacher.associatedCallBack = ^(){
NSLog(@"苍老师要写代码了!");
};
cangTeacher.associatedCallBack();
NSDictionary *propertyResultDic = [cangTeacher allProperties];
for (NSString *propertyName in propertyResultDic.allKeys) {
NSLog(@"propertyName:%@, propertyValue:%@",propertyName, propertyResultDic[propertyName]);
}
NSDictionary *methodResultDic = [cangTeacher allMethods];
for (NSString *methodName in methodResultDic.allKeys) {
NSLog(@"methodName:%@, argumentsCount:%@", methodName, methodResultDic[methodName]);
}
}
return 0;
}

这次运行结果多出了一个associatedBust(胸围)和一个associatedCallBack(写代码)属性。
如图:
这里写图片描述

我们成功的给苍老师添加个一个胸围的属性和一个写代码的回调,但是添加属性没有什么意义,我们平时在开发过成功中用的比较多的就是添加回调了。