本条要点:(作者总结)
- 向第三方类中添加分类时,总是给其名称加上你专用的前缀。
- 向第三方类中添加分类时,总应给其中的方法名加上你专用的前缀。
分类机制通常用于向无源码的既有类中新增功能。这个特性极为强大,但在使用时也很容易忽视其中可能产生的问题。这个问题在于:分类中的方法是直接添加在类里面的。它们就好比这个类中的固有方法。将分类方法加入类中这一操作是在运行期系统加载分类时完成的。运行期系统会把分类中所实现的每个方法都加入类的方法列表中。如果类中本来就有此方法,而分类又实现了一次,那么分类中的方法会覆盖原来那一份实现代码。实际上可能会发生很多次覆盖,比如某个分类中的方法覆盖了 “主实现” 中的相关方法,而另外一个分类中的方法又覆盖了这个分类中的方法。多次覆盖的结果以最后一个分类为准。
比方说,要给NSString 添加分类,并在其中提供一些辅助方法,用于处理与 HTTP URL 有关的字符串。你可能会把分类写成这样:
1 @interface NSString (HTTP) 2 3 // Encode a string with URL encoding 4 - (NSString *)urlEncodedString; 5 6 // Decode a URL encoded string 7 - (NSString *)urlDecodedString; 8 9 @end
现在看起来没什么问题,可是,如果还有一个分类也往 NSString 里面添加方法,那会如何呢?那个分类里可能也有个名叫 urlEncodedString 的方法,其代码与你所添加的大同小异,但却不能正确实现你所需的功能。那个分类的加载时机如果晚于你所写的这个分类,那么其代码就会把你的那一份覆盖掉,这样的话,你在代码中调用 urlEncodedString 方法时,实际执行的是那个分类里的实现代码。由于其执行结果和你预期的值不同,所以自己所写的那些代码也许就无法正常运行了。这种bug 很难追查,因为你可能意识不到实际执行的 urlEncodedString 代码并不是自己实现的那一份。
要解决此问题。一般的做法是: 以命名空间来区别各个分类的名称与其中所定义的方法。想在 Objective-C 中实现命名空间功能,只有一个办法,就是给相关名称都加上某个共用的前缀。与类名加前缀时所应考虑的因素相似,给分类所加的前缀也要选的恰当才行。一般来说,这个前缀应该与应用程序或程序库中其他地方所用的前缀相同。于是,我们可以给刚才那个 NSString 分类加上 ABC 前缀:
1 @interface NSString (ABC_HTTP) 2 3 // Encode a string with URL encoding 4 - (NSString *)abc_urlEncodedString; 5 6 // Decode a URL encoded string 7 - (NSString *)abc_urlDecodedString; 8 9 @end
从技术角度讲,并不是非得用命名空间把各个分类的名称区隔开不可。即便两个分类重名了,也不会出错。然而这样做不好,编译器会发出类似下面这种警告信息:
1 warning: duplicate definition of category 'HTTP' on interface 2 'NSString'
即便加了前缀,也难保其他分类不会覆盖你所写的方法,然而几率却小了很多,因为其他程序库很少会和你选用同一个前缀。这样做也能避免类的开发者以后在更新该类时所添加的方法与你在分类中添加的方法重名。比方说,假如苹果决定在 NSString 类里添加 urlEncodedString 方法,而你在分类中所写的方法又没加前缀,那么可能就会覆盖苹果公司的方法,这样做不合适,因为 NSString 类的其他使用者想得到由苹果公司实现的代码所输出的结果,而非你所返回的那个结果。还有一种可能,就是苹果公司编写的实现代码带有一些附加效果,该方法若为你所写的代码所覆盖,则会令对象内的数据互不一致,从而造成难于查找的 bug。
此外还要记住,如果向某个类的分类中加入方法,那么在应用程序中,该类的每个实例均可调用这些方法。比方说,若是向 NSString、NSArray、NSNumber 这种系统类里加入方法,那么这些类的每个实例均可调用你所加的方法,即便这些实例不是由你的代码创建出来的,也依然会如此。如果你无意中把自己分类里的方法名起的和其他分类一样,或是与第三方库所添分类中的方法重名了,那么就可能出现奇怪的 bug,因为你以为此方法执行的是自己所写的那份代码,然而实际上却不是。与之相似,刻意覆写分类中的方法也不好,尤其是当你把代码发布为程序库供其他开发者使用,而他们又要依赖系统中现存的功能时,更不应该这么做。若是其他开发者又覆写了同一个方法,那么情况会更糟,因为无法确定最后到底会执行哪份实现代码。这又一次说明了为何要给分类中的方法名加上前缀。
END