Runtime 全方位装逼指南。Runtime 全方位装逼指南,runtime指南。

Runtime是什么?见称知意,其定义才就是是“因为 Objective-C
是相同门动态语言,所以它们用一个周转时系统……这便是 Runtime
系统”云云。对博主这种菜鸟而言,Runtime
在事实上付出被,其实就算是一样组C语言的函数。胡适说:“多研究把问题,少语数主义”,云山雾罩的概念听多了连容易头晕,接下我们一直打代码入手学习
Runtime。

Runtime 全方位装逼指南,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 指针即可找到该所属的元类。

必威app 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类为止

苟还摸索不顶将起进入信息转发了,消息转发的大概过程要图:

必威app 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

http://www.bkjia.com/IOSjc/1232588.htmlwww.bkjia.comtruehttp://www.bkjia.com/IOSjc/1232588.htmlTechArticleRuntime 全方位装逼指南,runtime指南
Runtime是啊?见称知意,其定义才就是是“因为 Objective-C
是平等家动态语言,所以她需一个运行时系统…

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 指针即可找到其所属的元类。

必威app 3

达图实线是 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

完整的机动归档代码必威app在这里。

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类为止

若还找不顶将开进入信息转发了,消息转发的约过程要图:

必威app 4

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

相关文章