前言
上篇主要讲Runtime的一些术语描述和定义,Runtime的主要应用是用于消息的传递,今天会结合一些实战例子来讲下OC的消息传递机制。
普通消息传递
在OC里,对象调用方法叫作发送消息,对象调用方法在Runtime里被转化为objc_msgSend函数来实现
1
2
3
[ receiver oneMethod ];
//transfer to :
objc_msgSend ( receiver , @selector ( oneMethod ));
Runtime会根据类型自动转换为下列某个函数:
objc_msgSend :普通的消息都会通过该函数发送;
objc_msgSend_stret :消息中有数据结构作为返回值(不是简单值)时,通过此函数发送和接收返回值;
objc_msgSendSuper :和objc_msgSend类似,这里把消息发送给父类的实例;
objc_msgSendSuper_stret :和objc_msgSend_stret类似,这里把消息发送给父类的实例并接收返回值;
objc_msgSend的调用过程
1.
先检查这个selector是否存在,不存在则忽略;
2.
接着检查selector的target是否为nil,向nil对象发送任何消息都会被忽略掉;
3.
前面两步没问题,则先在isa指针指向的class的cache里面找有没有方法调用记录,如果有,则运行对应的函数,如果没有则在class的methodLists查找方法,没有则通过super_class指针找到父类的类对象结构体,然后从methodLists查找方法,如果仍找不到,则继续通过
super_class向上一级父类结构体中查找,直到根类(NSObject);
4.
如果还是找不到,则进入消息动态解析 ;
消息动态解析
动态解析流程图:
具体解析:
1.
通过resolveInstanceMethod,该方法决定是否动态添加方法。如果返回YES,则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回NO,则进入下一步;
2.
这一步会步入forwardingTargetForSelector方法,用于指定备选对象响应这个selector,不能指定为self,如果放回某个对象则会调用对象的方法,结束。如果放回nil,则进入第三步;
3.
这一步,我们通过methodSignatureForSelector进行方法签名,如果返回nil,则消息无法处理。如果返回methodSignature则进入下一步;
4.
这步调用forwardInvocation方法,我们通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象,如果方法方法调用成功,则结束。如果失败,则进入doesNotRecognizeSelector,如果没有实现这个方法会crash
实战
通过runtime动态创建类和对象
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
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
void sayFunction ( id self , SEL _cmd , id some ){
NSLog ( @"%@岁的%@说:%@" , object_getIvar ( self , class_getInstanceVariable ([ self class ], "_age" )), object_getIvar ( self , class_getInstanceVariable ([ self class ], "_name" )), some );
}
int main ( int argc , const char * argv []) {
@autoreleasepool {
//动态创建类
Class MyPeople = objc_allocateClassPair ([ NSObject class ], "Person" , 0 );
//为类添加成员变量
class_addIvar ( MyPeople , "_name" , sizeof ( NSString * ), log2 ( sizeof ( NSString * )), @encode ( NSString * ));
class_addIvar ( MyPeople , "_age" , sizeof ( int ), sizeof ( int ), @encode ( int ));
//注册方法("v@:@"代表返回值+参数列表)
SEL say = sel_registerName ( "say:" );
class_addMethod ( MyPeople , say , ( IMP ) sayFunction , "v@:@" );
//注册类
objc_registerClassPair ( MyPeople );
//创建实例对象
id peopleInstance = [[ MyPeople alloc ] init ];
[ peopleInstance setValue : @"李明" forKey : @"name" ];
//获取成员变量
Ivar age = class_getInstanceVariable ( MyPeople , "_age" );
object_setIvar ( peopleInstance , age , @18 );
//发送消息
objc_msgSend ( peopleInstance , say , @"你好呀" );
//销毁实例对象
peopleInstance = nil ;
//当类或子类的实例存在,则不能销毁类
objc_disposeClassPair ( MyPeople );
}
return 0 ;
}
tip:
默认会出现以下错误:
objc_msgSend()报错Too many arguments to function call ,expected 0,have3
直接通过objc_msgSend(self, setter, value)是报错,说参数过多。
请这样解决:
Build Setting–> Apple LLVM 7.0 – Preprocessing–> Enable Strict Checking of objc_msgSend Calls 改为 NO
通过runtime获取类的相关信息(属性、实例变量、方法)
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
#import <Foundation/Foundation.h>
@interface People : NSObject {
NSString * _nationality ;
}
@property ( nonatomic , copy ) NSString * name ;
@property ( nonatomic , strong ) NSNumber * age ;
/**
* 获取所有属性
**/
-( NSDictionary * ) getAllProperties ;
/**
* 获取所有实例变量
**/
-( NSDictionary * ) getAllIvars ;
/**
* 获取所有方法
**/
-( NSDictionary * ) getAllMethods ;
@end
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
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People
-( NSDictionary * ) getAllProperties {
unsigned int count = 0 ;
NSMutableDictionary * result = [ @{} mutableCopy ];
objc_property_t * properties = class_copyPropertyList ([ self class ], & count );
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 ) {
result [ name ] = propertyValue ;
} else {
result [ name ] = @"value 不能为 nil" ;
}
}
free ( properties );
return result ;
}
-( NSDictionary * ) getAllIvars {
unsigned int count = 0 ;
NSMutableDictionary * result = [ @{} mutableCopy ];
Ivar * ivars = class_copyIvarList ([ self class ], & count );
for ( NSUInteger i = 0 ; i < count ; i ++ ) {
const char * ivarName = ivar_getName ( ivars [ i ]);
NSString * name = [ NSString stringWithUTF8String : ivarName ];
id value = [ self valueForKey : name ];
if ( value ) {
result [ name ] = value ;
} else {
result [ name ] = @"value 不能为 nil" ;
}
}
free ( ivars );
return result ;
}
-( NSDictionary * ) getAllMethods {
unsigned int count = 0 ;
NSMutableDictionary * result = [ @{} mutableCopy ];
Method * methods = class_copyMethodList ([ self class ], & count );
for ( NSUInteger i = 0 ; i < count ; i ++ ) {
const char * methodName = sel_getName ( method_getName ( methods [ i ]));
NSString * name = [ NSString stringWithUTF8String : methodName ];
//获取参数列表
int args = method_getNumberOfArguments ( methods [ i ]);
result [ name ] = [ NSString stringWithFormat : @"args count is %d" , args - 2 ];
}
free ( methods );
return result ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import <Foundation/Foundation.h>
#import "People.h"
int main ( int argc , const char * argv []) {
@autoreleasepool {
People * p = [[ People alloc ] init ];
p . name = @"罗大锤" ;
p . age = @( 30 ) ;
[ p setValue : @"中国" forKey : @"nationality" ];
NSDictionary * properties = [ p getAllProperties ];
NSDictionary * ivars = [ p getAllIvars ];
NSDictionary * methods = [ p getAllMethods ];
NSLog ( @"属性为:%@" , properties );
NSLog ( @"实例变量:%@" , ivars );
NSLog ( @"方法:%@" , methods );
}
return 0 ;
}
打印结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2016 - 05 - 13 11 : 22 : 36.298 runtime 之 获取类的相关信息 ( 属性、实例变量、方法 )[ 2783 : 307932 ] 属性为: {
age = 30 ;
name = "\U7f57\U5927\U9524" ;
}
2016 - 05 - 13 11 : 22 : 36.299 runtime 之 获取类的相关信息 ( 属性、实例变量、方法 )[ 2783 : 307932 ] 实例变量: {
"_age" = 30 ;
"_name" = "\U7f57\U5927\U9524" ;
"_nationality" = "\U4e2d\U56fd" ;
}
2016 - 05 - 13 11 : 22 : 36.299 runtime 之 获取类的相关信息 ( 属性、实例变量、方法 )[ 2783 : 307932 ] 方法: {
".cxx_destruct" = "args count is 0" ;
age = "args count is 0" ;
getAllIvars = "args count is 0" ;
getAllMethods = "args count is 0" ;
getAllProperties = "args count is 0" ;
name = "args count is 0" ;
"setAge:" = "args count is 1" ;
"setName:" = "args count is 1" ;
}
Program ended with exit code : 0
通过Runtime给category添加属性
1
2
3
4
5
6
7
8
9
#import "People.h"
typedef void ( ^ CallBackSomething )();
@interface People (testCategory)
@property ( nonatomic , copy ) NSString * address ;
@property ( nonatomic , copy ) CallBackSomething block ;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import "People+testCategory.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People (testCategory)
-( void ) setAddress: ( NSString * ) address {
objc_setAssociatedObject ( self , @selector ( address ), address , OBJC_ASSOCIATION_COPY_NONATOMIC );
}
-( NSString * ) address {
return objc_getAssociatedObject ( self , @selector ( address ));
}
-( void ) setBlock: ( CallBackSomething ) block {
objc_setAssociatedObject ( self , @selector ( block ), block , OBJC_ASSOCIATION_COPY_NONATOMIC );
}
-( CallBackSomething ) block {
return objc_getAssociatedObject ( self , @selector ( block ));
}
@end
利用Runtime给对象归档和解档
1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>
@interface People : NSObject < NSCoding > {
NSString * _nationality ;
}
@property ( nonatomic , copy ) NSString * name ;
@property ( nonatomic , strong ) NSNumber * age ;
@end
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
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People
//归档
-( void ) encodeWithCoder: ( NSCoder * ) aCoder {
unsigned int count = 0 ;
Ivar * ivars = class_copyIvarList ([ self class ], & count );
for ( NSUInteger i = 0 ; i < count ; i ++ ) {
Ivar ivar = ivars [ i ];
const char * name = ivar_getName ( ivar );
NSString * key = [ NSString stringWithUTF8String : name ];
id value = [ self valueForKey : key ];
[ aCoder encodeObject : value forKey : key ];
}
free ( ivars );
}
//解档
-( instancetype ) initWithCoder: ( NSCoder * ) aDecoder {
if ( self = [ super init ]) {
unsigned int count = 0 ;
Ivar * ivars = class_copyIvarList ([ self class ], & count );
for ( NSUInteger i = 0 ; i < count ; i ++ ) {
Ivar ivar = ivars [ i ];
const char * name = ivar_getName ( ivar );
NSString * key = [ NSString stringWithUTF8String : name ];
id value = [ aDecoder decodeObjectForKey : key ];
[ self setValue : value forKey : key ];
}
free ( ivars );
}
return self ;
}
@end
利用Runtime实现Model与字典互转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <Foundation/Foundation.h>
@interface People : NSObject
@property ( nonatomic , copy ) NSString * name ;
@property ( nonatomic , strong ) NSNumber * age ;
/**
* 字典 转 Model
**/
-( instancetype ) initWithDictionary: ( NSDictionary * ) dict ;
/**
* Model转换成字典
**/
-( NSDictionary * ) covertToDictionary ;
@end
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
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People
-( instancetype ) initWithDictionary: ( NSDictionary * ) dict {
if ( self = [ super init ]) {
for ( NSString * key in dict ) {
id val = dict [ key ];
SEL setter = [ self propertySetterByKey : key ];
if ( setter ) {
objc_msgSend ( self , setter , val );
}
}
}
return self ;
}
-( NSDictionary * ) covertToDictionary {
unsigned int count = 0 ;
objc_property_t * properties = class_copyPropertyList ([ self class ], & count );
if ( count != 0 ) {
NSMutableDictionary * result = [ @{} mutableCopy ];
for ( NSUInteger i = 0 ; i < count ; i ++ ) {
const char * propertyName = property_getName ( properties [ i ]);
NSString * name = [ NSString stringWithUTF8String : propertyName ];
SEL getter = [ self propertyGetterByKey : name ];
if ( getter ) {
id value = objc_msgSend ( self , getter );
if ( value ) {
result [ name ] = value ;
} else {
result [ name ] = @"value 为 nil" ;
}
}
}
free ( properties );
return result ;
}
free ( properties );
return nil ;
}
#pragma mark - 生成setter
-( SEL ) propertySetterByKey: ( NSString * ) key {
//key的首字母大写
NSString * propertySetterName = [ NSString stringWithFormat : @"set%@:" , key . capitalizedString ];
SEL setter = NSSelectorFromString ( propertySetterName );
if ([ self respondsToSelector : setter ]) {
return setter ;
}
return nil ;
}
-( SEL ) propertyGetterByKey: ( NSString * ) key {
SEL getter = NSSelectorFromString ( key );
if ([ self respondsToSelector : getter ]) {
return getter ;
}
return nil ;
}
@end
消息动态解析(一)
1
2
3
4
5
6
7
8
9
10
11
12
13
#import <Foundation/Foundation.h>
@interface People : NSObject
@property ( nonatomic , copy ) NSString * name ;
/** m文件不实现方法,通过runtime动态添加方法
* 通过resolveInstanceMethod:方法决定是否动态添加方法。
如果返回Yes则通过class_addMethod动态添加方法,消息得到处理,结束;如果返回No
**/
-( void ) sing ;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation People
+( BOOL ) resolveInstanceMethod: ( SEL ) sel {
if ([ NSStringFromSelector ( sel ) isEqualToString : @"sing" ]) {
class_addMethod ([ self class ], sel , ( IMP ) otherIMP , "V@:" );
return YES ;
}
return [ super resolveInstanceMethod : sel ];
}
void otherIMP ( id self , SEL cmd ){
NSLog ( @"%@正在唱歌" ,(( People * ) self ). name );
}
@end
1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>
#import "People.h"
int main ( int argc , const char * argv []) {
@autoreleasepool {
People * p = [[ People alloc ] init ];
p . name = @"张全蛋" ;
[ p sing ];
}
return 0 ;
}
打印结果:
1
2
2016 - 05 - 13 11 : 24 : 16.905 runtime 之 消息动态解析 ( 一 )[ 2819 : 311517 ] 张全蛋正在唱歌
Program ended with exit code : 0
消息动态解析(二)
修改Bird唱歌方法的调用对象
1
2
3
4
5
6
#import <Foundation/Foundation.h>
@interface Bird : NSObject
@property ( nonatomic , copy ) NSString * name ;
-( void ) sing ;
@end
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
#import "Bird.h"
#import "People.h"
@implementation Bird
//第一步,不动态添加方法
+( BOOL ) resolveInstanceMethod: ( SEL ) sel {
return NO ;
}
//第二步,不指定备选对象响应sselector
-( id ) forwardingTargetForSelector: ( SEL ) aSelector {
return nil ;
}
//第三步,返回方法签名
-( NSMethodSignature * ) methodSignatureForSelector: ( SEL ) aSelector {
if ([ NSStringFromSelector ( aSelector ) isEqualToString : @"sing" ]) {
return [ NSMethodSignature signatureWithObjCTypes : "v@:" ];
}
return [ super methodSignatureForSelector : aSelector ];
}
//第四部,修改调用对象
-( void ) forwardInvocation: ( NSInvocation * ) anInvocation {
People * p = [[ People alloc ] init ];
p . name = @"张铁柱" ;
[ anInvocation invokeWithTarget : p ];
}
1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
#import "Bird.h"
#import <objc/runtime.h>
#import <objc/message.h>
int main ( int argc , const char * argv []) {
@autoreleasepool {
Bird * b = [[ Bird alloc ] init ];
b . name = @"小鸟" ;
[ b sing ];
}
return 0 ;
}
打印结果:
1
2
2016 - 05 - 13 11 : 29 : 58.335 runtime 之 消息动态解析 ( 二 )[ 2963 : 322829 ] 张铁柱正在唱歌
Program ended with exit code : 0
消息动态解析(三)
修改Person唱歌方法的实现
1
2
3
4
5
#import <Foundation/Foundation.h>
@interface People : NSObject
-( void ) sing ;
@end
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
#import "People.h"
@implementation People
//不动态添加方法
+( BOOL ) resolveInstanceMethod: ( SEL ) sel {
return NO ;
}
//不指定备选响应对象
-( id ) forwardingTargetForSelector: ( SEL ) aSelector {
return nil ;
}
//返回方法签名
-( NSMethodSignature * ) methodSignatureForSelector: ( SEL ) aSelector {
if ([ NSStringFromSelector ( aSelector ) isEqualToString : @"sing" ]) {
return [ NSMethodSignature signatureWithObjCTypes : "v@:" ];
}
return [ super methodSignatureForSelector : aSelector ];
}
//修改调用方法
-( void ) forwardInvocation: ( NSInvocation * ) anInvocation {
[ anInvocation setSelector : @selector ( dance )];
[ anInvocation invokeWithTarget : self ];
}
//若不实现forwardInvocation,则会调用此方法(可以注释掉forwardInvocation方法来做实验)
-( void ) doesNotRecognizeSelector: ( SEL ) aSelector {
NSLog ( @"消息无法处理:%@" , NSStringFromSelector ( aSelector ));
}
-( void ) dance {
NSLog ( @"跳舞中" );
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
#import "People.h"
#import <objc/runtime.h>
#import <objc/message.h>
int main ( int argc , const char * argv []) {
@autoreleasepool {
People * p = [[ People alloc ] init ];
[ p sing ];
}
return 0 ;
}
打印结果:
1
2
2016 - 05 - 13 11 : 33 : 01.871 runtime 之 消息动态解析 ( 三 )[ 3073 : 329356 ] 跳舞中
Program ended with exit code : 0
以上demo地址
参考文章:
Objective-C Runtime 1小时入门教程
详解Runtime运行时机制