扩展:
扩展是将你自己的代码插入到已经存在的对象类型中的方法,即extending an existing object type。这种扩展可以在自己定义的类型中,也可以是Swift本身的对象类型,还可以是Cocoa的对象类型。总之,扩展可以将你的代码打入你的甚至别人的类型中!
扩展的声明必须是在文件的最顶端。声明扩展:关键字extension放在已经存在的对象类型名之前,之后如果要加协议,那么就添加冒号和要添加的协议,之后再加大括号,里面写内容。此处的限制如下:
1、扩展不能重写已经存在的成员。(但是它可以重载已经存在的方法)
2、扩展不能声明存储属性。(但是它可以声明计算属性)
3、类的扩展不能声明指定构造器和析构器。(但是它可以声明便利构造器)
扩展对象类型:
在真实的编程过程中,为了封装一些不存在的函数(通过将它表达为属性或者方法),我有时会扩展Swift内部类型或者Cocoa类型。这是一些真实app的例子:
在某个纸牌游戏中,我需要进行洗牌,而牌储存在数组里面。那么我就扩展了Swift的Array,给它一个shuffle方法:
再如Cocoa的Core Graphics 框架有很多很有用的函数与CGRect结构体有关,Swift对它有诸多扩展,不过对于取得CGRect的中心坐标(Center Point : CGPoint),还没有捷径可走。所以我就扩展出了center属性:
扩展可以定义static和class方法。由于对象类型常常是全局可用的,所以这不失为是构造一个全局函数的好办法。比如,如果觉得金色会在开发中常常使用到,就没有必要每一次都重复定义,可以将它封装在全局函数中。然而也不需要直接构造一个全局函数,我们可以将他封装在UIColor中:
现在你就可以像调用UIColor.redcolor一样,使用UIColor.myGoldenColor来调用颜色了。
另一个扩展很好用的地方就是使Cocoa内置类和你自己的数据类型一起工作。比如在Zotz App中,我定义了一个枚举类型,用来读档存档。
其中的问题就是,让我存档时我需要每次调用rawValue。
这看起来很丑,所以必须要改。我们可以教会NSCoder(coder的类)怎样去干活儿。在这个扩展中,我重载了encoderObject:forKey" 方法:
事实上,我将rawValue的调用从我自己的代码中放进了NSCoder里。现在我就可以这样调用了:
扩展还可以整理对象类型的代码。一个常见的应用就是为对象类型增加其采用的协议。比如这样:
如果你觉得多行定义你的对象类型太复杂,那么用扩展可以很好地分解代码。
当你对Swift的结构体(Struct)进行扩展的时候,一个有趣的事情发生了:你声明一个构造器,原先的隐式构造器(Implicit initializer)可以保留下来:
这段代码意味着你可以通过显式构造器Digit(),也可以用原先的隐式构造器Digit(number:)。这样即使我们显式定义了构造器,也不会使原先的隐式构造器消失。
扩展协议(Extending Protocols):
在Swift2.0中,你可以扩展协议了!
像扩展其他的对象类型一样,你也可以扩展协议的方法和属性。不像协议的声明中方法和属性仅仅是要求采用者实现,扩展协议会是采用者继承该方法和协议,也就是说它们是真正的方法和属性。
看上面,Bird可以不用实现fly()就继承Flier。这是因为Flier协议的扩展提供了该方法,Bird实际上继承了fly():
采用者可以实现从扩展协议继承来的方法,再重写它:
但是请注意:这种继承不是多态的。采用者的实现不是重写;而仅仅是另一个实现。内部识别规则不适用,关键是引用。
即使f在内部其实是Insect(用is运算符可以知道),fly消息还是会发送给Flier。
为了将它变得看起来像多态继承,我们必须将fly在原协议中定义:
现在Insect就保持了它的内部完整:
这种差异是有意义的。因为协议采用不会引入动态分配。因此编译器必须进行静态决定。如果在原协议中,要求方法被声明,那么采用者就必须实现它,所以我们就可以调用该方法。但是如果该方法仅仅存在于协议扩展,这就取决于运行的动态分配(Dynamic Dispatch)。这就有可能使得协议落空,所以消息就被发送到协议扩展去了。
协议扩展的主要好处就是允许代码被转移至一个合适的Scope中。这里有一个例子:我有四个枚举类型,每一个都代表了Card的一个属性,Fill、Color、Shape和Number。他们都有一个人int类型的初始值。我对与每次都赋给rawValue有点烦躁了,所以就给每个枚举类型一个没有外部参数名的代理构造器,它们会调用内置的init(rawValue:)构造器:
尽管我不喜欢这样的方式,但是在Swift1.2之前我没有任何办法。而现在,我可以用协议扩展来改变这种情况。带有初始值的枚举类型将会自动采用内置的协议 Rawpresentable,初始值的类型别名叫做RawValue。所以我可以将我的构造器加入到此协议中:
在Swift标准库中,协议扩展意味着很多全局函数可以被改写为方法。比如,在Swift1.2之前,enumerate是一个全局函数:
原先这个函数只能是全局函数。
这个函数只作用于序列(sequence),所以也是SequenceType协议的采用者。在Swift2.0之前,怎样实现呢?enumerate可能在SequenceType协议中被定义(requirement),但是这意味着所有的采用者都要实现它,这显然不可能。所以我们只能把它声明为一个全局函数,然后用泛型约束保证入口,用序列作为参数。
在Swift2.0,enumerate就可以当做方法定义在SequenceType协议中了:
现在就不需要什么泛型约束了,也不需要泛型,甚至不需要参数。被发送了enumerate消息的序列就是要飙车enumerate的序列。
这个例子的方法可以被广泛使用。很多Swift标准库的全局函数变成了方法。
扩展泛型:
当你扩展泛型类型的时候,占位符类型名对于你的扩展声明是可见的。你很可能用到它们。不过这会使你的代码变得不清楚,因为看起来你在使用没有被定义过的变量。所以最好加点注释。
在Swift2.0中,泛型类型扩展可以包含where语句了。这和泛型约束的效果类似:它限制了决定泛型类型的代码,向编译器保证你的代码是合法的。
通过协议扩展,这意味着全局函数可以用作方法。看下面的这个例子:
为什么我要用全局函数呢?因为在Swift2.0之前我不得不这么做。如果我想把它(求最小值)变成Array的一个方法,在Swift1.2之前,我可以扩展Array而且也可以引用Array的泛型占位符,然而它不能进一步限制占位符。所以这里没有办法将此方法插入Array并保证该占位符代表的类型是 Comparable,所以编译器也不允许使用<比较Array的元素。而在Swift2.0就大大不同了。不仅可以更进一步地约束泛型类型,而且还可以将它变成Array的方法。
这个方法只能被元素是Comparable的序列调用,否则编译器将拒绝它们。
第二行不编译的原因就是因为我没有使Digit结构体采用Comparable。
同样这个改变也使得Swift的标准库有大规模的改变,允许把全局函数写入结构体扩展或者协议扩展作为方法。比如,全局函数find(在Swift1.2之前)变成了集合类型的indexOf方法,此方法的元素必须是Equatable。
这就是协议扩展,同样也是运用where语句的泛型扩展,二者在Swift2.0之前都是不被允许的。