第08章 类NSObject和运行时系统 中 上
8.2 消息发送机制
8.2.1 选择器和SEL类型
程序中的方法名(选择器)在编译后会被一个内部标识符所替代,这个内部标识符所对应的数据类型就是SEL类型。
Objective-C为了能够在程序中操作编译后的选择器,定义了@selector()指令。通过使用@selector()指令,就可以直接引用编译后的选择器。如:
@selector(mutableCopy)
也可以使用SEL类型的变量来发送消息,为此,NSObject中准备了如下方法。
- (id) performSelector:(SEL)aSelector
向消息的接受者发送aSelector代表的消息,返回这个消息执行的结果。
- (id) performSelector: (SEL)aSelector withObject: (id) anObject
如下面的例子:
SEL method = (cond1) ? @selector(activate:) : @selector(hide:);
id obj = (cond2) ? myDocument : defaultDocument;
[target performSelector: method withObject:obj];
这种调用方法的方式很像C语言中的函数指针的用法,函数指针是函数在内存中的地址。指针对应的函数是在编译的时候决定的,不能够执行指定之处的函数。SEL类型就相当于方法名,根据消息接受者的不同(如上例中的target),来动态执行不同的方法。
8.2.2 消息搜索
对象收到一个消息后会执行哪个方法是被动决定的。
所有的实例变量都存在一个Class类型的isa变量,他就是类对象。当收到消息后,运行时系统会检查类内是否有这个消息选择器相同的方法,如果有就执行对应的方法,如果没有就通过类对象中指向父类的指针查找父类中是否有相应的方法,以此类推,如果一直查找不到,则会提示执行时错误。
NSObject中定义可以动态查询一个对象是否能够相应某个选择器的方法。如:
- (BOOL) respondsToSelector:(SEL)aSelector
+(BOOL)instancesRespondToSelector:(SEL)aSelector;
8.2.3 以函数的形式来调用方法
但如果想尽可能地让程序更快一点,或则需要按照C语言的管理传递函数指针的时候,可以直接调用方法对应的函数,以节省发送消息的开销。
但要注意的是,如果以函数的形式来调用方法的话,就无法利用面向对象的动态绑定等功能。
通过使用下面的方法,可以获得某个对象持有的方法的函数指针,这些方法都定义在NSObject中。
- (IMP)methodForSelector:(SEL) aSelector
搜索和制定选择器相对应的方法,并返回指向该方法实现的函数指针。实例对象和类对象都可以使用这个方法。实例对象使用时,会返回实例方法对应的函数,对类对象使用时,会返回类对象相对应的函数。
+(IMP)instanceMethodFoeForSelector:(SEL)aSelector
搜索和指定选择器相对应的实例方法,并返回指向该实例方法实现的函数指针。
IMP是“implementation”的缩写,他是一个函数指针指向了方法实现代码的入口。IMP的定义为:
typedef id (*IMP)(id,SEL,…);
这个被指向的函数包括id(self 指针)、调用的SEL(方法名),以及其他一些参数。示例如下:
- (id)setBox:(id)obj1 title:(id)obj2;
假设foo是这个方法所属类的一个实例变量。
IMP funcp;
funcp = [foo methodForSelector:@selector(setBox:title:)];
xyz = (*funcp)(foo,@selector(setBox:title:),param1,param2); //示例中的代码无法正常编译、处理方法见下方引用的Blog。
由上可以看出,调用方法对应的函数时,除了方法声明时的参数外,还需要把消息接受对象和消息的选择器作为参数。虽然没有明确声明,但方法内部也可以访问这两个参数,因此这两个参数被叫做隐含参数(hidden arguments)。
8.2.4 对self进行赋值
8.2.5 发送消息的速度
8.2.6 类对象和根类
8.2.7 Target-action paradigm
8.2.8 Xcode中的动作方法和Outlet的写法
轻松学习之 IMP指针的作用
http://www.cocoachina.com/ios/20150717/12623.html
2015-07-17 14:16 编辑: lansekuangtu 分类:iOS开发 来源:J_雨的简书
可能大家一直看到有许多朋友在Runtime相关文章中介绍IMP指针的概念,那么IMP究竟有什么实际作用呢?让我们先从一个函数看起来。
Method Swizzling
如果对Runtime有一定了解的话,一定听说过或者用过这个函数:
1 | void method_exchangeImplementations(Method m1, Method m2) |
它通常叫做method swizzling,算是ObjC的"黑魔法"了,作用就是在程序运行期间动态的给两个方法互换实现,比如有这样一种使用场景:
我们的程序中有许多个ViewController,我想在对项目改动最小的情况下,在当每个Controller执行完ViewDidLoad以后就在控制台把自己的名字打印出来,方便我去做调试或者了解项目结构。
有许多朋友会这样说,让所有控制器都继承一个BaseController不就可以了吗?我在这里要解释一下这样做的缺点:假如你的项目里有许多Controller的话,你就需要把项目里凡是没有继承自BaseController的每个Controller都做一次修改了,而且随意更改层级结构会发生意想不到的错误。
其实我们的目的就是重写ViewDidLoad的方法,并在他的方法最后加上几句Log,所以我们需要给UIViewController建立一个category,因为我们知道,如果在Catagory中重写一个方法,就会覆盖它的原有方法实现,但是,这样做以后就没有办法调用系统原有的方法,因为在一个方法里调用自己的方法会是一个死循环。所以我们的解决办法就是,另外写一个方法来和viewDidLoad“交换”,这样外部调用viewDidLoad就会调到新建的这个方法中,同样,我们调用新建的方法就会调用到系统的viewDidLoad中了。
IMP指针
其实,还有一种更加简单的方法可以让我们办到相同的目的,运用IMP指针,IMP就是Implementation的缩写,顾名思义,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP,所以,我们可以直接调用方法的IMP指针,来避免方法调用死循环的问题。
调用一个IMP的方式和调用普通C函数相同,比如:
| id returnObjc = someIMP(objc,SEL,params...); |
不过如果你的项目没有做其他配置的话这样调用编译器是不会通过的,我们来看一下先它的定义:
| /// A pointer to the function of a method implementation. #if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id (*IMP)(id, SEL, ...); #endif |
在默认情况下你的工程是打开这个配置的:Enable Strict Checking of objc_msgSend calls设置为YES
这种情况下IMP被定义为无参数无返回值的函数。所以你需要到工程里搜索到这个选项并把它关闭。这样的麻烦就是,每次使用,你都需要修改工程配置,所以这里我再介绍另外一种办法:重新定义一个和有参数的IMP指 针相同的指针类型,在获取IMP时把它强转为此类型。这样运用IMP指针后,就不需要额外的给ViewController写新的方法:
还有一个地方我们需要注意,如果这样直接调用IMP的话就会发生经典的EXC_BAD_ACCESS错误,我们定义的IMP指针是一个有返回值的类型,而其实我们获取的viewDidLoad这个方法是没有返回值的,所以我们需要新定义一个和IMP相同类型的函数指针比如VIMP,把他的返回值定位Void,这样如果你修改的方法有返回值就用IMP,没有返回值就用VIMP。
值得注意的是,如果你重写的方法有返回值,不要忘记在最后做return。
总结
实际上直接调用一个方法的IMP指针的效率是高于调用方法本身的,所以,如果你有一个合适的时机获取到方法的IMP的话,你可以试着调用它。这是只是IMP使用的场景之一,它还有许多作用,希望大家多多发现。