美文网首页
函数调用

函数调用

作者: 枫叶1234 | 来源:发表于2018-05-03 15:59 被阅读7次

Objective-C是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IML)组成的。Selector相当于门牌号,而Implement才是真正的住户(函数实现)。

和现实生活一样,门牌可以随便发(@selector(XXX)),但是不一定都找得到住户,如果找不到系统会给程序几次机会来程序正常运行,实在没出路了才会抛出异常。下图是objc_msgSend调用时,查找SEL的IML的过程。咱们以这个流程为例看看其中涉及的很有用的函数。

231837047638961.png

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,如果找到函数实现则执行函数。如果找不到继续其他查找流程。

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对象中执行。

methodSignatureForSelector:

原型:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

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

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

doesNotRecognizeSelector:

原型:

- (void)doesNotRecognizeSelector:(SEL)aSelector

作为找不到函数实现的最后一步,NSObject实现这个函数只有一个功能,就是抛出异常。

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

使用场景

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

1、调用resolveInstanceMethod给个机会让类添加这个实现这个函数

2、调用forwardingTargetForSelector让别的对象去执行这个函数

3、调用methodSignatureForSelector(函数符号制造器)和forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。

如果都不中,调用doesNotRecognizeSelector抛出异常。

相关文章

  • JS函数调用

    js 里函数调用有4种模式:方法调用、正常函数调用、构造器函数调用、apply/call 调用。无论哪种函数调用除...

  • [转载]JavaScript权威指南(8)--函数

    文章前言 一 函数定义 二 函数调用 1,函数调用 2,方法调用 3,构造函数调用 4,间接调用 三 函数的实参和...

  • 【Solidity学习笔记】外部函数的调用

    3.8 外部函数的调用 在Solidity中,有两种函数调用:内部函数调用和外部函数调用。内部函数调用是指一个函数...

  • js里函数调用的四种模式

    js 里函数调用有4种模式:方法调用、正常函数调用、构造器函数调用、apply/call调用。同时,无论哪种函数调...

  • Nodejs学习笔记-函数

    调用本地函数调用外部函数-支持一个函数调用外部函数-支持多个函数字符串方式调用函数 代码:https://gith...

  • 内联函数

    解决函数调用效率的问题:函数之间调用,是内存地址之间的调用,当函数调用完毕之后还会返回原来函数执行的地址。函数调用...

  • Python学习(二)

    函数 python内置了很多函数可以直接被调用,可以从官方文档查阅内置的函数. 函数调用 调用abs()函数 函数...

  • JavaScript函数的四种调用模式

    函数调用模式 单独独立调用的就是函数:函数名(参数) 任何自调用函数都是函数模式 this表示全局变量 方法调用模...

  • 递归为什么会导致栈溢出

    下面来看一下,函数是如何调用的,在函数A里调用函数B,我们称A为调用者函数,B是被调用函数。每一次函数的调用,都会...

  • 2017-03-31 面向对象学习笔记

    函数的调用方式和 this 丢失 函数调用方式 普通函数方式调用 this - window 对象的...

网友评论

      本文标题:函数调用

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