函数调用

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

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

图:运行时查找函数的流程

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 == ) {
//动态造一个 setter函数
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
} else {
//动态造一个 getter函数
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
} - (void)forwardInvocation:(NSInvocation *)invocation
{
//拿到函数名
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:@"set"].location == ) {
//setter函数形如 setXXX: 拆掉 set和冒号
key = [[key substringWithRange:NSMakeRange(, [key length]-)] lowercaseString];
NSString *obj;
//从参数列表中找到值
[invocation getArgument:&obj atIndex:];
[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抛出异常。

文章参考

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/clm/NSObject/

https://www.mikeash.com/pyblog/friday-qa-2009-03-27-objective-c-message-forwarding.html

http://blog.csdn.net/yiyaaixuexi/article/details/8970734

补充: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

继承自NSObject的不常用又很有用的函数(2)的更多相关文章

  1. 继承自NSObject的不常用又很有用的函数(1)

    初始化阶段 —— load 和 initialize load函数 原型: +(void)load 当类被引用进程序的时候会执行这个函数. 在一个程序开始运行之前(在main函数开始执行之前),在库开 ...

  2. linux不常用但很有用的命令(持续完善)

    Linux登录后设置提示信息: /etc/issue 本地端登录前显示信息文件 /etc/issue.net 网络端登录前显示信息文件 /etc/motd 登陆后显示信息文件 可以添加以下几个常用选项 ...

  3. Sql Server 三个很有用的函数

    好久没有写有关SqlServer 数据库方面技术的文章了,正好今天遇到了一个问题,我就把这个当做一个练习记录下来.今天遇到一个麻烦事,详情如下:公司买了一个系统,在这个系统里面有一个“充值卡”的功能, ...

  4. CSS3中不常用但很有用的属性-1

    内容来源于W3Cschool和<图解CSS3核心技术与案例实战> 1.:target选择器 URL 带有后面跟有锚名称 #,指向文档内某个具体的元素.这个被链接的元素就是目标元素(targ ...

  5. mysql不常用但很有用的语句整理

    mysqld_multi多实例停止.启动 mysqld_multi --defaults-file=/etc/my.cnf start 1,2 mysqld_multi --defaults-file ...

  6. java不常用但很有用的问题排查工具(持续完善)

    因为用的频率不是很多,老忘掉,每次都要搜下,特记录下备忘. 查看进程的启动jvm选项 [root@iZ23nn1p4mjZ ~]# jinfo -flags 16603Attaching to pro ...

  7. 不常用但很有用的git show 和 git blame

    团队使用git 合作时,可能遇见想要查看一段比较难以阅读代码, 此时可能需要联系最新的修改者是哪位,这时候最有用的最快捷的方法就是git blame 啦, 这个指令的output是一个文件的各个区域段 ...

  8. closest()一个在评论里很有用的函数

    实例 本例演示如何通过 closest() 完成事件委托.当被最接近的列表元素或其子后代元素被点击时,会切换黄色背景: $( document ).bind("click", fu ...

  9. 8个很有用的PHP安全函数,你知道几个?

    原文:Useful functions to provide secure PHP application 译文:有用的PHP安全函数 译者:dwqs 安 全是编程非常重要的一个方面.在任何一种编程语 ...

随机推荐

  1. mssql 修改文件逻辑名称

    --查看文件逻辑名SELECT name FROM sys.database_files ALTER DATABASE [本身数据库名称]MODIFY FILE ( NAME = [原错误数据库名称] ...

  2. Oracle创建,删除用户与表空间

    1.创建表空间与用户 a:创建数据表空间 create tablespace user_data logging datafile 'D:\oracle\product\10.2.0\oradata\ ...

  3. 利用GCC编译器生成动态链接库和静态链接库

    转载请标明:http://www.cnblogs.com/winifred-tang94/ 1.编译过程 gcc –fPIC –c xxx.c 其中-fPIC是通知gcc编译器产生位置独立的目标代码. ...

  4. Ztree使用笔记

    在项目中需要用到树,使用了Ztree.(官网地址:http://www.treejs.cn/v3/main.php#_zTreeInfo,介绍很详细,有API,有demo) 1.初始化树:   $.f ...

  5. Redis持久化-数据丢失及解决(转载)

    本文转载自        Redis持久化-数据丢失及解决  感谢原作者 Redis的数据回写机制 Redis的数据回写机制分同步和异步两种, 同步回写即SAVE命令,主进程直接向磁盘回写数据.在数据 ...

  6. linux信号处理时机

    信号号称所谓软中断,事实上,还是没有真正的硬件中断那样能随时改变cpu的执行流 硬件中断之所以能一发生就得到处理是因为处理器在每个指令周期的结尾都会去检查中断,这种粒度是很细的 但是信号的实现只是在进 ...

  7. JQuery源码解析(十一)

    内存泄露 什么是内存泄露? 内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束.在C++中,因为是手动管理内存,内存泄露是经常出现的事情.而现在流行的C#和Java等语言采用了自动 ...

  8. Android Priority Job Queue (Job Manager):多重不同Job并发执行并在前台获得返回结果(四)

     Android Priority Job Queue (Job Manager):多重不同Job并发执行并在前台获得返回结果(四) 在Android Priority Job Queue (Jo ...

  9. PHP 发送与接收流文件

    http://blog.csdn.net/fdipzone/article/details/40098169

  10. 移动互联网实战--资源类APP的数据存储处理和优化

    前言: 对于资源类的APP, 其音频/图形占据了APP本身很大的比例. 如何存储和管理这些资源文件, 成了一个颇具挑战性的难点. 移动端的碎片化, 高中低端手机的并存, 需要开发者不光是具备基础的存储 ...