1. 程式人生 > >Swift4 學習筆記——高階篇

Swift4 學習筆記——高階篇

示例程式碼來源於 《iOS 11 Programming Fundamentals with Swift》

變數

Define and Call函式初始化

使用Define and Call函式進行變數初始化,Define and Call函式內容在下節。

let timed : Bool = {    
    if val == 1 {        
        return true    
    } else {        
        return false    
    }
}()

懶惰初始化

使用lazy關鍵字可以讓變數在使用的時候再初始化。

  • Global的變數是自動懶惰初始化的,不需要lazy關鍵字。
  • Static的變數也是自動懶惰初始化的,不需要lazy關鍵字。
  • Property變數用關鍵字lazy進行懶惰初始化。
class MyView : UIView {
    lazy var arrow = self.arrowImage()
    func arrowImage () -> UIImage {
        // ... big image-generating code goes here ...
    }
}

如果沒有lazy關鍵字,是不能通過編譯的,因為MyView還沒有初始化,不能呼叫例項方法。

一個比較常用的技術手段是結合使用define and call的函式來初始化lazy的成員變數:

lazy var prog : UIProgressView = {    
    let p = UIProgressView(progressViewStyle: .default)    
    p.alpha = 0.7    
    p.trackTintColor = UIColor.clear    
    p.progressTintColor = UIColor.black    
    p.frame = CGRect(x:0, y:0, width:self.view
.bounds.size.width, height:20) p.progress = 1.0 return p }()
  • lazy的成員變數初始化不是執行緒安全的。
  • lazy的成員變數不能是隻讀的。
  • lazy的成員變數也不能加觀察者。

計算變數

無論是類成員還是全域性變數,都可以定義成計算變數,就是在使用的時候計算一個值出來,而不是儲存這個值。也就是getter和setter。

var now : String {     
    get {         
        return Date().description     
    }    
    set {         
        print(newValue) 
    }
}
  • 計算變數必須是var的,必須指定型別。
  • getter函式必須返回同類型的值。
  • setter函式中的newValue就是被賦予的值。也可以使用set(val)指定。
  • 如果沒有setter函式,變數是隻讀的。
  • 必須有getter
  • 沒有setter的時候可以簡寫
var now : String {    
    return Date().description

}

變數觀察者

和OC的KVO類似。

var s = "whatever" {     
    willSet {         
        print(newValue)     
    }    
    didSet {         
        print(oldValue)         
        // self.s = "something else"    
    }
}
  • 在變數被改變之前willSet會被呼叫,newValue表示新的值
  • 在變數被改變之後didSet會被呼叫,oldValue表示原來的值
  • 在初始化的時候和didSet中不會觸發willSet和didSet
  • 如果變數是計算變數(有setter)則不能用willSet和didSet,因為可以在setter中做這些事情。

函式

函式巢狀

Swift中可以在函式體中定義函式,如果一個函式B只被函式A使用,那可以將B函式定義在函式An內。

func checkPair(_ p1:Piece, and p2:Piece) -> Path? {
    // ...
    if arr.count > 0 {
        func distance(_ pt1:Point, _ pt2:Point) -> Double { 1
            // utility to learn physical distance between two points
            let deltax = pt1.0 - pt2.0
            let deltay = pt1.1 - pt2.1
            return Double(deltax * deltax + deltay * deltay).squareRoot()
        }
        for thisPath in arr {
            var thisLength = 0.0
            for ix in thisPath.indices.dropLast() {
                thisLength += distance(thisPath[ix],thisPath[ix+1]) 2
            }
            // ...
        }
    }
    // ...
}

函式作為引數和返回值

因為Swift中函式是物件,所以函式也是可以作為引數傳遞的。

func doThis(_ f:() -> ()) {
    f()
}

func whatToDo() { 
    print("I did it")
}

doThis(whatToDo)

同樣,函式可以作為函式的返回值。函式作為引數或者返回值的一個好處是可以增加間接性,使得在呼叫的時候不必知道函式的定義,只要知道函式的簽名就行。

一個比較實用的例子:

let size = CGSize(width:45, height:20)
UIGraphicsBeginImageContextWithOptions(size, false, 0) 
let p = UIBezierPath(
    roundedRect: CGRect(x:0, y:0, width:45, height:20), cornerRadius: 8)
p.stroke() 
let result = UIGraphicsGetImageFromCurrentImageContext()! 
UIGraphicsEndImageContext()

上邊這段程式碼是建立一個矩形的圖片。這段程式碼中和UIGraphXXX相關的部分是可以提取的,因為每次畫圖都一樣。定義一個函式:

func imageOfSize(_ size:CGSize, _ whatToDraw:() -> ()) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    whatToDraw()
    let result = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return result
}

上邊的函式就是純粹的圖片製作過程,將畫圖的部分“外包”了出去,呼叫者將畫圖部分的函式傳入即可。

使用程式碼:

func drawing() { 
    let p = UIBezierPath( roundedRect: CGRect(x:0, y:0, width:45, height:20), cornerRadius: 8) 
    p.stroke() 
} 

let image = imageOfSize(CGSize(width:45, height:20), drawing)

iOS 10已經有一個函式叫做UIGraphicsBeginImageContext和imageOfSize提供差不多的功能。

函式作為返回值可以引出一個模式,類似於微型的工廠模式:

func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage {
    func f () -> UIImage { 
        let im = imageOfSize(sz) { 
            let p = UIBezierPath(roundedRect: CGRect(origin:CGPoint.zero, size:sz),
            cornerRadius: 8) 

            p.stroke() 
        } 

        return im
    } 

    return f
}

let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20)) 
self.iv.image = maker()

上述程式碼返回一個函式,這個函式是一個ImageMaker,呼叫maker能生成一個Image,這個Image的大小是製作Maker時候決定的,也就是makeRoundedRectangleMaker的引數sz。

匿名函式

UIView.animate(withDuration:0.4,    
    animations: {        
        () -> () in        
        self.myButton.frame.origin.y += 20   
    },    
    completion: {        
        (finished:Bool) -> () in        
        print("finished: \(finished)")    

    })

匿名函式很像OC中的block,說明下上邊的程式碼。

UIView.animate(withDuration: animations: completion:)中的aimations引數是一個()->Void型別的函式,compeltion是一個(Bool)->Void型別的函式。使用者可以定義兩個這樣型別的函式然後傳入,也可以在呼叫的原位定義一個匿名函式。() -> () in 這行程式碼的意思是下邊的函式是一個() -> ()型別。匿名函式的引數和返回值寫在了大括號的第一行。

匿名函式有很多書寫的簡化規則,看到能讀懂就行,不必非得使用這些語法糖。

  • 如果返回值編譯器能夠知道,那麼就可以省略返回值,如果沒有引數也可以省略引數,如果返回值和引數都能省略,那麼in也可以省略,所以,上邊的animations引數第一行是可以省略的:
UIView.animate(withDuration:0.4, 
    animations: { 
        // * (no in line)        
        self.myButton.frame.origin.y += 20    

    }, completion: { 
        (finished:Bool) in        
        print("finished: \(finished)")

    })

同樣,在completion引數中省略的返回值。

  • 對於completion來說,引數型別也可以省略,因為引數型別在UIView.animate函式的引數中已經標明瞭,所以可以寫成:
UIView.animate(withDuration:0.4, 
    animations: { 
        // * (no in line)        
        self.myButton.frame.origin.y += 20    

    }, completion: { 
        finished in        
        print("finished: \(finished)")

    })
  • 更進一步,引數的名字也可以使用0,1…取代,這樣引數的名字也可以省略:
UIView.animate(withDuration:0.4, 
    animations: { 

        self.myButton.frame.origin.y += 20    

    }, completion: { 
        print("finished: \($0)")

    })
  • 還有一種常見的變體要認識:
UIView.animate(withDuration:0.4, 
    animations: { 

        self.myButton.frame.origin.y += 20    

    }) { 
        print("finished: \($0)")
    }

當匿名函式是函式的最後一個引數的時候,可以將匿名函式的大括號部分寫在函式呼叫之後。

有了匿名函式,上邊畫圖的程式碼可以寫在一起:

let image = imageOfSize(CGSize(width:45, height:20), { 
    () -> () in 
    let p = UIBezierPath( roundedRect: CGRect(x:0, y:0, width:45, height:20), cornerRadius: 8) 
    p.stroke() 

})

Define and Call

就是定義函式的同時呼叫函式,這種形式:

{
    // ... code goes here
}()

和匿名函式相似,不同的是,匿名函式提供的是一個過程,Define and Call提供的是一個過程的結果,例子:

content.addAttribute(
    .paragraphStyle,
    value: {
        let para = NSMutableParagraphStyle()
        para.headIndent = 10
        para.firstLineHeadIndent = 10
        // ... more configuration of para ...
        return para
    }(),
    range:NSRange(location:0, length:1))

其中value引數是通過一個Define and Call的函式算出的結果。這種一次性的函式適合使用Define and Call或者巢狀函式。有兩個好處,一個是可見的範圍縮小了,避免衝突和誤用,二是可以捕獲上下文的變數,不需要定義一系列引數傳遞。

函式裝飾器 & escaping

看一個函式:

func countAdder(_ f: @escaping () -> ()) -> () -> () { 
    var ct = 0 

    return {
        ct = ct + 1 
        print("count is \(ct)") 
        f()
    }
}

這個函式接受一個()->()函式,返回一個()->()函式。作用是呼叫傳入的函式f,並且記錄列印函式f呼叫的次數。

使用:

func greet () { 
    print("howdy") 
} 

let countedGreet = countAdder(greet) 

countedGreet() //count is 1, howdy
countedGreet() //count is 2, howdy
countedGreet() //count is 3, howdy

內部的匿名函式捕獲了臨時變數ct,並儲存下來,以後的每次呼叫都會增加ct,並維護這個結果。從結果上看countAdder給greet會這樣簽名的函式增加了方法,類似一個微型的裝飾器模型。

@escaping, @escaping的意思是這個函式引數在當前函式執行完成後還可能被呼叫,在上邊的例子中,f隨著匿名函式被返回,在後續的呼叫中執行。這種情況下,f引數需要被標記為@escaping,否則會有編譯錯誤。因為使用者傳入的引數f可能是一個閉包,可能捕獲了一些臨時變數,那麼標記為@escaping意思就是在函式執行完之後也要保留這些捕獲的變數。

Curried Functions

還使用畫圖的例子,上面的畫圖例子的圓角半徑為8是寫死的,如果不想寫死,程式碼應該這樣寫:

func makeRoundedRectangleMaker(_ sz:CGSize, _ r:CGFloat) -> () -> UIImage { 
    return { 
        imageOfSize(sz) { 
            let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz), 
                cornerRadius: r) 

            p.stroke()
        }
    }
}

let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20), 8)

這裡還有另外一種思路:

func makeRoundedRectangleMaker(_ sz:CGSize) -> (CGFloat) -> UIImage { 
    return { 
        r in
        imageOfSize(sz) { 
            let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero, size:sz), 
                cornerRadius: r) 

            p.stroke()
        }
    }
}

let maker = makeRoundedRectangleMaker(CGSize(width:45, height:20))
self.iv.image = maker(8)

這段程式碼的不同之處在於makeRoundedRectangleMaker接受的還是一個引數,但是返回的函式多了一個引數CGFloat。閉包內同樣也捕獲了返回函式的引數r,在使用的時候再提供r的值。這種方式就叫做Curried Function。

繼承 & 多型

  • Swift中只有Class可以繼承
  • Swift是單繼承的。
  • 沒有一個像OC中NSObject這樣的公共基類。
  • 可以將一個class標記為final,這樣這個class就不能被繼承。

繼承的寫法:

// Cat是Kitten的基類

class Dog {
    func barkAt(cat:Kitten) {}
}

class NoisyDog : Dog {
    override func barkAt(cat:Cat) {} // or Cat?
}
  • 子類重寫基類的方法需要使用override關鍵字。
  • override的方法的引數可以是基類的引數的基類,也可以是基類引數對應的可選型別。這一要求是保證裡式替換原則的。

子類的初始化

初始化函式的繼承

  • 如果子類沒有定義初始化函式,那麼所有的初始化函式繼承自基類。(子類可以沒有初始化函式的必要條件是使用基類的函式就能把所有的成員都初始化了)
  • 如果子類沒有designated initializer,可以定義自己的convenience initializer,繼承基類的designated initializer。
  • 如果子類定義了自己的designated initializer,那麼所有基類的designated initializer和convenience initializer都不會被繼承了。
  • 如果子類重寫了基類所有的designated initializer,那麼基類的convenience initializer又可以被子類繼承了。

上述規則可以這樣理解,如果基類的初始化函式能滿足子類的初始化需求,那麼子類可以不寫初始化函式,使用基類的。另外,子類也可以定義一些輔助函式(convenience initializer)來使用基類的designated initializer初始化。如果子類定義了自己的designated initializer 那麼很可能的情況是基類的designated initializer已經不能滿足子類的初始化需求了,那麼所有的基類的designated initializer就不能成為子類的初始化函數了。(但是在子類的designated initializer中必須呼叫基類的designated initializer)。因為基類的convenience initializer中肯定是呼叫了基類的designated initializer,所以子類如果重寫了所有的designated initializer,那麼基類的convenience initializer中的呼叫就變成了呼叫子類的designated initializer,所以又可以初始化子類了,所以就可以繼承了。

  • 重寫基類的convenience initializer,不需要override關鍵字,重寫基類的designated initializer需要使用override關鍵字。
  • 基類的初始化函式可以被重寫成failable的初始化函式(就是返回?型別的例項的初始化函式),但是反過來不行。

required 初始化函式

在基類中被標記為required的初始化函式,子類必須也有這個初始化函式。“必須有”的意思是:如果子類不能根據上述規則通過繼承擁有這個初始化函式,就必須重寫它。

這條規則的使用場景:

func dogMakerAndNamer(_ whattype:Dog.Type) -> Dog {
    let d = whattype.init(name:"Fido") // compile error
    return d
}

dogMakerAndNamer是一個工廠函式,根據傳入的dog的具體型別來建立一個dog出來,那麼如果子類沒有init(name:)這個初始化函式,這個工廠函式就行不通了。解決的辦法就是在基類Dog的init(name:)前邊加上required關鍵字,要求所有子類都實現這個初始化函式。

class Dog {
    var name : String
    required init(name:String) {
        self.name = name
    }
}

屬性和方法的重寫

屬性分為兩類,一類是普通變數屬性,一類是計算變數屬性。

class Dog {
    var name : String   //普通變數屬性
    var age : Float {   //計算變數屬性,只讀
        return 1 + 10
    }

    required init(name:String) {
        self.name = name
    }
}

子類可以使用計算變數重寫基類的普通變數屬性,但是不能改變訪問許可權

class NoisyDog : Dog {
    override var name: String{
        set { self.name = newValue } //如果註釋掉這一句,編譯錯誤
        get { return "dog" }
    }
}

子類可以重寫基類的計算變數的屬性,並且可以改變訪問許可權

class NoisyDog : Dog {
    override var age: Float{
        set { self.age = newValue } //可以新增setter
        get { return 20 }
    }
}

向下型別轉換

let d : Dog? = NoisyDog()
let d2 = d as? NoisyDog
d2?.beQuiet()

型別判斷

let d : Dog? = NoisyDog()

if d is NoisyDog {
    let d2 = d as! NoisyDog
    d2.beQuiet()
}

上述判斷和轉化程式碼可以合併成一句:

if let d2 = d as? NoisyDog{
    print(d2)
}

Protocol

Swift中一個類實現一個Protocol需要顯式宣告。Protocol可以被struct或者enum實現。

protocol Flier {
    func fly()
}
  • 一個類可以實現多個Protocol。
  • protocol中可以定義類屬性,使用static關鍵字。
  • 如果Protocol 中的一個方法可能會改變屬性,並且這個Protocol設計允許被struct或者enum實現,那麼這個方法應該使用mutating關鍵字。
  • Protocol相當於提供了另一個維度的繼承體系,所以型別轉換和繼承體系的語法相同。

protocol的組合

如果一個函式接受一個引數,要求這個引數實現兩個Protocol,可以使用Protocol的組合

func f(_ x: CustomStringConvertible & CustomDebugStringConvertible) {
}

protocol和class也可以組合

protocol MyViewProtocol : class {
    func doSomethingCool()
}

class ViewController: UIViewController {
    var v: (UIView & MyViewProtocol)?
    // ...
}

ViewController 的建構函式接受一個引數,要求這個引數是UIView型別,並且實現了MyViewProtocol協議。

protocol MyViewProtocol : class, class關鍵字表示只能被class型別的實現。

Optional Protocol Method

是為了和OC 的Protocol相容。必須標記為@objc,並且標記了@objc的protocol只能被class型別實現,不能被struct和enum實現。

@objc protocol Flier {
    @objc optional var song : String {get}
    @objc optional func sing()
}


class Bird : Flier {
    func sing() { 
        print("tweet")
    }
}

Bird只實現了一個sing方法。在這種情況下,Swift不知道song屬性是不是安全的。

let f : Flier = Bird()
let s = f.song // s is an Optional wrapping a String

s將是一個String?而不是String,如果協議中song本身就是String?那麼s將是一個String??。這一點對於方法的返回值也適用。

@objc protocol Flier { 
    @objc optional var song : String? {get} 
    @objc optional func sing() 
} 

let f : Flier = Bird() 
let s = f.song 
// s is an Optional wrapping an Optional wrapping a String

對於方法的呼叫需要先拆箱

let f : Flier = Bird() 
f.sing?()

Literal Convertibles

Swift中字面變數的轉換是靠實現了一個或者多個協議來實現的。

struct Nest : ExpressibleByIntegerLiteral { 
    var eggCount : Int = 0 

    init() {} 

    init(integerLiteral val: Int) { 
        self.eggCount = val 
    } 
}

Nest實現了ExpressibleByIntegerLiteral協議,就可以使用Integer的字面變數來表示一個Nest,比如有這樣一個函式:

func reportEggs(_ nest:Nest) { 
    print("this nest contains \(nest.eggCount) eggs") 
}

接受的是一個Nest引數,就可以傳入一個字面變數

reportEggs(4) // this nest contains 4 eggs

ExpressibleByIntegerLiteral,這個協議要求實現init(integerLiteral:)方法。Nest實現了。

注意,這個協議是關於字面變數的,並不是Int型別到Nest的轉換。下邊程式碼是不能通過編譯的:

var x = 4
reportEggs(x)

其他的字面變數協議有:
- ExpressibleByNilLiteral
- ExpressibleByBooleanLiteral
- ExpressibleByIntegerLiteral
- ExpressibleByFloatLiteral
- ExpressibleByStringLiteral
- ExpressibleByExtendedGraphemeClusterLiteral
- ExpressibleByUnicodeScalarLiteral
- ExpressibleByArrayLiteral
- ExpressibleByDictionaryLiteral

泛型

型別是物件例項的模板,泛型就是型別的模板。不同的是,物件的的例項化是在執行期間進行的,模板的例項化是在編譯期間進行的。

泛型函式

func dogMakerAndNamer<WhatType:Dog>(_:WhatType.Type) -> WhatType { 
    let d = WhatType.init(name:"Fido") 
    return d 
}

在這個函式中,WhatType是一個佔位符,在編譯的時候會替換成真正的型別。對這個型別的要求是繼承自Dog.

Optional就是通過泛型來實現的,它是一個泛型的enum。

enum Optional<Wrapped> { 
   case none 
   case some(Wrapped)
   init(_ some: Wrapped) // ...
}

泛型協議

Self

如果一個protocol的方法宣告中使用了Self,這個Self表示實現這個protocol的那個類的當前例項本身。相當於在類的實現中的self。也就是說Self是protocol中的self(因為此時不知道具體型別是啥)。

associated type

protocol Flier { 
    associatedtype Other 
    func flockTogetherWith(_ f:Other) 
    func mateWith(_ f:Other) 
}

一個具體的類實現這個protocol的時候需要將Other的部分替換成具體的型別:

struct Bird : Flier { 
    func flockTogetherWith(_ f:Bird) {} 
    func mateWith(_ f:Bird) {} 
}

上邊的字面變數協議就是泛型協議。

public protocol ExpressibleByIntegerLiteral {
    associatedtype IntegerLiteralType
    public init(integerLiteral value: Self.IntegerLiteralType)
}

泛型類

struct HolderOfTwoSameThings<T> {
    var firstThing : T 
    var secondThing : T 

    init(thingOne:T, thingTwo:T) {
        self.firstThing = thingOne
        self.secondThing = thingTwo 
    }
}

let holder = HolderOfTwoSameThings(thingOne:"howdy", thingTwo:"getLost")

可以使用多個型別的泛型

func flockTwoTogether<T, U>(_ f1:T, _ f2:U) {}

泛型的約束

protocol Flier {
    func fly()
}

protocol Flocker {
    associatedtype Other : Flier // *
    func flockTogetherWith(f:Other)
}

struct Bird : Flocker, Flier {
    func fly() {}
    func flockTogetherWith(f:Bird) {}
}

Flocker定義了一個protocol,它的associatedtype要求必須是一個Flier,所以Bird要想採用Flocker協議,就必須也採用Flier協議。

顯示的例項化泛型

protocol Flier { 
    associatedtype Other
} 

struct Bird : Flier { 
    typealias Other = String 
}

或者

class Dog<T> { 
    var name : T?
} 

let d = Dog<String>()

組合使用泛型協議和泛型物件

protocol Flier { init() } struct Bird : Flier {
    init() {} 
} 

struct FlierMaker<T:Flier> {
    static func makeFlier() -> T {
        return T()
    } 
} 

let f = FlierMaker<Bird>.makeFlier() // returns a Bird

泛型的繼承

class Dog<T> { 
    func speak(_ what:T) {} 
}

class NoisyDog<T> : Dog<T> {}

或者在繼承的時候就例項化

class NoisyDog : Dog<String> { 
    override func speak(_ what:String) {} 
}

Associated Type Chains

protocol Fighter { 
    associatedtype Enemy where Enemy : Fighter 
}

Enemy 佔位符要求也是一個Fighter,因為Enemy本身是在Fighter中使用的,所以需要使用where語句。

例項化

struct Soldier : Fighter {
    typealias Enemy = Archer 
} 

struct Archer : Fighter {
    typealias Enemy = Soldier
}
struct Camp<T:Fighter> { 
    var spy : T.Enemy?
}

spy這個變數的型別是Fighter的Enemy,但是因為Fighter也還沒確定,所以使用T.Enemy。

Enemy是一個佔位符,Fighter是一個泛型。在Camp中,T也是佔位符,代表一個實現了Fighter的型別。所以spy是一個<佔位符>.<佔位符>的形式。隨後的例項化的時候,會替換所有的佔位符。

var c = Camp<Soldier>() 
c.spy = Archer()

泛型約束的where語句

func flyAndWalk<T: Flier> (_ f:T) {}
func flyAndWalk<T> (_ f:T) where T: Flier {}

func flyAndWalk2<T: Flier & Walker> (_ f:T) {}
func flyAndWalk2<T> (_ f:T) where T: Flier & Walker {} 
func flyAndWalk2a<T> (_ f:T) where T: Flier, T: Walker {}

更復雜些的約束

protocol Flier { 
    associatedtype Other 
} 

struct Bird : Flier {
    typealias Other = String 
} 

struct Insect : Flier {
    typealias Other = Bird 
} 

func flockTogether<T> (_ f:T) where T:Flier, T.Other:Equatable {}

flockTogether有一個型別佔位符T,where語句中要求T是Flier的,因為Flier本身有一個佔位符Other,還可以要求這個Other是可比較的。於是使用:T.Other:Equatable。

flockTogether(Bird()) // okay 
flockTogether(Insect()) // compile error

因為Insect的Other是Bird,而Bird不是Equatable的。所以第二句編譯錯誤。

如果使用”==”操作符,意思是型別必須是後邊的型別。

func flockTogether<T> (_ f:T) where T:Flier, T.Other == Walker {}

T.Other必須是Walker型別,不能是subclass。

Swfit中append(contentsOf:)函式的實現就使用了==操作符

mutating func append<S>(contentsOf newElements: S) 
    where S:Sequence, S.Element == Character

Extensions

Extensions類似於OC中的Category,能給已有的類新增功能。

  • extension不能重寫原來的方法,但是可以進行函式過載
  • extension不能新增stored property,但是可以新增compute property,可以新增static或者class的屬性,無論是不是計算屬性都可以
  • extension不能新增designated initializer但是可以新增convenience initializer。
extension Array {
    mutating func shuffle () { 
        for i in (0..<self.count).reversed() { 
            let ix1 = i 
            let ix2 = Int(arc4random_uniform(UInt32(i+1))) 
            self.swapAt(ix1, ix2) 
        } 
    }
}

如果擴充套件一個protocol,就可以新增屬性,還可以提供方法的實現

protocol Flier { } extension Flier {
    func fly() {
        print("flap flap flap")
    } 
} 

struct Bird : Flier { }

Bird繼承了fly的實現。但是這種繼承的方法沒有多型的特性

let i = Insect() 
i.fly() // whirr

let f : Flier = Insect() 
f.fly() // flap flap flap (!!)

如果仍想要多型的特性,需要在原來的protocol中加上fly方法

protocol Flier { 
    func fly() // * 

} 

extension Flier { 
    func fly() { 
        print("flap flap flap") 
    } 
}

也可以給泛型新增extension,如,使用extension給Array新增一個求最小值的方法

extension Array where Element:Comparable { 
    func myMin() -> Element { 
        var minimum = self[0] 
        for ix in 1..<self.count { 
            if self[ix] < minimum { 
                minimum = self[ix] 
            } 
        } 
        return minimum
    }
}

let a = [1, 4, 5, 0, 8]
print(a.myMin())

myMin這個函式只有在Comparable物件例項化的Array中才有(Array本身是泛型,初始化的時候會將泛型例項化),如果Array中儲存的物件不是Comparable的,那麼就不會看見myMin這個函式

let b = [UIColor.blue, UIColor.red]
b.myMin() // 編譯錯誤

Umbrella Types

Any
Any是Swift中的通用型別,但需要一個Any物件的時候,可以使用任何的型別。和OC互動的時候,OC中的id型別都可以表示為Any。比如UserDefault,NSCoding中的引數。

let ud = UserDefaults.standard 
ud.set("howdy", forKey:"greeting") 
ud.set(Date(), forKey:"now")

取出來使用的需要進行型別轉換:

let ud = UserDefaults.standard 
let d = ud.object(forKey:"now") as? Date 
if d != nil { // ...

}

AnyObject
AnyObject是一個空protocol,所有的型別都自動實現了這個protocol。在Swift中Any是可以代表任何型別,AnyObject代表任何物件型別,Swift中任何型別都是物件型別,所以沒有區別,但是OC中不是,所以AnyObject等價於OC中的id。

可以看一下型別轉換的過程

let s = "howdy" as AnyObject // String to NSString to AnyObject 
let i = 1 as AnyObject // Int to NSNumber to AnyObject 
let r = CGRect() as AnyObject // CGRect to NSValue to AnyObject 
let d = Date() as AnyObject // Date to NSDate to AnyObject 
let b = Bird() as AnyObject // Bird (struct) to boxed type to AnyObject

AnyObject的使用也和id類似:

class Dog { 
    @objc var noise : String = "woof" 
    @objc func bark() -> String { 
        return "woof" 
    } 
} 

class Cat {}

let c : AnyObject = Cat() 
let s = c.noise

let s = c.noise這句話是可以編譯過的,等價於OC中的[c noise],向一個id傳送訊息,在編譯期間並不檢查。

如果呼叫的時候加一個?號,那麼即使實際的型別沒有實現方法,也不會crash,但是返回的值是String?

let c : AnyObject = Cat() 
let s = c.bark?()

能這樣用的前提條件是方法或者屬性標記為@objc,或者類本身就是NSObject的子類。

AnyClass
AnyClass是AnyObject的子類,對應OC中的Class。

class Dog { 
    @objc static var whatADogSays : String = "woof" 

} 

class Cat {}

let c : AnyClass = Cat.self 
let s = c.whatADogSays