OC运行时编程指导
参考:Apple Document:Objective-C Runtime Programming Guide
OC语言尽可能将决定从编译和链接阶段推迟到运行时去做。
- 运行时版本和平台
- 与运行时进行交互
- 消息发送(Messaging)
- 动态方法解析
- 消息转发(Message Forwarding)
- 类型编译
- 定义的属性
一,历史和现代版本
“现在版本”被引入到OC2.0中,突出的特性是“不易碎性”,即如果改变了一个类的实例变量,不需要重新编译集成该类的子类;而“历史版本”需要重新编译。
iPhone以及64位的OSX应用使用“现代版本”。
二,同运行时交互
三种同Runtime交互的方式
- 通过OC源代码
OC源代码背后是运行时的消息传递机制。 - NSObject方法
(1)类似C++多态的运行时机制。
(2)一些NSObject的方法请求运行时信息:isKindOfClass,isMemberOfClass,respondsToSelector,conformsToProtocol,methodForSelector等。
(3)运行时函数。
三,消息发送
- objc_msgSend函数
OC中,消息在运行时才会绑定到方式实现。
[receiver message] -> objc_msgSend(receiver, selector, arg1, arg2, …)
消息传递函数的工作:
(1)首先找到选择器对应的函数实现。
(2)调用函数实现,将接受对象以及参数传给函数实现。
(3)最终将函数实现的返回值作为自己的返回值返回。
四,动态方法解析
如何动态的提供一个方法的实现呢?
@dynamic propertyName;
上面的代码告诉编译器,该属性关联的方法是动态提供的。
可以通过实现resolveInstanceMethod:和resolveClassMethod: 方法来为实例方法和类方法动态的提供实现。
一个OC方法就是一个至少有两个参数的简单的C函数,可以通过class_addMethod为类增加方法。
所以,如果有下面的方法:
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
可以用下面的方法resolveInstanceMethod将其动态的添加到类中,将类的成员函数命名为resolveThisMethodDynamically:
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
动态加载
一个OC程序可以在进行时加载并链接新的类和属性。新的代码被合并到程序内,跟启动时被加载的类和属性一同对待。
动态加载可以做很多事情,例如系统通用设置中的很多模块都是动态加载的。
在Cocoa环境中,动态加载通常用来允许应用被定制化。其他人可以编写你的程序运行时所加载的模块–跟IB加载用户调色板和OSX系统设置加载用户设置模块一样。被加载的模块扩展了应用的功能。他们通过你允许、但是未参与的方式实现。你提供框架,他们提供代码。
四,消息转发
将消息传递给一个不处理这个消息的对象会发生错误。但是,在抛出错误前,运行时系统接受消息的对象第二次机会去处理这个消息。
上面说的第二次机会就是运行时会给对象发送一个forwardInvocation:消息,带一个NSInvocation参数。这个对象封装了原始的消息以及传递给它的参数。
你可以实现一个forwardInvocation:方法,给消息一个默认的返回,或者用其他方式避免错误。
考虑这个场景,首先,你设计了一个可以相应negotiate消息的对象,你想要它的返回值包括另一个对象的返回值。你可以在negotiate方法的实现中,简单的通过将negotiate传递给另一个对象来实现。
更进一步,假设你想要你的对象相应negotiate消息,返回完整的返回定义在另一个类中的相应。一种方式是让你的类继承另一个类的方法。但是,这种方法可能会因为你的类跟实现negotiate的类不在一个继承层次上而不能成行。
即使不能继承negotiate方法,你也可以借用一下另一个类中的这个方法:
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
这种方式有点笨,特别是如果你希望你的对象传递很多消息给其他对象时,你需要去实现你想覆盖的每一个方法。另外,你不可能处理在写代码时还不知道的情况。这就依赖于运行时的事件,这将作为未来实现的类和方法。
当一个对象由于没有方法匹配消息中的选择器而无法响应消息时,运行时系统通过发送一个forwardInvocation:消息通知该对象,但是NSObject版本的方法简单调用了doesNotRecognizeSelector:,所以你需要去实现它。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
注意:forwardInvocation只在当前对象的现有方法无法处理消息时,才能被执行。
多继承
Warrior对象继承了Warrior,仿佛同时继承了Diploment.
代理对象
消息传递和继承
虽然消息传递可以模拟继承,但是NSObject类并没有将他们混淆。考虑respondsToSelector:和isKindOfClass:方法,如果给一个Warrior对象发送respondsToSelector:方法,返回的是NO。可以重载该方法以返回YES。
除了respondsToSelector: 和 isKindOfClass:,instancesRespondToSelector:方法也应该被考虑。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
注意:
1,只有实现了methodSignatureForSelector,forwardInvocation才会被调用。
2,头文件#import