Running Time

静态绑定:编译的时候就已经确定了调用的方法。函数地址其实是硬编码在指令中。

动态绑定:运行期才确定要调用的方法。

1.消息调用

调用过程过程

Running Time_第1张图片
Alt Image Text

(1)发送sendMessage:params消息给object对象;

(2)OC消息通过编译器转换成了C语言函数;

消息传递机制中的核心函数,叫做objc_msgSend,其“原型”如下:

void object_msgSend(id self, SEL cmd, ...)

OC中给对象发消息可以这样来写:

id returnValue = [someObject messageName: parameter];

someObject是接收者; messageName是选择子(方法名),选择子+参数合起来称为消息

编译器会将其转化为C语言的如下函数:

id returnValue = objc_msgSend(someObject, @selector(message:), params);

(3)在接收者someObject所属的类中,查询选择子selector(message:);

(4)如果没有找到,就去super class中查找;

(5)如果最终还是没有,就启动消息转发机制,将消息转发给别的对象。

(6)objc_msgSend会将匹配结果缓存在“快速映射表中”(fast map),每个类都有一个表,方便再次查找时快速查找。

2.消息转发机制

原理

(1)如果类想了解某条消息,它需要实现相对应的方法才行。

(2)由于编译期间不会进行动态绑定方法,所以如果没有实现对应方法的话,是不会抱错的,因为可以运行期间添加方法。

(3)如果运行期间也没有添加相应的方法的话,会启动“消息转发”直接,来让其他对象处理这条消息。

(4)但是如果其他对象也不能处理的话,程序就会抱错:

unrecognized selector sent to instance XXX

因为消息的接收者(instance)并没有实现对应的方法,不能理解这条消息(selector)。

消息转发过程(分为两个阶段)

Running Time_第2张图片
Alt Image Text
第一阶段:动态方法解析

(1)对象收到无法解析的message后,首先调用所属类的下列方法:

+ (BOOL)resolveInstanceMethod:(SEL)sel
 
+ (BOOL)resolveClassMethod:(SEL)sel

该方法会决定,是否新增一个实例方法来处理sel这个选择子;

前提是:该方法的视线代码已经写好了,只要在运行时动态插入到类里面即可。

(2)备援接收者

调用如下方法

- (id)forwardingTargetForSelector:(SEL)aSelector

若能当前接收者能找到备援对象,则将其返回;若找不到,就返回nil。

一个对象内部,可能有其他对象(实例变量),可以通过上面的方法,将内部能处理aSelector的对象返回,这样就好想该对象亲自处理了aSelector一样。

我们无法操作经由这一步所转发的消息,如果想要在发送给备援接收者之前先修改消息内容,那就得通过完整的消息转发机制来做了

第二阶段:完整的消息转发机制

(1)创建 NSInvocation对象:该对象封装了selector、target、pramaters

(2)调用方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

该方法多用于改变消息内容,比如追加pramaters,改变selector.

(3)如果本类不能处理,则应调用super class中的同名方法,直至NSObject

(4)如果调用了NSObject类的方法,就会继续调用

doesNotRecognizeSelector:

然后抛出异常,表明selector最终未能得到处理

(5)步骤越往后,处理消息的代价就越大。尽量在第一步就处理完,然后将该方法缓存起来,以便之后快速处理。

下面是一个代码示例

比如我们想给UIButton添加属性,比如name或order,用来描述button。

(1)首先定义一个类继承自UIButton

@interface XZButton : UIButton

@property (nonatomic ,strong) NSString *name;

@property (nonatomic ,strong) NSNumber *order;

@end

(2)在XZButton.m文件中导入下面的头文件,这样才能使用running time的函数。

 #import 

(3)注意使用 @dynamic,告诉编译器不用自动生成存取方法。

@dynamic name,order;

(4)声明一个NSMutableDictionary属性,用来存储要添加的属性,这里要注意了,既然是使用字典存储,那么如果你想添加NSInteger属性的话是会抱错的,因为字典不允许添加整型做为value(?)

@property (nonatomic ,strong) NSMutableDictionary *backingStore;

(5)init 方法中初始化字典

- (id)init{

if (self = [super init]) {

    _backingStore = [NSMutableDictionary dictionary];
}

return self;
}

(6)注意这步是重点,调用了resolveInstanceMethod方法,也就是上面说的,询问receiver能否动态添加对应的方法来处理selector

+ (BOOL)resolveInstanceMethod:(SEL)sel{

NSString *selectorString = NSStringFromSelector(sel);

if ([selectorString hasPrefix:@"set"]) {

    class_addMethod(self,sel,(IMP)xZAutoDictionarySetter,"v@:@");
}else{

    class_addMethod(self, sel, (IMP)xZAutoDictionaryGetter, "@ @:");
}

return YES;
}

(7)上面通过sel来判断存取方法,然后在下面实现对应的存取的方法


//set
void xZAutoDictionarySetter(id self,SEL _cmd,id value){
    
XZButton *typeSelf = (XZButton *)self;
    
NSMutableDictionary *backingStore = typeSelf.backingStore;
    
NSString *selectorString = NSStringFromSelector(_cmd);
    
NSMutableString *key = [selectorString mutableCopy];
    
//remove :
[key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    
//remove set
[key deleteCharactersInRange:NSMakeRange(0, 3)];
    
//首字母小写
NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    
if (value) {
    
    [backingStore setValue:value forKey:key];
}else{
    
    [backingStore removeObjectForKey:key];
}
}
//get
id xZAutoDictionaryGetter(id self, SEL _cmd){

XZButton *typeSelf = (XZButton *)self;

NSMutableDictionary *backingStore = typeSelf.backingStore;
    
NSString *key = NSStringFromSelector(_cmd);
    
return [backingStore valueForKey:key];
}

(8)注意,这里添加的get和set方法名不能重复,也就是不同的类中不能使用相同的方法名。否则会报错( duplicate symbol)。这里我也没太搞懂,不知道是不是因为是C函数,所以不能重名,一般的OC方法,在不同的类中是可以重名的,有明白的高人还请讲解一下

(9)使用

XZButton *button = [[XZButton alloc] init];

button.name = @"xuz";
button.order = [NSNumber numberWithInteger:14];

NSLog(@"button's name is %@ order is %ld",button.name,(long)[button.order integerValue]);

3.消息处理

(1)方法调配:处理selector的方法可以在运行期改变。既不需要源码,也不用通过子类继承来复写方法改变这个类本身的功能。并且新功能对本类的所有实例生效,而不是仅限于复写了相关方法的那些子类实例。

(2)类的方法列表会把selector映射到相关的实现方法之上,可以通过这张表来找到应该调用的方法。这些方法均以函数指针的形式来表示,这种指针叫做IMP,原型如下:

id (*IMP) (id,SEL,....)

(3)映射表如下

(4)OC运行期间可以向表中添加新的selector(并实现对应的方法);也可以更改selector映射的IMP指针指向的方法;还可以交换两个selector映射的指针。

(5)举例子,如何交换两个方法的实现,这里创建了Nsstring的分类ExchangeString,并实现了一个方法,用来和lowercaseString交换实现的方法

#import "NSString+ExchangeString.h"
#import 

@implementation NSString (ExchangeString)

- (NSString *)eoc_myLowercaseString{

    //获取selector对应的IMP, 注意这里是class_getInstanceMethod(实例方法),我一开始写的是class_getClassMethod(类方法),没有交换成功,所以一直循环调用
    Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));

    Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));

    //交换selector对应的IMP
    method_exchangeImplementations(originalMethod, swappedMethod);


    NSString *lowerCase = [self eoc_myLowercaseString];

    NSLog(@"%@ - > %@",self,lowerCase);

    return lowerCase;
}

使用:

NSString *testString = @"This is the testString";
NSString *resultString = [testString eoc_myLowercaseString];

结果

对象

(1)OC中对象并非在编译期就绑定好了,而是在运行期查找的。

(2)正常情况下,应该指明receiver的类型,如果想它发送无法解读的message的话,会报错。

(3)特殊情况id类型的对象,id 指代任意的OC对象类型。编译器假定它能响应任何message

(4)其实每个OC对象都是指向某块内存地址的指针。比如:

NSString *pointerVariable = @"some thing";

这里的pointerVariable可以理解为指向NSString的实例变量some thing的指针

id genericTypedString = @"Some thing";

因为这里id类型,本身就已经是指针了,所以不用加*。

所有OC对象都是分配在堆上的,如果不使用*,那么会把对象分配在栈上,编译器会报错
栈和堆的区别:< >。

(5)OC对象所用的结构体定义在运行期的程序库头文件中,比如id类型:

typedef struct objc_object *id;

struct objc_object{
    Class isa;
};

首个结构体的首个成员是Class类的变量,isa指针指向对象所属的类

Class对象也定义在运行期程序库的头文件中:

typedef struct objc_class *Class;

struct objc_class {
    Class isa;
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
};

isa指针:指向类所属的元类(meta),用来表述类对象本身所具备的的元数据,类方法即存在于元类中。

class指针:指向本类的超类。

name:类名。

version:类的版本号。

info:类的详情。

instance_size:该类实例对象的大小。

ivars:指向该类的成员变量的列表(类的属性列表)。

methodLists:指向该类的实例方法列表,它将方法选择器和方法实现地址联系起来。methodLists 是指向 ·objc_method_list 指针的指针,也就是说可以动态修改 *methodLists 的值来添加成员方法,这也是 Category 实现的原理,同样解释了 Category 不能添加属性的原因(为什么不能添加属性,因为ivars指向的是属性列表,修改ivars,并不能添加属性?只是改变了指向地址?)。

cache:Runtime 系统会把被调用的方法存到 cache 中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。

protocols:指向该类的协议列表。

类的继承体系图如下:


Running Time_第3张图片

上图实线是 super_class 指针,虚线是 isa 指针。根元类的超类是NSObject,而 isa 指向了自己(元类:即类对象所属的类型,也就是说所有的元类,它的类型都是NSObject?)。NSObject 的超类为 nil,也就是它没有超类。

(6)类型查询

isMemberOfClass:能够判断对象是否为某个特定类的实例。

isKindOfClass:能判断对象是否为某个类或其派生类的实例。

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict isMemberOfClass:[NSMutableDictionary class]];//yes
[dict isMemberOfClass:[NSDictionary class]];//no
[dict isKindOfClass:[NSMutableDictionary class]];//yes
[dict isKindOfClass:[NSDictionary class]];//yes

使用isa获得实例所属的类,然后通过super_class指针在继承体系中查询。因为对象是动态的,所以必须通过类型查询才能了解对象的真实类型。

从collection中获取的对象时,通常会使用类型查询,这些对象一般都不是强类型的,通常是id,所以如果不判断一下的话,有可能会报错。

如果是代理模式的话,那么用class方法查出来的类型和isKindOfClass查询处的结果会不一样,前者返回的是发起代理的对象,后者返回的是接受代理的对象。要尽量使用类型查询,这样会得到准确的对象类型。

你可能感兴趣的:(Running Time)