运行时

1.消息转发机制

1).简介

1).在编译期内,如果无法获得某些属性或者对象,它不会报错,因为可以动态插入。这时,就会启动消息转发机制,查找属性或方法
2). CALayer也采用了下面例子相识的方法,使得其成为兼容键值编码的容器类,也就是说能想里面随意添加属性,然后以键值对的形式访问

2).消息转发过程

1)第一阶段
咨询接收者所属的类,看其是否能动态添加方法,以处理当前这个方法或者属性,这叫做动态方法解析<1>
2)第二阶段
继第一阶段执行完,接收者自己无法以动态新增方法的手段来响应包含该选择子的消息,运行起系统就会请求接收者以其他手段来处理与消息相关的方法调用。首先,请接收者看看有没有其它对象能处理这条消息,这叫做备援接收者<2>。若有,则运行期系统会把消息转给那个对象。若无,则启动完整的消息转发机制<3>,运行期系统会把与消息有关的细节全部都封装在 NSInvocation对象中。

运行时_第1张图片
Snip20180104_1.png

<1>动态方法解析

+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveInstanceClass:(SEL)selector

判断这个类的实例方法或类方法能不能处理,返回bool值
相关方法的实现代码已经写好,但只能运行的时候动态插入在类里面,常见于@dynamic属性

<2>备援接收者

- (id)forwardingTargetForSelector:(SEL)selector

若能找到备援对象,则将其返回,否则返回nil

<3>完全的消息转发

- (void)forwardInvocaion:(NSINvocation*)invocation

改变调用目标,使消息在新目标上得以调用即可。常用的实现方式为:在触发消息前,先以某种方法改变消息内容,比如追加另外一个参数,或是改变对象等

#import 
@interface EOCAutoDictonary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) id opaqueObject;
@end

#import "EOCAutoDictonary.h"
#import 

@interface EOCAutoDictonary()
@property (nonatomic, strong) NSMutableDictionary *backingStore;
@end

@implementation EOCAutoDictonary
@dynamic string, number, date, opaqueObject;

-(id)init
{
    if ((self = [super init])) {
        _backingStore = [NSMutableDictionary new];
    }

    return self;
}

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *selectorString = NSStringFromSelector(sel);
    BOOL isGet = false;
    for (NSString *key in @[@"string",@"number",@"date",@"opaqueObject"]) {
        if ([selectorString isEqualToString:key]) {
            isGet = true;
        }
    }
    if ([selectorString hasPrefix:@"set"]) {
        //对象-方法-方法名的指针-类型编码
        class_addMethod(self, sel, (IMP)autoDictonaySetter, "v@:@");
    }else if (isGet || [selectorString hasPrefix:@"get"]){
        class_addMethod(self, sel, (IMP)autoDictonayGetter, "@@:");
    }
    
    return YES;
}

id autoDictonayGetter(id self, SEL _cmd){
    
    EOCAutoDictonary *typeSelf = (EOCAutoDictonary*)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    NSString *key = NSStringFromSelector(_cmd);
    return [backingStore objectForKey:key];
}

void autoDictonaySetter(id self, SEL _cmd, id value){
    
    EOCAutoDictonary *typeSelf = (EOCAutoDictonary*)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    NSString *slectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = [slectorString mutableCopy];
    
    /*
     * example:"setOpaqueObject:"
     */
    
    /*
     * delete ':'
     */
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    /*
     * delete 'set'
     */
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    /*
     * 首字母小写
     */
    NSString *lowercaseFirstChar = [[key substringToIndex:1] lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    
    if (value) {
        [backingStore setObject:value forKey:key];
    }else{
        [backingStore removeObjectForKey:key];
    }
}

@end

2.消息传递机制

要调用得函数直到运行期才能确定,证明了OC是一门真正得动态语言:

void doThing(int type){
    void (*func)();
    if(type == 0 ){
        func = printA;
    }else{
        func = printB;
    }
    func();
}

OC底层调用方法得函数得原型

void objc_msgSend(id self, SEL sel, ....arg)

在运行期执行doThing,会翻译为objc_msgSend函数,objc_msgSend向self传递消息执行sel,如果self没有该方法,就会沿着继承体系继续向上查找,等查找合适得方法之后再跳转。如果最终招不到,就会消息转发。
调用一次objc_msgSend是需要很多步骤费时得,所以它会将匹配结果缓存在快速映射表里面,每个累都有一块缓存。当然这种缓存还是不如静态绑定得函数调用操作快,不过差距不大

3.方法调配技术

运行时_第2张图片
#import 
    Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
    Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));
    method_exchangeImplementations(originalMethod, swappedMethod);

@interface NSString (EOCMyAdditions)
-(NSString *)eoc_myLowercaseString;
@end

@implementation NSString (EOCMyAdditions)
-(NSString *)eoc_myLowercaseString{
    
    NSString *lowercase = [self eoc_myLowercaseString];
    NSLog(@"%@ == %@",self,lowercase);
    return lowercase;
}
@end

你可能感兴趣的:(运行时)