美文网首页OC
runtime 基础篇(三)

runtime 基础篇(三)

作者: 东方_未明 | 来源:发表于2016-06-15 10:30 被阅读71次

    每次都想自己写, 但是时间太少, 最重要的是每次想写, 都发现很多写的太好的文章, 找一个写的深入浅出的文章转载一下
    转载地址: http://www.cnblogs.com/biosli/p/NSObject_inherit_2.html

    消息动态解析

    • Objective-C是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IML)组成的。
    • 系统根据@selector(XXX)查找方法, 如果找不到, 系统会给程序几次机会来仍然使程序正常运行, 如果这几次机会都浪费了, 才会抛出异常

    下图是objc_msgSend调用时,查找SEL的IML的过程:


    运行时查找函数的流程.png

    1 .resolveInstanceMethod函数

    + (BOOL)resolveInstanceMethod:(SEL)name
    

    这个函数在运行时(runtime),没有找到SEL的IML时就会执行。这个函数是给类利用class_addMethod添加函数的机会。
    根据文档,如果实现了添加函数代码则返回YES,未实现返回NO。
    实现的例子:

    //全局函数
    void dynamicMethodIMP(id self, SEL _cmd)
    {
        // implementation ....
    }
    
    @implementation MyTestObject
    //…
    //类函数
    + (BOOL) resolveInstanceMethod:(SEL)aSEL
    {
        if (aSEL == @selector(resolveThisMethodDynamically))
        {
              class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
              return YES;
        }
        return [super resolveInstanceMethod:aSel];
    }
    //…
    @end
    

    注意事项:
    根据Demo实验,这个函数返回的BOOL值系统实现的objc_msgSend函数并没有参考,无论返回什么系统都会尝试再次用SEL找IML,如果找到函数实现则执行函数。如果找不到继续其他查找流程。

    2 .forwardingTargetForSelector:

    - (id)forwardingTargetForSelector:(SEL)aSelector
    

    流程到了这里,系统给了个将这个SEL转给其他对象的机会。
    返回参数是一个对象,如果这个对象非nil、非self的话,系统会将运行的消息转发给这个对象执行。否则,继续查找其他流程。
    实现示例:

    //转发目标类
    @interface NoneClass : NSObject
    @end
    
    @implementation NoneClass
    +(void)load
    {
        NSLog(@"NoneClass _cmd: %@", NSStringFromSelector(_cmd));
    }
    
    - (void) noneClassMethod
    {
        NSLog(@"_cmd: %@", NSStringFromSelector(_cmd));
    }
    @end
    
    @implementation MyTestObject
    //…
    //将消息转出某对象
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        NSLog(@"MyTestObject _cmd: %@", NSStringFromSelector(_cmd));
    
        NoneClass *none = [[NoneClass alloc] init];
        if ([none respondsToSelector: aSelector]) {
            return none;
        }
        
        return [super forwardingTargetForSelector: aSelector];
    }
    //…
    @end
    

    当执行MyTestObject对象执行[myTestObject nonClassMethod]函数时,消息会抛到NoneClass对象中执行。

    3 . methodSignatureForSelector:

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    

    这个函数和后面的forwardInvocation:是最后一个寻找IML的机会。这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行。

    4 . forwardInvocation:

    - (void)forwardInvocation:(NSInvocation *)anInvocation
    

    真正执行从methodSignatureForSelector:返回的NSMethodSignature。在这个函数里可以将NSInvocation多次转发到多个对象中,这也是这种方式灵活的地方。(forwardingTargetForSelector只能以Selector的形式转向一个对象)
    下面这个示例代码,诠释了这种实现优势:

    #import <Foundation/Foundation.h>
     
    @interface Book : NSObject
    {
        NSMutableDictionary *data;
    }
    //声明了两个setter/getter
    @property (retain) NSString *title; 
    @property (retain) NSString *author;
    @end
     
    @implementation Book
    @dynamic title, author; //不自动生成实现
     
    - (id)init
    {
        if ((self = [super init])) {
            data = [[NSMutableDictionary alloc] init];
            [data setObject:@"Tom Sawyer" forKey:@"title"];
            [data setObject:@"Mark Twain" forKey:@"author"];
        }
        return self;
    }
     
    - (void)dealloc
    {
        [data release];
        [super dealloc];
    }
     
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
    {
        NSString *sel = NSStringFromSelector(selector);
        if ([sel rangeOfString:@"set"].location == 0) {
            //动态造一个 setter函数
            return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
        } else {
            //动态造一个 getter函数
            return [NSMethodSignature signatureWithObjCTypes:"@@:"];
        }
    }
     
    - (void)forwardInvocation:(NSInvocation *)invocation
    {
        //拿到函数名
        NSString *key = NSStringFromSelector([invocation selector]);
        if ([key rangeOfString:@"set"].location == 0) {
            //setter函数形如 setXXX: 拆掉 set和冒号 
            key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
            NSString *obj;
            //从参数列表中找到值
            [invocation getArgument:&obj atIndex:2];
            [data setObject:obj forKey:key];
        } else {
            //getter函数就相对简单了,直接把函数名做 key就好了。
            NSString *obj = [data objectForKey:key];
            [invocation setReturnValue:&obj];
        }
    }
     
    @end
    

    5 . doesNotRecognizeSelector:

    - (void)doesNotRecognizeSelector:(SEL)aSelector
    

    作为找不到函数实现的最后一步,NSObject实现这个函数只有一个功能,就是抛出异常。
    虽然理论上可以重载这个函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出“一定不能让这个函数就这么结束掉,必须抛出异常”。

    使用场景

    在一个函数找不到时,Objective-C提供了三种方式去补救:

    1. 调用resolveInstanceMethod给个机会让类添加这个实现这个函数
    2. 调用forwardingTargetForSelector让别的对象去执行这个函数
    3. 调用methodSignatureForSelector(函数符号制造器)和forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。
      如果都不中,调用doesNotRecognizeSelector抛出异常。

    6 respondsToSelector

    + (BOOL)respondsToSelector:(SEL)aSelector
    
    

    这个函数大家再熟悉不过了,用来检查对象是否实现了某函数。

    此函数通常是不需要重载的,但是在动态实现了查找过程后,需要重载此函数让对外接口查找动态实现函数的时候返回YES,保证对外接口的行为统一。
    示例代码(接forwardInvocation的例子):

    @implementation Book
    //…
    - (BOOL) respondsToSelector:(SEL)aSelector
    {
        if (@selector(setTitle:) == aSelector ||
            @selector(title) == aSelector ||
            @selector(setAuthor:) == aSelector ||
            @selector(author) == aSelector)
        {
            return YES;
        }
        
        return [super respondsToSelector: aSelector];
    }
     //…
    @end
    

    相关文章

      网友评论

        本文标题:runtime 基础篇(三)

        本文链接:https://www.haomeiwen.com/subject/evaudttx.html