如果你不仅仅是想进行简单的 swizzling,而是要实际修改库,我可以想到一些有用的例子。
自 iOS 5 起,
NSURLConnection
提供了
sendAsynchronousRequest:queue:completionHandler:
方法,它是一种基于块(闭包)的方式,可以从任何可通过 URL(本地或远程)标识的资源中执行异步加载。这是一种非常有用的方法,因为它使您的代码更加清晰、简洁,比传统的委托模式更容易将相关部分的代码放在一起。
该方法在iOS 4中没有提供。因此,在我的项目中,当应用程序启动时(通过适当的
+(void)load
),我检查该方法是否已定义。如果没有,我会将其实现补丁到类上。从此以后,程序的其他部分可以按照iOS 5的规范编写,而无需执行任何版本或可用性检查,就像我只针对iOS 5一样,除了它也可以在iOS 4上运行。
在Java或C ++中,我想通过创建自己的类来发出URL连接,每次调用时执行运行时检查来实现相同类型的事情。这是一个更糟糕的解决方案,因为它更难退回。反过来,如果有一天我决定仅支持iOS 5,我只需删除添加了
sendAsynchronousRequest:...
的源文件即可。没有其他变化。
关于方法混淆,我只看到它被建议在某些情况下使用,即当有人想要更改现有类的功能,但无法访问创建该类的代码时。因此,通常你试图通过对其实现进行假设来从外部修改逻辑不透明的代码。我不会支持任何语言中的这个想法。我猜在Objective-C中更多地推荐它是因为苹果更倾向于使事情变得不透明(例如,在iOS 3.1之前要显示自定义相机视图的每个应用程序,以及在iOS 4.0之前要对相机输入执行自定义处理的每个应用程序等),而不是因为这是Objective-C中的一个好主意。它不是。
编辑:进一步阐述 - 我不能发布完整的代码,因为我是作为工作的一部分编写的,但我有一个名为NSURLConnectionAsyncForiOS4
的类,它实现了sendAsynchronousRequest:queue:completionHandler:
。该实现实际上非常简单,只是将操作调度到指定队列,通过旧的sendSynchronousRequest:...
接口执行同步加载,然后将结果发布到处理程序上。
那个类有一个
+ (void)load
,这是你添加到一个类中的类方法,它将在该类被加载到内存中后立即执行,实际上作为元类的全局构造函数,并具有所有通常的警告。在我的
+load
中,我直接使用 Objective-C 运行时通过其 C 接口检查是否在
NSURLConnection
上定义了
sendAsynchronousRequest:...
。如果没有,则将我的实现添加到
NSURLConnection
中,因此从此以后它就被定义了。这明确不是 swizzling - 我没有调整任何东西的现有实现,我只是在 Apple 的实现不可用时添加了一个用户提供的实现。相关的运行时调用是
objc_getClass
、
class_getClassMethod
和
class_addMethod
。在代码的其余部分,每当我想执行异步 URL 连接时,我只需编写例如:
[NSURLConnection sendAsynchronousRequest:request
queue:[self anyBackgroundOperationQueue]
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *blockError)
{
if(blockError)
{
}
if(data)
{
}
}
因此,我的其余代码只是针对iOS 5 API编写的,并且不知道也不关心我在其他地方提供了微小部分iOS 5更改的代码。就像我说的那样,当我不再支持iOS 4时,我将从项目中删除这个代码片段,我的所有其他代码都将继续不知道或不关心。
我有类似的代码来提供
NSJSONSerialization
的替代部分实现(它在运行时动态创建一个新类并将方法复制到其中);你需要做的唯一调整是,其他地方对
NSJSONSerialization
的引用将由链接器在加载时解析一次,这并不是你真正想要的。因此,在我的预编译头文件中添加了一个快速的
#define
,将
NSJSONSerialization
定义为
NSClassFromString(@"NSJSONSerialization")
。这样做虽然不太优雅,但在找到一种方法以保持iOS 4支持的同时仅按照iOS 5标准编写项目方面,它是一个类似的行动线路。