新增类及其成员

1.API

To create a new class, start by calling objc_allocateClassPair. Then set the class’s attributes with functions like class_addMethod and class_addIvar. When you are done building the class, call objc_registerClassPair. The new class is now ready for use.
Instance methods and instance variables should be added to the class itself. Class methods should be added to the metaclass.

1.1.增加类

1
2
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes);
void objc_registerClassPair(Class cls)

这两个方法配对使用,在二者之间增加成员变量、属性、实例方法和类方法等。调用第二个方法即注册类,这之后类的结构也就固定下来,不能再向类中增加新的成员变量,但是可以增加属性和方法。其中新增的属性不会自动生成_属性名成员变量,也不会提供setter与getter函数及其实现。

1.2.增加方法

1
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
  • 实例方法应该加到当前实例的isa指向的类中;
  • 类方法则应该加到当前类的元类中;

1.3.增加成员变量

1
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types);

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.
The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.

新增成员变量应该在类注册完成之前进行,类注册完成之后类结构已固定,实例的内存空间已经确定,不能再增加新的成员变量。

新增加的成员变量应该加到实例对象所属的类中,而不能加到其所属类的元类中。

1.4.增加属性

1
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount);

2.示例

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
#import "AppDelegate.h"
#import <objc/message.h>
#import <objc/runtime.h>

@implementation AppDelegate

void learnOnTV(id self,SEL _cmd)
{
NSLog(@"++++执行learnOnTV方法");
}

void learOnBook(id self,SEL _cmd)
{
NSLog(@"++++执行learOnBook方法");
}

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//创建新的类
Class ASDClass = objc_allocateClassPair([NSObject class], "ASDClass", 0);

//增加成员变量
const char *height = "height";
BOOL status1 = class_addIvar(ASDClass, height, sizeof(id), rint(log2(sizeof(id))), @encode(id));
if (status1) {
NSLog(@"+++++成功添加成员变量'height'~");
}else{
NSLog(@"+++++添加成员变量'height'失败~");
}

//为类增加属性
objc_property_attribute_t type = {"T","@\"NSString\""};
objc_property_attribute_t ownership1 = {"C",""}; // C = copy
objc_property_attribute_t ownership2 = { "N", "" }; // N = nonatomic
objc_property_attribute_t backIvars = {"V","_name"}; //属性名
objc_property_attribute_t atts[] = {type,ownership1,ownership2,backIvars};
BOOL status2 = class_addProperty(ASDClass, "name", atts, 4);
if (status2) {
NSLog(@"+++++成功添加属性'name'~");
}else{
NSLog(@"+++++添加属性'name'失败~");
}

//为类增加方法
class_addMethod(ASDClass, @selector(learn), (IMP)learnOnTV, "v@:");
//"v@:"字符,依次表示函数返回值类型和每个参数的类型,这里v表示返回值void,@表示调用者,:表示SEL

//注册类
objc_registerClassPair(ASDClass);

//增加成员变量(!!会失败,因为类结构已固定,实例大小已确定)
const char *width = "width";
BOOL status3 = class_addIvar(ASDClass, width, sizeof(id), rint(log2(sizeof(id))), @encode(id));
if (status3) {
NSLog(@"+++++成功添加成员变量'width'~");
}else{
NSLog(@"+++++添加成员变量'width'失败~");
}

//为类增加属性(会成功)
objc_property_attribute_t type2 = {"T","@\"NSString\""};
objc_property_attribute_t ownership3 = {"C",""}; // C = copy
objc_property_attribute_t ownership4 = { "N", "" }; // N = nonatomic
objc_property_attribute_t backIvars2 = {"V","_name"}; //属性名
objc_property_attribute_t atts2[] = {type2,ownership3,ownership4,backIvars2};
BOOL status4 = class_addProperty(ASDClass, "nick", atts2, 4);
if (status4) {
NSLog(@"+++++成功添加属性'nick'~");
}else{
NSLog(@"+++++添加属性'nick'失败~");
}
NSArray *ivarsArr = [RuntimeTool ivarListWithClass:ASDClass];
NSArray *propsArr = [RuntimeTool propertyListWithClass:ASDClass];
NSArray *methodArr = [RuntimeTool methodListWithClass:ASDClass];

NSLog(@"++++所有成员变量:%@",ivarsArr);
NSLog(@"++++所有属性:%@",propsArr);
NSLog(@"++++所有方法:%@",methodArr);

//调用learn方法
objc_msgSend([ASDClass new],@selector(learn));

//替换learn方法的实现函数为learOnBook
class_replaceMethod(ASDClass, @selector(learn), (IMP)learOnBook, "v@:");

//再次调用learn方法
objc_msgSend([ASDClass new],@selector(learn));

return YES;
}

其中RuntimeTool是工具类,用来获取成员变量、属性和方法列表,实现代码如下:

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
93
94
95
96
97
98
99
100
101
102
103
104
#import "RuntimeTool.h"
#import <objc/runtime.h> //包含对类、成员变量、属性、方法的操作
#import <objc/message.h> //包含消息机制

@implementation RuntimeTool

/**
获取类中所有成员变量的名称与类型

@param aClass 目标类名
@return 返回一个数组,{键:变量名,值:类型}
*/
+ (NSArray *)ivarListWithClass:(Class)aClass
{
if (!object_isClass(aClass)){
return nil;
}

unsigned int count = 0;
NSMutableArray *resultArr = [NSMutableArray array];

Ivar *ivars = class_copyIvarList([aClass class], &count);

for (int i = 0; i<count; i++){
Ivar ivar = ivars[i];
const char *charName = ivar_getName(ivar);
const char *charType = ivar_getTypeEncoding(ivar);

NSString *name = [NSString stringWithCString:charName encoding:NSUTF8StringEncoding];

NSString *type = [NSString stringWithCString:charType encoding:NSUTF8StringEncoding];

NSDictionary *dic = @{@"ivar_name":name,@"objc_type":type};

[resultArr addObject:dic];
}

free(ivars);

return resultArr;
}

/**
获取类中所有属性的名称与类型

@param aClass 目标类名
@return 返回一个数组,{键:属性名,值:类型}
*/
+ (NSArray *)propertyListWithClass:(Class)aClass
{
if (!object_isClass(aClass)){
return nil;
}

unsigned int count = 0;
NSMutableArray *resultArr = [NSMutableArray array];

objc_property_t *properties = class_copyPropertyList([aClass class], &count);

for (int i = 0; i<count; i++){
objc_property_t aProperty = properties[i];
const char *charName = property_getName(aProperty);
const char *charType = property_getAttributes(aProperty);

NSString *name = [NSString stringWithCString:charName encoding:NSUTF8StringEncoding];

NSString *type = [NSString stringWithCString:charType encoding:NSUTF8StringEncoding];

NSDictionary *dic = @{@"property_name":name,@"objc_type":type};

[resultArr addObject:dic];
}

free(properties);

return resultArr;
}

/**
获取类中所有方法名

@param aClass 目标类
@return 返回所有方法名组成的一个数组
*/
+ (NSArray *)methodListWithClass:(Class)aClass
{
if (!object_isClass(aClass)){
return nil;
}
NSMutableArray *methodArr = [NSMutableArray array];

unsigned int count = 0;
Method *methods = class_copyMethodList(aClass, &count);

for (int i = 0; i < count; i++) {
SEL sel_name = method_getName(methods[i]);
NSString *name = [NSString stringWithCString:sel_getName(sel_name) encoding:NSUTF8StringEncoding];

[methodArr addObject:name];
}
free(methods);

return methodArr;
}

注意:直接调用objc_msgSend在编译时会报错,需要在 “Build Setting”中,把“ Enable Strict Checking of objc_msgSend Calls” 关掉。

示例运行后输出日志:

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
+++++成功添加成员变量'height'~
+++++成功添加属性'name'~
+++++添加成员变量'width'失败~
+++++成功添加属性'nick'~
++++所有成员变量:(
{
"ivar_name" = height;
"objc_type" = "@";
}
)
++++所有属性:(
{
"objc_type" = "T@\"NSString\",C,N,V_name";
"property_name" = nick;
},
{
"objc_type" = "T@\"NSString\",C,N,V_name";
"property_name" = name;
}
)
++++所有方法:(
learn
)
++++执行learnOnTV方法
++++执行learOnBook方法

从日志中也可以得出结论:

1、在类allocateregister之间可以添加成员变量属性方法

2、在register之后,即类结构固定之后不能再添加成员变量,但可以继续添加属性方法

3、动态添加的属性,不会创建对应的以_开头的成员变量,这一步需要由编译器来完成。

ps: 热更新JSPatch就是在OC的 runtime 基础上实现的,大致原理是:JS传递字符串给OC,OC通过 Runtime 的运行时函数,调用和替换相应的OC方法。待后期慢慢研究。。


新增类及其成员
https://davidlii.cn/2017/09/01/runtime-addClass.html
作者
Davidli
发布于
2017年9月1日
许可协议