Runtime小笔记(二)

前言

上篇主要讲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运行时机制

Comments