首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》10.2 类别与扩展

关灯直达底部

类别是Objective-C的一个语言特性,你可以通过它探究现有的类并注入额外的方法。类别对应于Swift的扩展(参见第4章)。借助Swift扩展,你可以将类或实例方法添加到Cocoa类中;Swift头文件大量使用了扩展,既用于组织Swift自己的对象类型,也用于修改Cocoa类。与之相同,Cocoa使用类别来组织自己的类。

Objective-C的类别有名字,你会在头文件、文档中看到对这些名字的引用。不过,名字本身是没什么意义的,因此不用考虑太多。

10.2.1  Swift如何使用扩展

查看主Swift头文件,你会看到很多原生对象类型声明都包含了一个初始声明,后跟一系列扩展。比如,在声明了泛型结构体Array<Element>后,Array结构体头会继续声明不少于7个扩展。其中有些扩展增加了协议的使用;不过大多数并没有。它们都会向Array添加属性或方法声明;这正是扩展的意义所在。

扩展的功能性不是最重要的;头文件本可以在Array结构体的声明中加入所有这些属性与方法。相反,它将这些内容分散到了多个扩展中。扩展用于将相关的功能聚合到一起,组织对象类型的成员,从而使得开发者能够更容易地理解。

在Swift Core Graphics头文件中,一切都是扩展。Swift在这里适配了其他地方定义的类型,将Swift数字类型用于Core Graphics和CGFloat数字类型,将Cocoa结构体如CGPoint与CGRect用作Swift对象类型。特别地,它向CGRect提供了多个附加属性、初始化器与方法,这样就可以直接按照Swift结构体与之交互,而不必调用Cocoa Core Graphics C辅助函数来操纵CGRect了。

10.2.2 你应该如何使用扩展

Swift允许编写全局函数,这么做也没什么错的。不过,为了面向对象的封装,你常常需要编写作为已有对象类型一部分的函数。最简单,也是最有效的方式就是通过扩展将这种函数作为方法注入已有的对象类型中。如果仅仅添加一两个方法就使用子类化显得太过于笨重了;此外,这么做也没什么太大的好处。(另外,扩展可用于Swift全部3种对象类型,但我们却无法子类化Swift枚举和结构体。)

比如,假设想要向Cocoa的UIView类中添加一个方法。你可以子类化UIView并声明自己的方法,不过这样会使方法只位于你的UIView子类以及该子类的子类中:它不会出现在UIButton、UILabel及其他内建的UIView子类中,这是因为它们都是UIView的子类,而不是你定义的子类的子类,你也无法改变这一点!另外,扩展可以漂亮地解决这个问题:将方法注入到UIView中,那么它会被所有内建的UIView子类所继承。

在Swift 2.0中,你可以通过协议扩展以一种有选择但却统一的方式将功能注入类中。假设我需要一个UIButton和一个UIBarButtonItem(它们不是UIView,但却拥有类似于按钮的行为)来共享某个方法。我可以声明一个协议,它拥有一个方法,同时在协议扩展中实现该方法,然后通过扩展让UIButton和UIBarButtonItem使用该协议,因此就会拥有该方法:


protocol ButtonLike {    func behaveInButtonLikeWay}extension ButtonLike {    func behaveInButtonLikeWay {        // ...    }}extension UIButton : ButtonLike {}extension UIBarButtonItem : ButtonLike {}  

第4章介绍了几个扩展示例,这些示例都来自于我所编写的iOS程序(参见4.10节)。此外,我常常会与Swift头文件相同的方式来使用扩展,将单个对象类型的代码组织到多个扩展中,目的在于表述清晰。

10.2.3  Cocoa如何使用类别

Cocoa将类别作为一种组织工具,这一点与Swift扩展很相似。类的声明常常会按照功能拆分为多个类别,这些类别位于单独的头文件中。

NSString就是个很好的示例。它定义在Foundation框架中,基本的方法声明在NSString.h中。除了初始化器,我们发现NSString本身只有两个方法,分别是length与characterAtIndex:,因为这两个方法是字符串所需的最基础的功能。

额外的NSString方法(创建字符串、处理字符串编码、分割字符串、字符串搜索等)都分布在各个类别当中。它们都位于Swift转换后的扩展当中。比如,在String类本身的声明之后,我们发现Swift的转换中有如下代码:


extension NSString {    func substringFromIndex(from: Int) -> String    func substringToIndex(to: Int) -> String    // ...}  

这实际上是Swift对如下Objective-C代码的转换结果:


@interface NSString (NSStringExtensionMethods)- (NSString *)substringFromIndex:(NSUInteger)from;- (NSString *)substringToIndex:(NSUInteger)to;// ...  

符号(关键字@interface,后跟类名,再后跟一对圆括号,里面是另一个名字)是Objective-C类别。

此外,虽然有一些Cocoa NSString类别出现在相同的文件NSString.h中,不过还有很多位于其他文件中。比如:

·字符串可能作为文件路径名,因此我们在NSPathUtilities.h中发现了一个NSString类别,其方法和属性如pathComponents等用于将路径名字符串划分为各个组成部分。

·在NSURL.h(主要用于声明NSURL类及其类别)中还有另一个NSString类别,它声明了用于处理URL字符串中百分号转义的方法,如stringByAddingPercentEscapesUsingEncoding。

·在另一个完全不同的框架(UIKit)中,NSStringDrawing.h还添加了两个NSString类别,其方法drawAtPoint:等用于在图形上下文中绘制字符串。

这种组织方式对于程序员并没有那么重要,因为NSString就是个NSString,无论它如何获得其方法都是如此。不过在查阅文档时就很重要了!声明在NSString.h、NSPathUtilities.h与NSURL.h中的NSString方法文档都集中在NSString类文档页面中。不过,声明在NSStringDrawing.h中的NSString方法却不是这样的;相反,它们位于单独的文档NSString UIKit Additions Reference中(推测一下,这是因为它们来自于不同的框架)。这样,字符串绘制方法就会难以找到,特别是NSString类文档页面没有指向其他文档的链接。我认为这是Cocoa文档结构的一个败笔。