继续Swift的进阶学习,这一阶段使用的是王巍(onevcat) 的Swifter一书,书中记录了100个Swift开发的Tips,本章包含了Tip1~Tip20。
1.柯里化(Currying) 
Currying我们在之前的Swift by Tutorials中也学习过,是一种可以使函数分布调用的方式,也可以充当产生新方法的工厂方法,例如:
1 
2 
3 
4 
5 
6 
7 
8 
func  greaterThan ( comparor:  Int )( Input:  Int )  ->  Bool  { 
    return  Input  >  comparor 
 } 
 let  greaterThan10  =  greaterThan ( 10 ) 
 greaterThan10 ( Input:  12 ) 
greaterThan10 ( Input:  1 ) 
 
作者举了一个实践的例子,我们之前也讲过Swift中的Selector只能通过String类型来赋值,从实用角度讲,是很好的与OC的Target/Action做了桥接,但是任然无法保证调用的安全性,用例就是借助Currying对selector调用做了改造,这篇博客 做了详细说明。
首先通过以下的例子,我们可以得知,一个类的方法也是可以被获取的,这因为Swift中方法是第一类对象,例如最后depositor的调用,它的类型就是BankAccount –> (Double) –> () ,其实也是currying调用,利用这一特性,博主自己对selector的调用进行了改造。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
class  BankAccount  { 
    var  balance  =  0.0 
 
     func  deposit ( amount:  Double )  { 
         balance  +=  amount 
     } 
 } 
 let  account  =  BankAccount () 
account . deposit ( 100 ) 
print ( account . balance ) 
 let  depositor  =  BankAccount . deposit 
depositor ( account )( 100 ) 
print ( account . balance ) 
 
这样改变了selector的调用方式,现在开发者需要传入target和一个(T) –> () –> ()类型的action,而不采用之前的字符串输入,这当然保证了该方法一定会被响应,因为如果,该类型没有这个方法,在编译期间就会报错,而不会是在runtime中。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
protocol  TargetAction  { 
    func  performAction () 
 } 
 struct  TargetActionWrapper < T:  AnyObject >:  TargetAction  { 
    weak  var  target:  T ? 
     let  action:  ( T )  ->  ()  ->  () 
 
     func  performAction ()  ->  ()  { 
         if  let  t  =  target  { 
             action ( t )() 
         } 
     } 
 } 
 enum  ControlEvent  { 
    case  TouchUpInside 
     case  ValueChanged 
     //... 
 } 
 class  Control  { 
    var  actions  =  [ ControlEvent:  TargetAction ]() 
 
     func  setTarget < T:  AnyObject > ( target:  T ,  action:  ( T )  ->  ()  ->  (),  controlEvent:  ControlEvent )  { 
         actions [ controlEvent ]  =  TargetActionWrapper ( target:  target ,  action:  action ) 
     } 
 
     func  removeTargetForCotrolEvent ( controlEvent:  ControlEvent )  { 
         actions [ controlEvent ]  =  nil 
     } 
 
     func  performActionForControlEvent ( controlEvent:  ControlEvent )  { 
         actions [ controlEvent ] ? . performAction () 
     } 
 } 
 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
class  MyViewController  { 
    let  button  =  Control () 
 
     func  viewDidLoad ()  { 
         button . setTarget ( self ,  action:  MyViewController . onButtonTap ,  controlEvent:  . TouchUpInside ) 
     } 
 
     func  onButtonTap ()  { 
         println ( "Button was tapped" ) 
     } 
 } 
 
2.Struct Mutable的方法 
这个问题,之前的Swift by Tutorials 也提到过,结构体的实例方法是不能修改自己声明的变量的,因为Struct是数值型的,默认是不可变的,所以如果某个方法需要修改结构体的变量,就需要添加mutating 关键字,使得编译器可以对结构体进行copy-on-write操作。
1 
2 
3 
4 
5 
6 
7 
8 
struct  User  { 
    var  age:  Int 
     var  weight:  Int 
     var  height:  Int 
     mutating  func  gainWeight ( newWeight:  Int )  { 
         weight  +=  newWeight 
     } 
 } 
 
3.将protocol的方法声明为mutating 
这个问题是承接上一个问题的,Swift中的protocol是可以被class、struct、enum实现的,所以如果protocol中一些方法正好会修改到自身变量,但没有加mutating 关键字的话,struct和enum实现该方法时就无法编过,所以一定要在设计protocol时考虑这个问题。另外,我补充一点,如果这个protocol只是给class实现的,那么可以不加mutating,同时也可以使用protocol ClassOnlyProtocol: class 这样的写法,告诉使用者,该协议只能实现于class。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
import  UIKit 
 protocol  Vehicle  { 
    var  numberOfWheels:  Int  { get } 
     var  color:  UIColor  { get  set } 
 
     mutating  func  changeColor () 
 } 
 struct  MyCar:  Vehicle  { 
    let  numberOfWheels  =  4 
     var  color  =  UIColor . blueColor () 
 
     mutating  func  changeColor ()  { 
         color  =  UIColor . redColor () 
     } 
 } 
 
4.Sequence 
在Swift中,只要实现了SequenceType,就可以使用for…in快速遍历,而需要先实现一个GeneratorType来控制遍历的顺序。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
class  ReverseGenerator:  GeneratorType  { 
    typealias  Element  =  Int 
 
     var  counter:  Element 
     init < T > ( array:  [ T ])  { 
         self . counter  =  array . count  -  1 
     } 
 
     init ( start:  Int )  { 
         self . counter  =  start 
     } 
 
     func  next ()  ->  Element ?  { 
         return  self . counter  <  0  ?  nil  :  counter -- 
     } 
 } 
 struct  ReverseSequence < T >:  SequenceType  { 
    var  array:  [ T ] 
 
     init  ( array:  [ T ])  { 
         self . array  =  array 
     } 
 
     typealias  Generator  =  ReverseGenerator 
 
     func  generate ()  ->  Generator  { 
         return  ReverseGenerator ( array:  array ) 
     } 
 } 
 let  arr  =  [ 0 ,  1 ,  2 ,  3 ,  4 ] 
 for  i  in  ReverseSequence ( array:  arr )  { 
    print ( "Index \(i) is \(arr[i])" ) 
 } 
 
for…in如何利用这两个协议,作者给出了一个解释。
1 
2 
3 
4 
var  g  =  array . generate () 
while  let  obj  =  g . next ()  { 
  print ( obj ) 
 } 
 
而且你也可以免费使用map,filter,reduce这些函数,因为SequenceType的几个extension实现了他们。关于这几个函数的使用,我们在Swift by Tutorials做了很多介绍。
5.多元组(Tuple) 
Tuple我们之前也介绍过,主要用于简单场景的复合数据类型,作者举了一个CGRect的方法在OC和Swift中的实践。
CGRect有个函数CGRectDivide() ,用来切割矩形区域,由于会返回两个新的CGRect,而OC又没有返回多个结果的机制,只能先声明两个CGRect,然后将指针传入函数,等待函数对其赋值。
1 
2 
3 
4 
5 
//CGRectDivide(CGRect rect, CGRect *slice, CGRect *remainder, CGFloat amount, CGRectEdge edge) 
CGRect  rect  =  CGRectMake ( 0 ,  0 ,  100 ,  100 ); 
CGRect  small ; 
CGRect  large ; 
CGRectDivide ( rect ,  & small ,  & large ,  20 ,  CGRectMinXEdge ); 
 
而在Swift中由于有了元组,可以优雅的进行实现,同时,由于Swift中结构体可以添加方法,所以将原来的函数直接作为了CGRect的一个方法,使得实现更加简洁。
1 
2 
3 
//func rectsByDividing(atDistance: CGFloat, fromEdge: CGRectEdge) -> (slice: CGRect, remainder: CGRect) 
let  rect  =  CGRectMake ( 0 ,  0 ,  100 ,  100 ) 
let  ( small ,  large )  =  rect . rectsByDividing ( 20 ,  fromEdge:  . MinXEdge ) 
 
6.@autoclosure和?? 
@autoclosure是简化代码的一个关键字,之前说过方法是Swift中的第一类对象,所以会在Swift中出现大量的匿名方法,即闭包closure作为参数,如下例。
1 
2 
3 
4 
5 
6 
func  logIfTrue ( @ autoclosure  predicate: ()  ->  Bool )  { 
    if  predicate ()  { 
         print ( "True" ) 
     } 
 } 
logIfTrue ({ return  2 > 1 }) 
 
当然按照之前介绍的简化策略,上面的调用可以简化为:
 
然而我们可以在方法定义时加上@autoclosure,这样调用时,Swift会自动将输入的参数转化为closure,但需要注意的是使用@autoclosure关键字时,闭包的参数只能是(),如果包含参数时,不能使用该关键字。
1 
2 
3 
4 
5 
6 
func  logIfTrue ( @ autoclosure  predicate: ()  ->  Bool )  { 
    if  predicate ()  { 
         print ( "True" ) 
     } 
 } 
logIfTrue ( 2 > 1 ) 
 
??可以用来对Optional类型进行空值判断,并赋默认值,用法如下:
1 
2 
3 
var  level:  Int ? 
var  startLevel  =  1 
var  currentLevel  =  level  ??  startLevel 
 
??的具体实现如下,两个版本的区别是默认值是否是optional类型,但是需要注意的是输入的第二个参数被封装成了一个闭包的类型,作者猜测了??方法的具体实现。
1 
2 
3 
4 
@ warn_unused_result 
public  func  ??< T > ( optional:  T ? ,  @ autoclosure  defaultValue:  ()  throws  ->  T ? )  rethrows  ->  T ? 
@ warn_unused_result 
public  func  ??< T > ( optional:  T ? ,  @ autoclosure  defaultValue:  ()  throws  ->  T )  rethrows  ->  T 
 
1 
2 
3 
4 
5 
6 
7 
8 
func  ??< T > ( optional:  T ? ,  @ autoclosure  defaultValue:  ()  ->  T )  ->  T  { 
    switch  optional  { 
         case  . Some ( let  value ) : 
             return  value 
         case  . None: 
             return  defaultValue () 
     } 
 } 
 
这里没有直接用T类型作为默认值的参数,而是封转了一个方法,这是一种优化手段。这样,系统在开始判断optional是否有值之前就不需要开辟默认值的内存,只有在optional为nil时,才去准备默认值。但是在开发者调用??方法时又不需要输入一个闭包参数,而仍是一个数值,这就是巧妙的利用了@autoclosure关键字。
作者留出了关于Swift的||和&&的实现,其实他们的情况也和??一样,需要分布判断,下面是我给出的答案。
1 
2 
3 
4 
5 
6 
7 
8 
func  ||< T  :  BooleanType ,  U  :  BooleanType > ( lhs:  T ,  @ autoclosure  rhs:  ()  ->  U )  ->  Bool  { 
    switch  lhs . boolValue  { 
     case  true: 
         return  true 
     case  false: 
         return  rhs (). boolValue 
     } 
 } 
 
1 
2 
3 
4 
5 
6 
7 
8 
func  &&< T  :  BooleanType ,  U  :  BooleanType > ( lhs:  T ,  @ autoclosure  rhs:  ()  ->  U )  ->  Bool  { 
    switch  lhs . boolValue  { 
     case  false: 
         return  false 
     case  true: 
         return  rhs (). boolValue 
     } 
 } 
 
7.Optional Chaining 
Optional Chaining在之前也介绍过,可以摆脱Swift中不必要的有值判断,但使用时也需注意其返回值和执行方法的不确定性。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
class  Toy  { 
    let  name:  String 
     init ( name:  String )  { 
         self . name  =  name 
     } 
 } 
 class  Pet  { 
    var  toy:  Toy ? 
 } 
 class  Child  { 
    var  pet:  Pet ? 
 } 
 let  xiaoming  =  Child () 
let  toyName  =  xiaoming . pet ? . toy ? . name 
 
如上,虽然name定义为toy的String类型,非Optional,但由于Optional Chaining的使用,是可能提前返回nil的,所以最终toyName的类型,还是String?。
实际开发中,一般还是用Optional Binding+Optional Chaining的方式来取值。
1 
2 
3 
if  let  toyname  =  xiaoming . pet ? . toy ? . name  { 
    print ( toyname ) 
 } 
 
如果,我们给Toy再加一个扩展:
1 
2 
3 
4 
5 
extension  Toy  { 
    func  play ()  { 
         //... 
     } 
 } 
 
如果我们想将play()封装成一个以Child为参数的closure,那么就要注意closure的返回类型了。以下列出了错误和正确的写法。
1 
2 
3 
4 
//wrong 
//let playClosure = {(child: Child) -> () in child.pet?.toy?.play()} 
//right 
let  playClosure  =  {( child:  Child )  ->  () ?  in  child . pet ? . toy ? . play ()} 
 
由于toy之前的值的不确定性,所以play()不一定可以正常执行,所以应该是()?类型,或者Void?类型,最后列出我们实际使用的场景,依然使用Optional Binding。
1 
2 
3 
if  let  result:  ()  =  playClosure ( xiaoming )  { 
    //... 
 } 
 
8.操作符 
Swift与OC的一大区别是支持操作符的重载,比如我们需要定义以下结构体的相加运算,我们可以重载加号操作符。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
struct  Vector2D  { 
    var  x  =  0.0 
     var  y  =  0.0 
 } 
 func  + ( left:  Vector2D ,  right:  Vector2D )  ->  Vector2D  { 
    return  Vector2D ( x:  left . x + right . x ,  y:  left . y + right . y ) 
 } 
 let  v1  =  Vector2D ( x:  2.0 ,  y:  3.0 ) 
let  v2  =  Vector2D ( x:  1.0 ,  y:  4.0 ) 
let  v3  =  v1 + v2 
print ( v3 ) 
 
类似的,我们也可以重载减号(-),负号(-)这样的运算符。
但是如果我们要定义一个全新的运算符,比如点积运算,单纯重载是无效的,因为该方法并不存在于Swift之中,需要我们先声明,如下:
1 
2 
3 
4 
infix  operator  +*  { 
    associativity  none 
     precedence  160 
 } 
 
infix表示这是一个中位操作符,即前后都是输入,其他修饰子还包括prefix(一个输入,并操作符作用于输入之前,类似++i,!i)和postfix(一个输入,并操作符作用于输入之后,类似i++,i—)。
associativity定义的事结合律,就是计算顺序,比如常见的加法和减法就是left,我们这里定义的点积的结果是Double,不会再和其他点积结合,所以就是none。
precedence是运算的优先级,越高的优先级优先进行运算,Swift中乘除法是150,加减法是140,这里的点积是160,是优先于乘除法的。
之后就可以定义点积运算了。
1 
2 
3 
4 
5 
func  +*  ( left:  Vector2D ,  right:  Vector2D )  ->  Double  { 
    return  left . x * right . x + left . y * right . y 
 } 
 let  result  =  v1  +*  v2 
 
需要注意的是,重载的操作符都是函数,所以是全局的,也就是说多次重载会导致冲突,尤其是多个module同时重载时。所以应该尽量将其作为其他某个方法的简便写法,避免实现大量逻辑或提供独一无二的功能,因为这样即使出现冲突,使用者还可以通过方法名调用的方式来使用,总之,重载操作符的特性不要滥用,除非确实存在大量并全局的需求。
9.func的参数修饰 
这一问题在之前也说过,一般我们声明Swift函数时都不会添加参数修饰符,如:
1 
2 
3 
func  incrementor ( variable:  Int )  ->  Int  { 
    return  variable  +  1 
 } 
 
而很多人可能不会这么写,因为有++这样的运算符,一般都会这么写:
1 
2 
3 
func  incrementor ( variable:  Int )  ->  Int  { 
    return  ++ variable 
 } 
 
然而直接报错,原因是Swift默认参数是let型的,所以++对自己会重新赋值,所以会报错,修改也很简单。修改参数为var就好。
1 
2 
3 
4 
5 
6 
7 
8 
func  incrementor ( var  variable:  Int )  ->  Int  { 
    return  ++ variable 
 } 
 var  putIn  =  3 
let  resultOut  =  incrementor ( putIn ) 
print ( resultOut ) 
print ( putIn ) 
 
但在我们使用时会发现,返回值没错,但是作为输入的putIn并没有改变,这一原因是即使我们将参数声明为var,但Swift在使用之前还是会将其copy一份,然后对其copy修改,所以实际输入的变量并不会改变。
如果我们想对输入参数在方法内修改,就要使用inout关键字,这样我们就将参数的指针输入了方法,所以在调用时也需要传入参数的指针,其实这与OC中的block使用外部变量是一样的,默认就是值拷贝,无法修改原值,如需修改,在原值加_block关键字。
1 
2 
3 
4 
5 
6 
7 
func  incrementor ( inout  variable:  Int )  { 
    ++ variable 
 } 
 var  putIn  =  3 
incrementor ( & putIn ) 
print ( putIn ) 
 
作者补充的一点是,参数修饰符是有传递限制性的,入股统一参数要跨层调用,需要保证同一参数的修饰统一,如下例:
1 
2 
3 
4 
5 
6 
func  makeIncrementor ( addNumber:  Int )  ->  (( inout  variable:  Int )  ->  ())  { 
    func  incrementor ( inout  variable:  Int )  ->  ()  { 
         variable  +  addNumber 
     } 
     return  incrementor 
 } 
 
10.字面量转换 
字面量就是指特定的数字,字符串或布尔值这样可以直接表明类型,并直接赋值的值。如下:
1 
2 
3 
4 
5 
let  aNumber  =  3 
let  aString  =  "Hello" 
let  aBool  =  true 
let  anArray  =  [ 1 ,  2 ,  3 ] 
let  aDictionary  =  [ "key1" :  "value1" ,  "key2" :  "value2" ] 
 
Swift提供了一组协议,用来将字面量转换为特定类型,对于实现了字面量转换接口的类型,在字面量赋值时,可以直接通过赋值的方式,将其转化为对应类型。这些协议有:
ArrayLiteralConvertible 
BooleanLiteralConvertible 
DictionaryLiteralConvertible 
FloatLiteralConvertible 
NilLiteralConvertible 
IntegerLiteralConvertible 
StringLiteralConvertible 
 
下面举例BooleanLiteralConvertible的实现,BooleanLiteralConvertible的定义是:
1 
2 
3 
4 
5 
protocol  BooleanLiteralConvertible  { 
  typealias  BooleanLiteralConvertible 
   
   init ( booleanLiteral  value:  BooleanLiteralType ) 
 } 
 
BooleanLiteralConvertible在Swift中已有定义:
1 
typealias  BooleanLiteralType  =  Bool 
 
那么我们自己实现字面量转换时,就需要实现init方法就好了,这样我们就可以直接使用true和false对MyBool类型赋值:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
enum  MyBool:  Int  { 
    case  myTrue ,  myFalse 
 } 
 extension  MyBool:  BooleanLiteralConvertible  { 
    init ( booleanLiteral  value:  Self . BooleanLiteralType )  { 
         self  =  value  ?  myTrue  :  myFalse 
     } 
 } 
 let  myTrue:  MyBool  =  true 
let  myFalse:  MyBool  =  false 
 myTrue . rawValue 
myFalse . rawValue 
 
BooleanLiteralConvertible是这些协议中最简单的了,如果是StringLiteralConvertible协议,还要实现另外两个方法,这两个方法我们一般开发中不会遇到,是对应的字符簇和字符 的字面量转换。
ExtendedGraphemeClusterLiteralConvertible 
UnicodeScalarLiteralConvertible 
 
继续举例比如,我们声明一个Person类,想通过String赋值,来生成Person对象,那么可以这样实现。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
class  Person:  StringLiteralConvertible  { 
    let  name:  String 
     init ( name  value:  String )  { 
         self . name  =  value 
     } 
 
     required  init ( stringLiteral  value:  String )  { 
         self . name  =  value 
     } 
 
     required  init ( extendedGraphemeClusterLiteral  value:  String )  { 
         self . name  =  value 
     } 
 
     required  init ( unicodeScalarLiteral  value:  String )  { 
         self . name  =  value 
     } 
 } 
 
下面三个init方法都加上了required 关键字,这是由初始化方法的完备性需求所决定的,该类的子类都需要保证能实现类似的字面量转换,以确保类型安全。
上面多次调用了self.name = value,其实是不必要的,我们可以在之后的每个init中调用最初的init方法,当然这需要在其他init方法前加convenience 关键字。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
class  Person:  StringLiteralConvertible  { 
    let  name:  String 
     init ( name  value:  String )  { 
         self . name  =  value 
     } 
 
     required  convenience  init ( stringLiteral  value:  String )  { 
         self . init ( name:  value ) 
     } 
 
     required  convenience  init ( extendedGraphemeClusterLiteral  value:  String )  { 
         self . init ( name:  value ) 
     } 
 
     required  convenience  init ( unicodeScalarLiteral  value:  String )  { 
         self . init ( name:  value ) 
     } 
 } 
 let  p:  Person  =  "xiaoming" 
print ( p . name ) 
 
Person例子中,没有像MyBool一样,使用extension来扩展,因为在extension中,我们无法定义required的初始化方法,也就是说我们无法为非final的class添加字面量转换。
综上,字面量转换是一个强大的功能,使用得当对代码简洁度有很大提高,但是对于使用者来讲,如果没有详细的文档支持,会造成很多困惑,所以它的最佳实践还有待开发者们继续挖掘。
11.下标 
Swift中的Array和Dictionary是支持下标读写的,这一点与新版OC是一致的。
1 
2 
3 
4 
5 
6 
7 
var  arr  =  [ 1 ,  2 ,  3 ] 
arr [ 2 ] 
arr [ 2 ]  =  4 
 var  dict  =  [ "cat" : "meow" ,  "goat" : "mie" ] 
dict [ "cat" ] 
dict [ "cat" ]  =  "miao" 
 
而Swift比较先进的是,Swift是允许自定义下标的,比如作者给出的,使用subscript关键字为Array添加支持以[Int]为index来读取的extension。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
extension  Array  { 
    subscript ( input:  [ Int ])  ->  ArraySlice < Element >  { 
         get  { 
             var  result  =  ArraySlice < Element > () 
             for  i  in  input  { 
                 assert ( i  <  self . count ,  "index out of range" ) 
                 result . append ( self [ i ]) 
             } 
             return  result 
         } 
         set  { 
             for  ( index ,  i )  in  input . enumerate ()  { 
                 assert ( i  <  self . count ,  "index out of range" ) 
                 self [ i ]  =  newValue [ index ] 
             } 
         } 
     } 
 } 
 var  arr  =  [ 1 ,  2 ,  3 ,  4 ,  5 ] 
arr [[ 0 ,  2 ,  3 ]] 
arr [[ 0 ,  2 ,  3 ]]  =  [ - 1 ,  - 3 ,  - 4 ] 
arr 
 
12.方法嵌套 
方法在Swift中成为了第一对象,可以作为参数,可以作为结果返回,而且可以在方法中定义子方法,这对内容过长的方法进行重构提供了新的思路。如果子方法只会被调用一次,那么它更适合被声明到父方法内部。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
func  appendQuery ( var  url:  String ,  key:  String ,  value:  AnyObject )  ->  String  { 
    func  appendQueryDictionary ( var  url:  String ,  key:  String ,  value:  [ String:  AnyObject ])  { 
         //... 
         return  result 
     } 
 
     func  appendQueryArray ( var  url:  String ,  key:  String ,  value:  [ AnyObject ])  { 
         //... 
         return  result 
     } 
 
     func  appendQuerySingle ( var  url:  String ,  key:  String ,  value:  AnyObject )  { 
         //... 
         return  result 
     } 
 
     if  let  dictionary  =  value  as ?  [ String:  AnyObject ]  { 
         return  appendQueryDictionary ( url ,  key:  key ,  value:  dictionary ) 
     }  else  if  let  array  =  value  as ?  [ AnyObject ]  { 
         return  appendQueryArray ( url ,  key:  key ,  value:  array ) 
     }  else  { 
         return  appendQuerySingle ( url ,  key:  key ,  value:  value ) 
     } 
 } 
 
还有在方法模板中(就是在Swift By Tutorials中提到的Partial application ),开发者一方面希望提供一个模板来让使用者定制方法,而同时不希望暴露太多的的实现细节。
1 
2 
3 
4 
5 
6 
func  makeIncrementor ( addNumber:  Int )  ->  (( inout  Int )  ->  Void  { 
    func  incrementor ( inout  variable:  Int )  ->  Void  { 
         variable  +=  addNumber ; 
     } 
     return  incrementor ; 
 } 
 
13.命名空间 
OC是没有命名空间的,开发时只能通过自己添加前缀来区分,但是还是有可能出现类文件相同的情况,而另一种情况是,一些第三方库本身引用了其他第三方库,而开发者同时使用这两个库时,就会出现冲突。
Swift在一定程度上解决了这一问题,目前Swift支持基于module的命名空间,也就是说根据不同的Target,可以有相同的类型名称,而一个target中还是不能有相同的类型。比如:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
//MyClass.swift 
//该文件在app的主Target中 
class  MyClass  { 
    class  func  hello ()  { 
         print ( "hello from app" ) 
     } 
 } 
//MyClass.swift 
//该文件存在于NameSpaceFramework中 
public  class  MyClass  { 
    public  class  func  hello ()  { 
       print ( "hello from framework" ) 
     } 
 } 
 
使用时如果出现冲突,可以使用Target名称作为前缀,如:
1 
2 
MyClass . hello () 
NameSpaceFramework . MyClass . hello () 
 
另一种方法是通过类型嵌套来指定访问范围,将名字重复的类型定义到不同的struct中,从而达到不使用多个module,也可以重复定义类型。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
struct  MyClassContainer1  { 
       class  MyClass  { 
            class  func  hello ()  { 
                print ( "hello from MyClassContainer1" ) 
            } 
        } 
 } 
struct  MyClassContainer2  { 
       class  MyClass  { 
            class  func  hello ()  { 
                print ( "hello from MyClassContainer2" ) 
        } 
    } 
 } 
 
使用时,
1 
2 
MyClassContainer1 . MyClass . hello () 
MyClassContainer2 . MyClass . hello () 
 
14.Any和AnyObject 
Any和AnyObject是Swift的两个妥协的产物,因为OC是动态语言,不对类型进行严格的检查,所以会有id这种类型存在,并大量使用。而Swift是一种对类型严格检验的语言,只是为了与Cocoa协作,将原来的id用AnyObject来代替(实际上应该是AnyObject?类型,因为id可以为nil)。
AnyObject可以代表任何class类型的实例
Any可以代表任意类型,甚至包括方法(func)类型
 
但使用时,作者还是推荐我们对AnyObject?类型的实例,进行常规的拆解,再使用,确保安全性,例如:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
func  someMethod ()  ->  AnyObject  { 
    //... 
     return  result 
 } 
 let  anyObject  :  AnyObject ?  =  SomeClass . someMethod () 
if  let  someInstance  =  anyObject  as ?  SomeRealClass  { 
    //... 
     someInstance . funcOfSomeRealClass () 
 } 
 
实际上AnyObject是一个protocol,所有的class都隐式实现了这一接口,这也是AnyObject只适用于class的原因,而Swift中的Array和Dictionary是struct类型,是不能用AnyObject来表示的。所以产生了Any这个类型,Any可以表示所有类型,包括class、struct、enum等。
使用Swift原生类型是可以提高性能的,所以代码中大量出现AnyObject和Any时,有必要重新检查代码,因为Swift中强调类型安全,这意味着代码结构和设计出现了问题。
15.typealias和泛型接口 
typealias最常见的使用在于对于一些类型重新命名来增加可读性,如下例,使用typealias对CGPoint和Double重新命名后,使得代码更符合其代表的意义,如果在大型工程中对代码可读性的提高是很明显的。
1 
2 
3 
4 
5 
6 
7 
8 
func  distanceBetweenPoint ( point:  CGPoint ,  toPoint:  CGPoint )  ->  Double  { 
    let  dx  =  Double ( toPoint . x  -  point . x ) 
     let  dy  =  Double ( toPoint . y  -  point . y ) 
     return  sqrt ( dx  *  dx  +  dy  *  dy ) 
 } 
let  origin:  CGPoint  =  CGPoint ( x:  0 ,  y:  0 ) 
let  point:  CGPoint  =  CGPoint ( x:  1 ,  y:  1 ) 
let  distance:  Double  =   distanceBetweenPoint ( origin ,  toPoint:  point ) 
 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
typealias  Location  =  CGPoint 
typealias  Distance  =  Double 
func  distanceBetweenPoint ( location:  Location , 
    toLocation:  Location )  ->  Distance  { 
         let  dx  =  Distance ( location . x  -  toLocation . x ) 
         let  dy  =  Distance ( location . y  -  toLocation . y ) 
         return  sqrt ( dx  *  dx  +  dy  *  dy ) 
 } 
let  origin:  Location  =  Location ( x:  0 ,  y:  0 ) 
let  point:  Location  =  Location ( x:  1 ,  y:  1 ) 
let  distance:  Distance  =   distanceBetweenPoint ( origin ,  toLocation:  point ) 
 
需要注意的是,对于泛型使用typealias时,如果泛型类型的确定性没有保证时,是不可以使用的,如下:
1 
2 
3 
4 
5 
//错误的例子,不可使用typealias 
class  Person < T >  {} 
typealias  Worker  =  Person 
typealias  Worker  =  Person < T > 
typealias  Worker < T >  =  Person < T > 
 
1 
2 
3 
class  Person < T >  {} 
typealias  WorkId  =  String 
typealias  Worker  =  Person < WorkId > 
 
最后一点,Swift是没有泛型接口的,但使用typealias可以模拟一个泛型接口,在protocol中声明一个必须实现的typealias,要求遵从该protocol的class或struct实现该typealias,比如在GeneratorType和SequenceType中就使用了这一技巧。
1 
2 
3 
4 
5 
6 
7 
8 
9 
protocol  GeneratorType  { 
    typealias  Element 
     mutating  func  next ()  ->  Self . Element ? 
 } 
 protocol  SequenceType  { 
    typealias  Generator  :  GeneratorType 
     func  generate ()  ->  Self . Generator 
 } 
 
16.可变参数函数 
可变参数函数就是可以接受任意多个参数的函数,例如NSString的-stringWithFormat: 的方法。但是在OC中,自己实现一个可变参数函数是非常困难的。而在Swift中,这变得非常容易,如下声明一个累加的函数:
1 
2 
3 
4 
func  sum ( input:  Int ...)  ->  Int  { 
    return  input . reduce ( 0 ,  combine:  + ) 
 } 
print ( sum ( 1 ,  2 ,  3 ,  4 ,  5 )) 
 
这个结合了reduce方法,但是需要注意的是可变参数只能作为方法中最后一个参数,而且一个方法中最多只能有一组可变参数,同时可变参数必须是同一个类型的。如果想像-stringWithFormat: 这样传入不同类型的参数的话,就需要作出一些变化。
一种解决方法是使用Any来作为参数类型,然后对接收到的首个元素单独处理;另一个解决方法是利用使用_ 作为参数外部标签,调用时不再需要加上参数名字的特性,先指定一个参数作为字符串,然后跟一个匿名参数列表,这样看起来就像跟了一个参数列表,Swift中的-stringWithFormat: 实际上就是这样实现的。
1 
2 
3 
extension  NSString  { 
    public  convenience  init ( format:  NSString ,  _  args:  CVarArgType ...) 
 } 
 
17.初始化方法顺序 
与OC不同,Swift的初始化方法需要保证类型的所有属性都被初始化,在子类的初始化方法中,我们需要保证子类实例的成员初始化完成后,再调用父类的初始化方法。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
class  Cat  { 
    var  name:  String 
     init ()  { 
         name  =  "cat" 
     } 
 } 
 class  Tiger:  Cat  { 
    let  power:  Int 
     override  init ()  { 
         power  =  10 
         super . init () 
         name  =  "tiger" 
     } 
 } 
 
如果子类不需要对父类的成员变量作出修改,那么不存在第三步的情况下,super.init()也不需要调用,Swift在初始化最后会自动调用一次super.init()。
1 
2 
3 
4 
5 
6 
class  Lion:  Cat  { 
    let  power:  Int 
     override  init ()  { 
         power  =  10 
     } 
 } 
 
18.Designated,Convenience和Required 
Swift强调安全性,在初始化方法中也不例外。OC中的init方法是不安全的,init不能保证只被调一次,也没有保证在初始化方法中实现各个成员变量的初始化。
而在Swift中强化了designated初始化方法的地位,Swift中不加修饰的init方法中药保证所有的非Optional的成员变量被初始化,而且在子类的designated初始化中也要强制调用父类的designated初始化。所以在Swift中无论何种路径,被初始化对象是一定可以完整初始化的。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
class  ClassA  { 
    let  numA:  Int 
     init ( num:  Int )  { 
         numA  =  num 
     } 
 } 
 class  ClassB:  ClassA  { 
    let  numB:  Int 
 
     override  init ( num:  Int )  { 
         numB  =  num  +  1 
         super . init ( num:  num ) 
     } 
 } 
 
与designated初始化方法对应的是convenience初始化方法,作为补充初始化方法,所有的convenience初始化必须调用同一类中的designated初始化来完成。另外,convenience初始化时不能被子类重写或者从子类中以super的方式调用的。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
class  ClassA  { 
    let  numA:  Int 
     init ( num:  Int )  { 
         numA  =  num 
     } 
 
     convenience  init ( bigNum:  Bool )  { 
         self . init ( num:  bigNum  ?  10000  :  1 ) 
     } 
 } 
 class  ClassB:  ClassA  { 
    let  numB:  Int 
 
     override  init ( num:  Int )  { 
         numB  =  num  +  1 
         super . init ( num:  num ) 
     } 
 } 
 
只要在子类中重写了父类convenience方法所需的init方法,我们在子类中也可以使用父类的convenience的初始化方法了,比如:
1 
let  aClassBInstance  =  ClassB ( bigNum:  true ) 
 
作者提出两个原则:
初始化路径必须保证对象完全初始化,这可以通过调用本类型的designated初始化方法来得到保证; 
子类的designated初始化方法必须调用父类的designated方法,以保证父类也完成初始化。 
 
某些情况下我们希望子类一定实现designated初始化方法,我们可以添加required关键字来限制,强制子类对这个方法重写。这样的好处是,保证某些依赖designated初始化方法的convenience初始化方法始终可用,如下,将ClassA的designated方法设置为required时,ClassB始终可以成功调用init(bigNum: Bool)。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
class  ClassA  { 
    let  numA:  Int 
     required  init ( num:  Int )  { 
         numA  =  num 
     } 
 
     convenience  init ( bigNum:  Bool )  { 
         self . init ( num:  bigNum  ?  10000  :  1 ) 
     } 
 } 
 class  ClassB:  ClassA  { 
    let  numB:  Int 
 
     required  init ( num:  Int )  { 
         numB  =  num  +  1 
         super . init ( num:  num ) 
     } 
 } 
 
最后说明的一点是designated初始化方法也可以添加required,确保子类对其实现,这在子类必须重写该方法是很有用。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
class  ClassA  { 
    let  numA:  Int 
     required  init ( num:  Int )  { 
         numA  =  num 
     } 
 
     required  convenience  init ( bigNum:  Bool )  { 
         self . init ( num:  bigNum  ?  10000  :  1 ) 
     } 
 } 
 class  ClassB:  ClassA  { 
    let  numB:  Int 
 
     required  init ( num:  Int )  { 
         numB  =  num  +  1 
         super . init ( num:  num ) 
     } 
 
     required  convenience  init ( bigNum:  Bool )  { 
         self . init ( num:  bigNum  ?  20000  :  1 ) 
     } 
 } 
 
19.初始化返回nil 
在OC中init方法是可以返回nil的,用以表示该次初始化失败,但在Swift默认下是不能return的,所以也就没有机会返回一个Optional值,例如NSURL的初始化方法-initWithString:,如果输入为不规格的string,是会返回nil的。
1 
2 
3 
NSURL  * url  =  [[ NSURL  alloc ]  initWithString: @"ht tp://swifter tips" ]; 
NSLog ( @"%@" , url ); 
//输出(null) 
 
而在Swift中,-initWithString:对应的是一个convenience方法,convenience init?(string URLString: String)。为了解决init不能返回nil的问题,Swift1.1之后Apple添加了可以返回nil的能力,需要在init声明后加上一个?或者!,
1 
2 
3 
4 
5 
public  convenience  init ? ( string  URLString:  String ) 
 let  url  =  NSURL ( string:  "ht tp://swifter tips" ) 
print ( url ) 
//输出(null) 
 
20.protocol组合 
Swift中以Any来表示任意类型,而Any在Swift中其实是protocol<>的一个同名类型,其实这就是protocol组合的应用。
标准的protocol组合的写法是:
1 
protocol < ProtocolA ,  ProtocolB ,  ProtocolC > 
 
这是一个同时需要实现这三个协议的一个新的匿名协议类型,等同于新申明一个协议类型。
1 
2 
 protocol  ProtocolD:  ProtocolA ,  ProtocolB ,  ProtocolC  { 
  } 
 
 
Any定义时的protocol<>类型是需要实现空协议的协议,也就是任意类型的意思。
protocol组合相比新建一个协议的最大区别在于其匿名性,可以使代码更简洁,例如:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
protocol  KittenLike  { 
    func  meow ()  ->  String 
 } 
 protocol  DogLike  { 
    func  bark ()  ->  String 
 } 
 protocol  TigerLike  { 
    func  aou ()  ->  String 
 } 
 class  MysteryAnimal:  KittenLike ,  DogLike ,  TigerLike  { 
    func  meow ()  ->  String  { 
         return  "meow" 
     } 
 
     func  bark ()  ->  String  { 
         return  "bark" 
     } 
 
     func  aou ()  ->  String  { 
         return  "aou" 
     } 
 } 
 
如果需要单独检查宠物类或者猫科类的叫声时,我们可能会选择新建两个新的协议。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
protocol  PetLike:  KittenLike ,  DogLike  { 
 } 
 protocol  CatLike:  KittenLike ,  TigerLike  { 
 } 
 struct  soundChecker  { 
    static  func  checkPetTalking ( pet:  PetLike )  { 
         //... 
     } 
 
     static  func  checkCatTalking ( cat:  CatLike )  { 
         //... 
     } 
 } 
 
我们可以使用typealias来重命名这两个协议,而避免引入新的类型。
1 
2 
typealias  PetLike  =  protocol < KittenLike ,  DogLike > 
typealias  CatLike  =  protocol < KittenLike ,  TigerLike > 
 
如果这两个协议只是临时使用一次,那么直接使用protocol组合也是可以的。
1 
2 
3 
4 
5 
6 
7 
8 
9 
struct  soundChecker  { 
    static  func  checkPetTalking ( pet:  protocol < KittenLike ,  DogLike > )  { 
         //... 
     } 
 
     static  func  checkCatTalking ( cat:  protocol < KittenLike ,  TigerLike > )  { 
         //... 
     } 
 } 
 
最后作者提供了一个解决,同时实现多个协议,且出现方法冲突的解决方法。就是在使用前先进行类型转化。
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
protocol  A  { 
    func  bar ()  ->  Int 
 } 
 protocol  B  { 
    func  bar ()  ->  String 
 } 
 class  Class:  A ,  B  { 
    func  bar ()  ->  Int  { 
         return  1 
     } 
 
     func  bar ()  ->  String  { 
         return  "Hi" 
     } 
 } 
 let  instance  =  Class () 
 let  num  =  ( instance  as  A ). bar () 
let  str  =  ( instance  as  B ). bar ()