如何在Objective-C中实现或修改方法调度表?

3
有时候,设置方法调度表很有用,这样调用方法A就会调用AImpl()方法,而在另一个时间调用方法A就会调用BImpl()方法。在Objective-C中,如何为系统自动调用的方法(例如委托方法)进行此操作?
例如,如果系统第一次调用viewDidAppear,我希望它调用viewAppearFirstTime方法,而后续调用viewDidAppear将会触发完全不同的方法体(而不是在代码中使用布尔标志进行if-else检查)。
另一个例子,假设在应用程序中经常调用UIView的drawRect方法,如果第一次调用的drawRect与后续的调用不同,则不想包含if-test,因为那会使代码更难阅读,并且在第一次之后检查也是不必要的。

这对于除了单例之外的任何类来说都是非常棘手的问题,因为方法查找表是按类而不是按实例进行的,所以任何交换都将应用于该类的每个实例。 - Chuck
我同意Chuck的观点——在类上重新映射实现肯定会导致许多极难追踪的错误。此外,我不同意你的说法,认为这会使代码“更难阅读”。如果使用任何标准模式进行操作,即使是初级程序员,代码也应该相当容易阅读。而你的解决方案将使代码更难理解,即“更难阅读”。 - Jason Coco
你能描述一种情况,第一个drawRect:与后续的drawRect:调用不同吗?如果你正在缓存东西,请在getter中缓存它们,而不是在drawRect:中。由于视图可以进入和离开屏幕,所以“第一次”调用drawRect:甚至不清楚是什么意思。是类的第一次?实例的第一次?自从我们被放在屏幕上以来的第一次?自从视图加载以来的第一次(这可能会发生多次)?你有一个具体的例子吗? - Rob Napier
我尝试使用不同的方法来实现viewDidAppearFirstTime。请查看https://github.com/T-Pham/ViewDidAppearFirstTime - Thanh Pham
4个回答

3
我需要说明一下:你描述的特定情况可能不应该这样做。这几乎肯定是过于聪明了,应该将其分为viewDidLoadviewWillAppear而不是两个版本的viewWillAppear。尽管如此,问题确实会出现。
我更喜欢的解决方案是保留一个指向我想要执行的操作的SEL。例如,在复杂的异步活动之后,我使用这种技术来进行“下一步操作”:
if (self.nextActionSelector != NULL)
{
    [self performSelector:self.nextActionSelector];
}

因此,您可以使用一个viewWillAppearSelector,随着时间的推移,viewWillAppear将调用它指向的任何内容。
Ben的方法交换建议在某些情况下很有用,但更常见的是在尝试修改某个现有对象的行为而不是自己时使用。这确实会让调试变得有趣...好吧,其实不是有趣的,而是痛苦的。非常痛苦。
除此之外,我会看一下-forwardInvocation:,它可以用于响应您没有直接实现的消息。然后,您可以重写调用以调用您真正想要的方法。虽然这不是它的预期使用方式。它是用于透明地转发调用到其他对象的。但至少调试起来不那么困难,因为调试器会去你期望的地方。
通常在这种情况下,我不是寻找布尔值,而是寻找我已经运行过的证据。检查view是否已设置,或者初始化为第一次所以我不需要特殊的变量,并且我对清除我的状态(如iPhone在内存紧张时倾倒东西的做法)具有弹性的某些其他值。如果可能的话,我会使这些东西在它们的getter中自我初始化,而不是让某些外部方有特殊的第一次逻辑。保持这里的逻辑简单使维护变得更容易,而且NSInvocation非常慢(好吧,在我的测试中,您除了在紧密循环中以外都不会注意到它,但它比方法调用慢大约500倍。也就是说,我并不建议根据性能做出这个决定,只是为了可维护性)。

2

阅读这篇文章,然后将你的NSInvocation实例放入NSDictionary中。


2
Objective-C运行时允许您在运行时修改从选择器到方法实现的映射,非常灵活。请参阅CocoaDev上的文章获取更多信息:CocoaDev: Method Swizzling。您可以交换任何您已存在映射的函数,因此您可以有效地替换obj-c运行时中的任何函数。
不过,并不是每个地方都应该这样做——首先试一下NSInvocations。由于您可以编写自己的viewDidAppear函数并从其中调用viewAppearFirstTime:函数,所以方法交换可能有些过度使用。
祝你好运!

2
如果您想修改类的所有实例的方法,可以通过方法交换来实现。我从未听说过它被用于此目的,但您可能可以在viewDidAppearFirstTime中使用class_replaceMethod完成您想要的内容。
然而,如果您想修改单个实例的方法分派,我认为您唯一的选择是在viewDidAppear方法中使用条件语句,或者考虑重新设计,不需要在首次和后续出现时使用单独的方法(从您的描述中,将代码分成viewDidLoadviewWillAppear函数似乎很合适)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接