Objective-C Runtime 指南(2)

来源:互联网 发布:淘宝节日活动有哪些 编辑:程序博客网 时间:2024/05/19 13:59

动态方法决议

本章介绍了如何动态的实现一个类的方法。

动态方法决议:

    一些时候,你可想要动态的实现类的一个方法。例如:Objective-C的属性标识符@dynamic propertyName 
表明这个属性对应的方法将会被动态的提供。
想要动态的给方法提供一个实现,需要重写resolveInstanceMethod:和resolveClassMethod:, 然后利用class_addMethod函数给类提供新的方法实现。例如,有这样一个函数:

void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}

可以这样动态实现:

@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

    消息的分发和方法的动态决议是互不影响的。一个类有机会在启动消息分发机制之前对方法进行决议。如果respondsToSelector:或者instancesRespondToSelector:被调用,动态决议机制给了IMP一个执行的机会,如果resolveInstanceMethod:被实现了,但是某个选择器实际上想要走转发机制,在函数对于那些选择器中返回NO就可以了。

动态载入

    Objective-C程序可以在运行时装载和链接新的类和类别,并且和一开始就加载的类和类别没有任何区别。利用动态载入技术可以做很多有意义的事情。例如,可以根据系统偏好设置动态加载不同模块。

    在Cocoa环境下,动态加载常常被用于应用程序的定制。可加载的模块拓展了应用程序能做的事情,可以允许用户做某些决定。你提供了框架,用户自己提供实现代码。在Mach-O模块中有很多和动态加载相关的函数,进一步1,Cocoa的NSBundle类提供了更方便的动态加载接口——面向对象的和service整合的接口。想要了解更多,查看Foundation framework的NSBundle文档和OS XABI Mach-O文件。


消息转发

转发

    向一个对象发送它不支持的消息会得到一个Error,但是在向用户发送这个Error之前,runtime 向对象发送一个forwardInvocation:消息, 只有一个参数NSInvocation,NSInvocation中包含了原始的消息和参数。

    通过实现响应forwardInvocation:的方法可以改变发送Error消息。一般forwardInvocation:将对象不能响应的消息转发给其他对象。

    为了理解消息转发,想像一下下边的场景:你设计了一个对象,响应negotiate消息,但是你希望对negotiate消息的相应中包含另外一个对象对negotiate的响应,这样你就需要在消息响应函数中将negotiate发送给另外一个对象。进一步,如果你希望你的对象对negotiate的响应和另外一个对象对negotiate的响应完全相同,你可能希望你的对象继承自另外一个对象,但是很可能这是行不通的,即使行得通,仅仅因为这个理由建立一个继承关系也是不恰当的。
    因此,正确的做法是,在方法内部将negotiate转发给另外一个对象。

- negotiate

{

if ( [someOtherObject respondsTo:@selector(negotiate)] )

return [someOtherObject negotiate];

return self;

}

    如果有很多消息需要转发,这么做有点显得笨拙,需要为每个想要转发的消息写一个函数。并且,有些消息可能是事先不知道的,就没有办法实现。
    forwardInvocation:消息优美的解决了这个问题,当对象因为没有选择器不能响应消息的时候,runtime将forwardInvocation发送给对象,NSObject简单的实现了对forwardInvocation的响应:调用doesNotRecognizeSelector:。如果你自己的类重写了对forwardInvocation的响应,就可以利用这个机会将消息转发给其他对象。
    为了转发消息,forwardInvocation:的实现需要做到两点:

  • 决定要将消息转发给谁
  • 将原来的消息参数传递给新的消息接收对象

消息的转发使用invokeWithTarget:

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

if ([someOtherObject respondsToSelector: [anInvocation selector]])

[anInvocation invokeWithTarget:someOtherObject];

else

[super forwardInvocation:anInvocation];

}

    消息的返回值会返回到最初的发送者那里。就如同转发的对象自己响应了这个消息一样。
    forwardInvocation可以作为一个未知消息转发中心,将消息分发给不同的对象,或者同一个对象,可以将消息翻译成其他消息,或者直接忽略某些消息。forwardInvocation还可以将多个消息的返回值合并。总之,forwardInvocation可以在错误消息传递的时候做任何想做的事情。这为程序设计中对象的链接提供了一种方式。
注意:只有当对象没有响应消息的方法的时候forwardInvocation才会被调用,如果对象有响应消息的方法,就不会被调用。对于forwarding和invocation的更多讨论,参见NSInvovation类的文档。


转发和多继承

消息转发使用了其他类中定义的方法,这点有点像继承。如下图所示,一个对象转发了一个消息:


    在图示中,一个Warriors的实例将一个negotiate消息转发给了一个Diplomat的实例。于是Warriors将会像Diplomat一样(会谈判),尽管实际上是Diplomat做的这件事情。
    这样,通过消息转发,一个对象可以在表现上实现两个“继承”分支的方法。
    使用消息转发的优点是:多继承将不同的方法合并到一个类中,使这个类比较臃肿,功能很多(译者:另外还有语义上的问题,如Warrior继承自Diplomat会让人觉得不舒服)但是转发仅仅是建立了对象之间的一个链接,更像是组合。


代理对象:

    消息转发机制可以用于实现代理模式,用一个轻量级的对象给其他对象路由消息。

    “代理”在进程间通信过程中,负责管理通信的所有细节,对于代理的使用着来说,与远程对象的通信就像是和本地对象通信一样,直接和代理交互即可,但是代理并没有重复实现远程对象的功能,仅仅是给远程对象提供了一个本地调用的地址而已。
    即使没有进程间通信,你也可能使用代理,比如,你有一个对象需要操纵很多数据,比如画图,或者读取很多文件等等,这个对象的初始化需要很多开销,你可能想在使用的时候才加载它,这时候,就可以使用一个代理对象作为占位符。当真的需要那个大对象的时候,代理对象再负责初始化它,并将消息发送给它。


消息转发和继承:

    尽管消息转发和继承有很多相似之处,NSObject却从来不会将它们搞混。像respondsToSelector:和isKindOfClass:这样的函数就能明显的判断出消息转发和继承。因此,下边的代码会返回NO,尽管在上边的例子中Warrior貌似可以响应negotiate消息:

if( [aWarrior respondsToSelector: @selector(negotiate)]) { }

但是,如果你就是想让这个表达式返回YES呢?答案是重写respondsToSelector:方法,

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES; //这个分支是正常的该类响应的消息。
    else {
            //这个分支是你想让它表现的像响应的消息
    /* Here, test whether the aSelector message can *
    * be forwarded to another object and whether that *
    * object can respond to it. Return YES if it can. */
}
    return NO;
}


        同理重写isKindOfClass可以让一个类貌似属于另外一个继承体系。需要注意的是,除了respondsToSelector: 和 isKindOfClass:,还有几个函数也必须重写,instancesRespondToSelector:,如果被”模拟继承“的类遵守某些协议,那么conformsToProtocol:也需要重写,同理配套设施methodSignatureForSelector:也需要重写。至此,你实现了一个伪继承(我想和继承你的行为一摸一样,但是我就是不继承你)

    Objective-C提供了如此灵活的技术,但是却不建议使用,最好在没有其他解决方案的情况下才选择这种技术。并且这是一种高级技术,要确保你知道自己在做什么

Note: This is an advanced technique, suitable only for situations where no other solution is possible.
It is not intended as a replacement for inheritance. If you must make use of this technique, make
sure you fully understand the behavior of the class doing the forwarding and the class you’re
forwarding to.

    上边提到的方法在NSObject类的文档中均有说明。对于更多有关invokeWithTarget的话题,请参考NSInvocation类的官方文档



原创粉丝点击