Runtime 全方位装逼指南

Runtime是啊?见称知意,其定义才就是是“因为 Objective-C
是一律山头动态语言,所以它需要一个运转时系统……这便是 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 是一个 Class
类型的成员变量,那么 Class 又是啊吧?

4、Class

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

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 也生一个 isa 指针,指于那所属的元类(meta)。

  • ·super_class:指于该超类。

  • ·name:是类名。

  • ·version:是看似的版本信息。

  • ·info:是近似的详情。

  • ·instance_size:是此类的实例对象的大小。

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

  • ·methodLists:指向该类的实例方法列表,它用艺术选择器和方式实现地方联系起来。methodLists
    是依赖于 ·objc_method_list 指针的指针,也就是说可以动态修改
    *methodLists 的值来补偿加成员方法,这为是 Category
    实现的原理,同样解释了 Category 不能够加加属性的原由。

  • ·cache:Runtime 系统会将让调用的措施存到 cache
    中(理论及说话一个法要吃调用,那么她发或以后还见面给调用),下次寻觅的时刻效率又强。

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

说到这边小乱了,我们来捋一下,当我们调用一个方时,其运行过程大约如下:

率先,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
五单道中甄选一个来调用。如果消息是传递让超类,那么会调用
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。第二个分子是 (id)class_getSuperclass(objc_getClass(“Son”)).

  • 其次步是错开 Father 这个看似里去搜寻 – (Class)class,没有,然后去 NSObject
    类去摸,找到了。最后内部是下
    objc_msgSend(objc_super->receiver, @selector(class))
    去调用,此时早已与 [self class] 调用同一了,所以个别单出口结果尚且是
    Son。

7、对象关联

靶关联允许开发者对曾经在的近乎以 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 添加一个监听单击事件之 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");
};

完的目标关系代码点这里。

8、自动归档

博主于上学 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 有100只属性之话语,就待写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

总体的全自动归档代码在这里。

9、字典与模型互转

太初步博主是如此用字典给 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

一体化代码在这里。

10、动态方法分析

面前我们留下了平等碰东西从来不说,那就算是使某对象调用了无存的法时见面怎样,一般情况下程序会crash,错误信息类似下面这样:

unrecognized selector sent to instance 0x7fd0a141afd0

而是以次crash之前,Runtime
会给咱们动态方法分析的空子,消息发送的手续大致如下:

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

2.检测是 target 是休是 nil 对象。ObjC 的性状是容对一个 nil
对象执行外一个道无见面 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