运行时小结


第一部分

objc_msgSend 这是Objective-C的方法调用的核心,它可调用一个类的所有方法,不管它有没有暴露出来。

例如:
TestObj.h 文件:

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

@interface TestObj : NSObject

@property (nonatomic, assign) NSInteger userId;
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, assign) BOOL gender;


+ (NSInteger)test;
+ (NSString *)testWithArg:(NSInteger)arg1 arg2:(NSString *)arg2;

- (BOOL)isChnage;

@end
TestObj.m 文件:

@implementation TestObj
+ (NSInteger)test
{
NSLog(@"这是测试方法");

return 6;
}

+ (NSString *)testWithArg:(NSInteger)arg1 arg2:(NSString *)arg2
{
NSLog(@"测试方法 : %ld -- %@", arg1, arg2);

return @"test";
}

+ (void)test2
{
NSLog(@"内部方法");
}

- (BOOL)isChnage
{
NSLog(@“有没有改变");

return NO;
}

- (NSString *)changeUserName:(NSString *)userName
{
return @"修改后的名字";
}

@end

以下是调用示例:

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

// 直接调用类方法,返回值需要强转,也可以不处理返回值
NSInteger test1 = (NSInteger)objc_msgSend(objc_getClass("TestObj"), sel_registerName("test"));

NSLog(@"测试1:%ld", test1);

// 调用有参数的类方法
NSString *test2 = (NSString *) objc_msgSend(objc_getClass("TestObj"), @selector(testWithArg:arg2:), 6, @"消息");

NSLog(@"测试2:%@", test2);

// 直接调用TestObj里面的类方法,尽管它没有暴露出来, 这就是运行时的厉害之处
objc_msgSend(objc_getClass("TestObj"), sel_registerName("test2"));

// 调用方法
TestObj *testObj = [TestObj new];
objc_msgSend(testObj, sel_registerName("isChnage"));


第二部分

无论目标类有没有将方法暴露出来,一样可以同过运行时将它遍历出来,并可以找出方法的入参.

示例:

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

unsigned int methodCount;
Method *methods = class_copyMethodList(objc_getClass("TestObj"), &methodCount);
for (int i = 0; i < methodCount; i++) {

Method tempMethod = methods[i];

NSLog(@“方法名称:%s", sel_getName(method_getName(tempMethod)));
NSLog(@“入参个数:%d", method_getNumberOfArguments(tempMethod));
NSLog(@“%s”, method_getTypeEncoding(tempMethod));
NSLog(@“返回值类型:%s”, method_copyReturnType(tempMethod));

IMP imp = method_getImplementation(tempMethod);
NSLog(@"IMP: %@", imp_getBlock(imp));

NSLog(@"---入参----\n");

unsigned int argCount = method_getNumberOfArguments(tempMethod);
for (int j = 0; j < argCount; j++) {
NSLog(@"%s", method_copyArgumentType(tempMethod, j));
}
}

// 手动释放,避免内存泄露
free(methods);

1
2
1. 方法的入参默认会有两个,这是系统运行时添加进去的。
2. 运行时的入参和返回的数据类型跟OC 的数据类型不同,以下是苹果官方提供的类型对应列表

第三部分

通过运行时,可以获取方法的变量,并且可以通过映射,动态修改变量名称,MJExtension 框架就是利用了这一特点,所以它可以通过MJExtensionConfig 文件来修改参数的映射。

参考:http://www.cnblogs.com/ludashi/p/4673935.html

示例:

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

unsigned int propertyCount;
objc_property_t *propertys = class_copyPropertyList(objc_getClass("TestObj"), &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t tempProperty = propertys[i];

NSLog(@“成员变量名称:%s", property_getAttributes(tempProperty));
NSLog(@"成员变量描述:%s”, property_getAttributes(tempProperty));
}

// 释放
free(propertys);


第四部分

一个对象可以绑定任何一个对象,包括 block。

涉及到的方法如下:

1
2
3
4
5
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)  // 设置绑定对象

objc_getAssociatedObject(id object, const void *key) // 获得绑定对象

objc_removeAssociatedObjects(id object) // 移除绑定对象

绑定对象的时候,需要根据对象的属性,设置不同的关联策略,也就是Objc的内存管理的引用计数机制,包括有:

1
2
3
4
5
6
7
8
9
OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */

OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.

OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.

OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.

OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.

示例代码:

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

// 绑定对象
TestObj2 *a = [TestObj2 new];
a.userID = 1001;
a.userName = @"AbooJan";
a.books = @[@"Harry", @"永无止境", @"编程思想"];

_test = [[TestObj alloc] init];
objc_setAssociatedObject(_test, "testObj", a, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(_test, "testNum", @(666), OBJC_ASSOCIATION_ASSIGN);

// 获得绑定对象
TestObj2 *a = objc_getAssociatedObject(_test, "testObj”);
NSNumber *testNum = objc_getAssociatedObject(_test, "testNum");

NSLog(@"%@", [a description]);
NSLog(@"%ld", [testNum integerValue]);


第五部分

Method Swizzling,本身Objc的方法调用是通过消息转发机制来实现的,既然如此,就可以通过重新映射方法对应的实现来达到“偷天换日”的目的。跟消息转发相比,Method Swizzling 的做法更为隐蔽,甚至有些冒险,也增大了debug的难度。

代码示例:

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

/*
* 1. 如果是一个子类,一般在 initialize 方法中替换方法
* 2. 如果是一个分类,一般在 load 方法中实行替换
*
* 在以上两个方法中替换,是保证它在一启动的时候就实行替换
*
*
* load和initialize有很多共同特点,下面简单列一下:

* 1.在不考虑开发者主动使用的情况下,系统最多会调用一次
* 2.如果父类和子类都被调用,父类的调用一定在子类之前
* 3.都是为了应用运行提前创建合适的运行环境
* 4.在使用时都不要过重地依赖于这两个方法,除非真正必要
*
*
* +load 能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。
* +initialize 在其所属类的方法被调用或类初始化时会被调用,否则它可能永远不会被调用
*
*/
+ (void)load
{
NSLog(@"load");
}

+ (void)initialize
{
// dispatch_once是GCD中的一个方法,它保证了代码块只执行一次,并让其为一个原子操作,
// 保证线程的安 全,避免并发引发问题,认为它是Method Swizzling 的最佳实现


static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

// 第一种初始化方法
Class target = [self class];

Method originalMethod = class_getInstanceMethod(target, sel_registerName("description"));
Method swizzMethod = class_getInstanceMethod(target, sel_registerName("swizzle_description"));


// 第二种初始化方法
// Class aClass = object_getClass((id)self);
//
// Method originalMethod = class_getClassMethod(aClass, @selector(description));
// Method swizzledMethod = class_getClassMethod(aClass, @selector(swizzle_description));


/*
* object_getClass((id)self) 与 [self class] 返回的结果类型都是 Class,
* 但前者为元类,后者为其本身,因为此时 self 为 Class 而不是实例。
*/


/*
* 如果类中不存在要替换的方法,那就先用class_addMethod和class_replaceMethod函数添加
* 和替换两个方法的实现;如果类中已经有了想要替换的方法,那么就直接调
* 用 method_exchangeImplementations函数交换了两个方法的 IMP。
* 个人建议将要替换的方法实现出来.
*/


// BOOL didAddMethod = class_addMethod(target, method_getName(originalMethod), method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
//
// if (didAddMethod) {
//
// class_replaceMethod(target, method_getName(swizzMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
//
// }else{
// method_exchangeImplementations(originalMethod, swizzMethod);
// }


// 如果确定已添加替换的方法,直接执行替换就可以了
method_exchangeImplementations(originalMethod, swizzMethod);

});
}

- (NSString *)description
{
NSLog(@"原来的方法");

return [super description];
}

- (void)swizzle_description
{
NSLog(@"这是替换的方法");

// 本身替换的方法只会执行一次,当执行以下方法的时候,它就会调用该类原来的方法了
[self aboo_description];


// 如果调用以下方法,就会进入死循环
// [self description];
}

Demo: https://github.com/AbooJan/RunTimeDemo.git