Runtime 全方位装逼指南

Runtime是什么?见名知意,其定义无非就是“因为 Objective-C
是一门动态语言,所以它需求3个运转时系统……那就是 Runtime
系统”云云。对博主那种菜鸟而言,Runtime
在实质上开发中,其实就是一组C语言的函数。胡适之说:“多钻研些难题,少谈些主义”,云山雾罩的定义听多了三番五次不难头晕,接下去大家直接从代码出手学习
Runtime。

1、由objc_msgSend说开去

Objective-C
中的方法调用,不是简约的法子调用,而是发送消息,约等于说,其实
[receiver message] 会被编译器转化为: objc_msgSend(receiver,
selector),何以声明?新建贰个类 MyClass,其.m文件如下:

1
2
3
4
5
6
7
8
9
10
11
#import "MyClass.h"
@implementation MyClass
-(instancetype)init{
    if (self = [super init]) {
        [self showUserName];
    }
    return self;
}
-(void)showUserName{
    NSLog(@"Dave Ping");
}

动用 clang 重写命令:

1
$ clang -rewrite-objc MyClass.m

接下来在同一目录下会多出三个 MyClass.cpp 文件,双击打开,可以见见 init
方法已经被编译器转化为下边那样:

1
2
3
4
5
6
static instancetype _I_MyClass_init(MyClass * self, SEL _cmd) {
    if (self = ((MyClass *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MyClass"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"));
    }
    return self;
}

我们要找的就是它:

1
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"))

objc_msgSend 函数被定义在 objc/message.h 目录下,其函数原型是酱紫滴:

1
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )

该函数有五个参数,一个 id 类型,贰个 SEL 类型。

2、SEL

SEL 被定义在 objc/objc.h 目录下:

1
typedef struct objc_selector *SEL;

实则它就是个映射到艺术的C字符串,你能够用 Objective-C 编译器命令
@selector() 或然 Runtime 系统的 sel_registerName 函数来获取七个 SEL
类型的方法接纳器。

3、id

与 SEL 一样,id 也被定义在 objc/objc.h 目录下:

1
typedef struct objc_object *id;

id 是一个结构体指针类型,它可以本着 Objective-C
中的任何对象。objc_object 结构体定义如下:

1
struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};

咱俩平时所说的靶子,就长那些样子,这几个结构体唯有一个分子变量
isa,对象足以经过 isa 指针找到其所属的类。isa 是3个 Class
类型的成员变量,那么 Class 又是怎么着吗?

4、Class

Class 也是3个布局体指针类型:

1
typedef struct objc_class *Class;

objc_class 结构体是酱紫滴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class   OBJC2_UNAVAILABLE;
    const char *name   OBJC2_UNAVAILABLE;
    long version     OBJC2_UNAVAILABLE;
    long info     OBJC2_UNAVAILABLE;
    long instance_size   OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars  OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists  OBJC2_UNAVAILABLE;
    struct objc_cache *cache    OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols   OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

小编们普通说的类就长这规范:

  • ·Class 也有2个 isa 指针,指向其所属的元类(meta)。

  • ·super_class:指向其超类。

  • ·name:是类名。

  • ·version:是类的版本音信。

  • ·info:是类的详情。

  • ·instance_size:是此类的实例对象的轻重。

  • ·ivars:指向该类的积极分子变量列表。

  • ·methodLists:指向该类的实例方法列表,它将艺术选用器和方法完结地点联系起来。methodLists
    是指向 ·objc_method_list 指针的指针,约等于说可以动态修改
    *methodLists 的值来添加成员方法,那也是 Category
    落成的原理,同样解释了 Category 不可以添加属性的来头。

  • ·cache:Runtime 系统会把被调用的点子存到 cache
    中(理论上讲壹个措施借使被调用,那么它有或然将来还会被调用),下次摸索的时候效用更高。

  • ·protocols:指向该类的商事列表。

说到此地有点乱了,我们来捋一下,当大家调用3个主意时,其运营进程大约如下:

首先,Runtime 系统会把措施调用转化为音讯发送,即
objc_msgSend,并且把措施的调用者,和格局选取器,当做参数传递过去.

此时,方法的调用者会经过 isa 指针来找到其所属的类,然后在 cache 或者methodLists 中搜索该措施,找拿到就跳到相应的不二法门去履行。

只要在类中从未找到该方法,则经过 super_class
往上拔尖超类查找(如若一向找到 NSObject
都并未找到该方法的话,那种场所,大家松手前面新闻转发的时候再说)。

眼下大家说 methodLists
指向该类的实例方法列表,实例方法即-方法,那么类措施(+方法)存储在何处呢?类措施被储存在元类中,Class
通过 isa 指针即可找到其所属的元类。

图片 1

上图实线是 super_class 指针,虚线是 isa
指针。根元类的超类是NSObject,而 isa 指向了协调。NSObject 的超类为
nil,相当于它并未超类。

5、使用objc_msgSend

眼下大家使用 clang 重写命令,看到 Runtime
是哪些将艺术调用转化为音讯发送的。大家也可以东施效颦,来读书使用一下
objc_msgSend。新建二个类 TestClass,添加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)showAge{
    NSLog(@"24");
}
-(void)showName:(NSString *)aName{
    NSLog(@"name is %@",aName);
}
-(void)showSizeWithWidth:(float)aWidth andHeight:(float)aHeight{
    NSLog(@"size is %.2f * %.2f",aWidth, aHeight);
}
-(float)getHeight{
    return 187.5f;
}
-(NSString *)getInfo{
    return @"Hi, my name is Dave Ping, I'm twenty-four years old in the year, I like apple, nice to meet you.";
}

咱俩得以像上面那样,使用 objc_msgSend 依次调用那么些方式:

1
2
3
4
5
6
7
8
TestClass *objct = [[TestClass alloc] init];
((void (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("showAge"));
((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("showName:"), @"Dave Ping");
((void (*) (id, SEL, float, float)) objc_msgSend) (objct, sel_registerName("showSizeWithWidth:andHeight:"), 110.5f, 200.0f);
float f = ((float (*) (id, SEL)) objc_msgSend_fpret) (objct, sel_registerName("getHeight"));
NSLog(@"height is %.2f",f);
NSString *info = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getInfo"));
NSLog(@"%@",info);

唯恐你已经注意到,objc_msgSend 在利用时都被胁持转换了一下,那是因为
objc_msgSend
函数可以hold住各个不一样的重回值以及三个参数,但暗许情形下是没有参数和重回值的。假诺大家把调用
showAge 方法改成那样:

1
objc_msgSend(objct, sel_registerName("showAge"));

Xcode 就会报错:

1
Too many arguments to function call, expected 0, have 2.

完整的 objc_msgSend
使用代码在这里

6、objc_msgSendSuper

编译器会基于情状在
objc_msgSend,objc_msgSend_stret,objc_msgSendSuper,objc_msgSendSuper_stret
或 objc_msgSend_fpret
多个办法中选拔3个来调用。如若音讯是传递给超类,那么会调用
objc_msgSendSuper 方法,假若消息重临值是数据结构,就会调用
objc_msgSendSuper_stret 方法,假使再次回到值是浮点数,则调用
objc_msgSend_fpret 方法。

此地大家第三说一下 objc_msgSendSuper,objc_msgSendSuper 函数原型如下:

1
OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

当我们调用 [super selector] 时,Runtime 会调用 objc_msgSendSuper
方法,objc_msgSendSuper 方法有七个参数,super 和 op,Runtime 会把
selector 方法采取器赋值给 op。而 super 是三个 objc_super
结构体指针,objc_super 结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;
    /// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

Runtime 会创设三个 objc_spuer
结构体变量,将其地方作为参数(super)传递给 objc_msgSendSuper,并且将
self 赋值给 receiver:super—>receiver=self。

举个栗子,问上边的代码输出什么:

1
2
3
4
5
6
7
8
9
10
11
12
@implementation Son : Father
- (id)init
{
    self = [super init];
    if (self)
    {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

答案是一切输出 Son。

运用 clang 重写命令,发现上述代码被转化为:

1
2
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));

当调用 [super class] 时,会转换到 objc_msgSendSuper 函数:

  • 首先步先构造 objc_super 结构体,结构体第②个分子就是
    self。第1个成员是 (id)class_getSuperclass(objc_getClass(“Son”)).

  • 其次步是去 Father 这一个类里去找 – (Class)class,没有,然后去 NSObject
    类去找,找到了。最后内部是接纳objc_msgSend(objc_super->receiver, @selector(class))
    去调用,此时一度和 [self class] 调用平等了,所以七个出口结果都是Son。

⑦ 、对象关联

对象关联允许开发者对已经存在的类在 Category 中添加自定义的属性:

1
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

·object 是源对象

·value 是被提到的靶子

·key 是关联的键,objc_getAssociatedObject 方法通过差其他 key
即可取出对应的被波及对象

·policy
是三个枚举值,表示关联对象的行事,从命名就能来看各类枚举值的意义:

1
2
3
4
5
6
7
8
9
10
11
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    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.
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

要取出被提到的对象使用 objc_getAssociatedObject
方法即可,要删减一个被提到的对象,使用 objc_setAssociatedObject
方法将相应的 key 设置成 nil 即可:

1
objc_setAssociatedObject(self, associatedKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);

objc_removeAssociatedObjects 方法将会移除源对象中具有的涉及对象.

举个栗子,假使大家要给 UIButton 添加1个监听单击事件的 block 属性,新建
UIButton 的 Category,其.m文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "UIButton+ClickBlock.h"
#import static const void *associatedKey = "associatedKey";
@implementation UIButton (ClickBlock)
//Category中的属性,只会生成setter和getter方法,不会生成成员变量
-(void)setClick:(clickBlock)click{
    objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
    if (click) {
        [self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
    }
}
-(clickBlock)click{
    return objc_getAssociatedObject(self, associatedKey);
}
-(void)buttonClick{
    if (self.click) {
        self.click();
    }
}
@end

下一场在代码中,就可以利用 UIButton 的属性来监听单击事件了:

1
2
3
4
5
6
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.view.bounds;
[self.view addSubview:button];
button.click = ^{
    NSLog(@"buttonClicked");
};

全部的目标关联代码点这里

⑧ 、自动归档

博主在念书 Runtime 以前,归档的时候是酱紫写的:

1
2
3
4
5
6
7
8
9
10
11
- (void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeObject:self.ID forKey:@"ID"];
}
- (id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        self.ID = [aDecoder decodeObjectForKey:@"ID"];
        self.name = [aDecoder decodeObjectForKey:@"name"];
    }
    return self;
}

这就是说难点来了,假如当前 Model 有9柒个个性的话,就需要写100行那种代码:

1
[aCoder encodeObject:self.name forKey:@"name"];

思维都头疼,通过 Runtime 大家就可以轻松消除这么些标题:

1.使用 class_copyIvarList 方法得到当前 Model 的装有成员变量.

2.使用 ivar_getName 方法取得成员变量的名称.

3.透过 KVC 来读取 Model 的属性值(encodeWithCoder:),以及给 Model
的天性赋值(initWithCoder:).

举个栗子,新建一个 Model 类,其.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
#import "TestModel.h"
#import #import @implementation TestModel
- (void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int outCount = 0;
    Ivar *vars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar var = vars[i];
        const char *name = ivar_getName(var);
        NSString *key = [NSString stringWithUTF8String:name];
        // 注意kvc的特性是,如果能找到key这个属性的setter方法,则调用setter方法
        // 如果找不到setter方法,则查找成员变量key或者成员变量_key,并且为其赋值
        // 所以这里不需要再另外处理成员变量名称的“_”前缀
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        unsigned int outCount = 0;
        Ivar *vars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            Ivar var = vars[i];
            const char *name = ivar_getName(var);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
    }
    return self;
}
@end

全体的自行归档代码在这里

⑨ 、字典与模型互转

最开头博主是这么用字典给 Model 赋值的:

1
2
3
4
5
6
7
-(instancetype)initWithDictionary:(NSDictionary *)dict{
    if (self = [super init]) {
        self.age = dict[@"age"];
        self.name = dict[@"name"];
    }
    return self;
}

同理可得,蒙受的难题跟归档时候同样(后来应用MJExtension),那里大家有个别来学学一下里边规律,字典转模型的时候:

1.依据字典的 key 生成 setter 方法

2.使用 objc_msgSend 调用 setter 方法为 Model 的品质赋值(恐怕 KVC)

模型转字典的时候:

1.调用 class_copyPropertyList 方法拿到当前 Model 的持有属性

2.调用 property_getName 得到属性名称

3.依据属性名称变更 getter 方法

4.使用 objc_msgSend 调用 getter 方法拿到属性值(只怕 KVC)

代码如下:

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
#import "NSObject+KeyValues.h"
#import #import @implementation NSObject (KeyValues)
//字典转模型
+(id)objectWithKeyValues:(NSDictionary *)aDictionary{
    id objc = [[self alloc] init];
    for (NSString *key in aDictionary.allKeys) {
        id value = aDictionary[key];
        /*判断当前属性是不是Model*/
        objc_property_t property = class_getProperty(self, key.UTF8String);
        unsigned int outCount = 0;
        objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);
        objc_property_attribute_t attribute = attributeList[0];
        NSString *typeString = [NSString stringWithUTF8String:attribute.value];
        if ([typeString isEqualToString:@"@\"TestModel\""]) {
            value = [self objectWithKeyValues:value];
        }
        /**********************/
        //生成setter方法,并用objc_msgSend调用
        NSString *methodName = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];
        SEL setter = sel_registerName(methodName.UTF8String);
        if ([objc respondsToSelector:setter]) {
            ((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
        }
    }
    return objc;
}
//模型转字典
-(NSDictionary *)keyValuesWithObject{
    unsigned int outCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    for (int i = 0; i < outCount; i ++) {
        objc_property_t property = propertyList[i];
        //生成getter方法,并用objc_msgSend调用
        const char *propertyName = property_getName(property);
        SEL getter = sel_registerName(propertyName);
        if ([self respondsToSelector:getter]) {
            id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);
            /*判断当前属性是不是Model*/
            if ([value isKindOfClass:[self class]] && value) {
                value = [value keyValuesWithObject];
            }
            /**********************/
            if (value) {
                NSString *key = [NSString stringWithUTF8String:propertyName];
                [dict setObject:value forKey:key];
            }
        }
    }
    return dict;
}
@end

一体化代码在这里

⑩ 、动态方法分析

前方我们留下了一点东西没说,那就是只要有些对象调用了不存在的措施时会怎么着,一般意况下程序会crash,错误音信类似上边那样:

unrecognized selector sent to instance 0x7fd0a141afd0

而是在先后crash从前,Runtime
会给大家动态方法分析的机遇,音讯发送的手续大约如下:

1.检测那一个 selector 是否要不经意的。比如 Mac OS X
开发,有了废品回收就不理会 retain,release 那些函数了

2.检测这几个 target 是还是不是 nil 对象。ObjC 的表征是允许对三个 nil
对象执行别的3个办法不会 Crash,因为会被忽视掉

3.比方上边几个都过了,那就发轫查找这几个类的 IMP,先从 cache
里面找,完了找拿到就跳到相应的函数去实践

比方 cache 找不到就找一下办法分发布

4.假诺分宣布找不到就到超类的分发表去找,一向找,直到找到NSObject类截止

即便还找不到即将起来进入音信转载了,音信转载的大约进程如图:

图片 2

1.跻身 resolveInstanceMethod:
方法,钦命是或不是动态增加方法。若重返NO,则跻身下一步,若重回YES,则透过
class_addMethod 函数动态地丰富方法,音讯得到处理,此流程完结。

2.resolveInstanceMethod: 方法再次来到 NO 时,就会进入
forwardingTargetForSelector: 方法,那是 Runtime
给我们的第一次机遇,用于钦命哪个目的响应这个selector。重回nil,进入下一步,再次回到有些对象,则会调用该对象的法子。

3.若 forwardingTargetForSelector: 重回的是nil,则大家率先要透过
methodSignatureForSelector:
来内定方法签名,再次来到nil,表示不处理,若再次来到方法签名,则会进入下一步。

4.当第 methodSignatureForSelector: 方法重临方法签名后,就会调用
forwardInvocation: 方法,大家得以透过 anInvocation
对象做过多处理,比如修改落成情势,修改响应对象等。

 

参考链接:

http://www.cocoachina.com/ios/20160523/16386.html

 http://www.jianshu.com/p/ed65518ec8db

 http://www.jianshu.com/p/927c8384855a