2016年2月11日 星期四

iOS筆記:Swift(5)

Initialization

Initialization is the process of preparing an instance of a class, structure, or enumeration for use.
(為了使用某個類別、結構或列舉型別的實例而進行的準備過程。這個過程包含了為實例中的每個屬性設置初始值和為其執行必要的準備和初始化任務。)

Initializers

建構器在創建某特定型別的新實例時呼叫。它的最簡形式類似於一個不帶任何參數的實例方法,以關鍵字init命名。

下面範例中定義了一個用來保存華氏溫度的結構Fahrenheit,它擁有一個Double型別的儲存型屬性temperature:

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
println("The default temperature is \(f.temperature)° Fahrenheit")
// 輸出 "The default temperature is 32.0° Fahrenheit」

更簡單的方式在定義結構Fahrenheit時為屬性temperature設置預設值:

struct Fahrenheit {
    var temperature = 32.0
}

Automatic Reference Counting

Swift 使用自動引用計數(ARC)這一機制來跟蹤和管理你的應用程式的內存。通常情況下,Swift 的內存管理機制會一直起著作用,你無須自己來考慮內存的管理。

How ARC Works

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance. This memory holds information about the type of the instance, together with the values of any stored properties associated with that instance.

The reference is called a “strong“ reference because it keeps a firm hold on that instance, and does not allow it to be deallocated for as long as that strong reference remains.

Strong Reference Cycles Between Class Instances

下面的範例展示了自動引用計數的工作機制。範例以一個簡單的Person類別開始,並定義了一個叫name的常數屬性:

class Person {
    let name: String
    init(name: String) {
        self.name = name
        println("\(name) is being initialized")
    }
    deinit {
        println("\(name) is being deinitialized")
    }
}

在上面的範例中,ARC 會跟蹤你所新創建的Person實例的參考數量,並且會在Person實例不再被需要時銷毀它。

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

將這兩個實例關聯在一起之後,一個迴圈強參考被創建了。Person實例現在有了一個指向Apartment實例的強參考,而Apartment實例也有了一個指向Person實例的強參考。因此,當你斷開john和number73變數所持有的強參考時,參考計數並不會降為 0,實例也不會被 ARC 銷毀:

當你把這兩個變數設為nil時,沒有任何一個析構函式被呼叫。強參考迴圈阻止了Person和Apartment類別實例的銷毀,並在你的應用程式中造成了內存泄漏。

在你將john和number73賦值為nil後,強參考關系如下圖:

enter image description here

Resolving Strong Reference Cycles Between Class Instances

Swift 提供了兩種辦法用來解決你在使用類別的屬性時所遇到的迴圈強參考問題:弱參考(weak reference)和無主參考(unowned reference)。

Weak References

弱參考不會牢牢保持住參考的實例,並且不會阻止 ARC 銷毀被參考的實例。這種行為阻止了參考變為迴圈強參考。宣告屬性或者變數時,在前面加上weak關鍵字表明這是一個弱參考。

在實例的生命周期中,如果某些時候參考沒有值,那麼弱參考可以阻止迴圈強參考。

Weak references must be declared as variables, to indicate that their value can change at runtime. A weak reference cannot be declared as a constant.

Checking Type

用型別檢查運算子(is)來檢查一個實例是否屬於特定子型別。若實例屬於那個子型別,型別檢查運算子回傳 true ,否則回傳 false 。

for item in library {
    if item is Movie {
        ++movieCount
    } else if item is Song {
        ++songCount
    }
}

Any和AnyObject的型別檢查

Swift為不確定型別提供了兩種特殊型別別名:

  • AnyObject可以代表任何class型別的實例。
  • Any可以表示任何型別,除了方法型別(function types)

Extension Syntax

宣告一個擴展使用關鍵字extension:

extension SomeType {
    // 加到SomeType的新功能寫到這裡
}

or 一個擴展可以擴展一個已有型別,使其能夠適配一個或多個協定(protocol)。當這種情況發生時,協定的名字應該完全按照類別或結構的名字的方式進行書寫:

extension SomeType: SomeProtocol, AnotherProctocol {
    // 協定實作寫到這裡
}

計算型屬性(Computed Properties)

擴展可以向已有型別添加計算型實例屬性和計算型型別屬性。

extension Double {
    var km: Double { return self * 1_000.0 }
    var m : Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
println("One inch is \(oneInch) meters")
// 列印輸出:"One inch is 0.0254 meters"
let threeFeet = 3.ft
println("Three feet is \(threeFeet) meters")
// 列印輸出:"Three feet is 0.914399970739201 meters"

Extension Methods

extension Int {
    func repetitions(task: () -> ()) {
        for i in 0..self {
            task()
        }
    }
}

repetitions方法使用了一個() -> ()型別的單參數(single argument),表明函式沒有參數而且沒有回傳值。

定義該擴展之後,你就可以對任意整數呼叫repetitions方法,實作的功能則是多次執行某任務:

3.repetitions({
    print("Hello!")
    })
// Hello!
// Hello!
// Hello!

Protocol Syntax

協定的定義與類別,結構,列舉的定義非常相似,如下所示:

protocol SomeProtocol {
    // 協定內容
}

在類別,結構,列舉的名稱後加上協定名稱,中間以冒號:分隔即可實作協定;實作多個協定時,各協定之間用逗號,分隔,如下所示:

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 結構內容
}

當某個類別含有父類別的同時並實作了協定,應當把父類別放在所有的協定之前,如下所示:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 類別的內容
}

Protocol Property & Method Requirements

協定能夠要求其遵循者必須含有一些特定名稱和型別的實例屬性(instance property)或類別屬性 (type property),也能夠要求屬性具有(設置權限)settable 和(存取權限)gettable,但它不要求屬性是儲存型屬性(stored property)還是計算型屬性(calculate property)。

前綴var關鍵字將屬性宣告為變數。在屬性宣告後寫上{ get set }表示屬性為可讀寫的。{ get }用來表示屬性為可讀的。即使你為可讀的屬性實作了setter方法.

protocol SomeProtocol {
    var musBeSettable : Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

用類別來實作協定時,使用class關鍵字來表示該屬性為類別成員;用結構或列舉實作協定時,則使用static關鍵字來表示:

protocol AnotherProtocol {
    class var someTypeProperty: Int { get set }
}

protocol FullyNamed {
    var fullName: String { get }
}

FullyNamed協定含有fullName屬性。因此其遵循者必須含有一個名為fullName,型別為String的可讀屬性。

struct Person: FullyNamed{
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
//john.fullName 為 "John Appleseed"

方法跟屬性類似, 其遵循者必備某些特定的實例方法和類別方法。協定方法的宣告與普通方法宣告相似,但它不需要方法內容。

protocol SomeProtocol {
    class func someTypeMethod()
}

protocol RandomNumberGenerator {
    func random() -> Double
}
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = ((lastRandom * a + c) % m)
        return lastRandom / m
    }
}
let generator = LinearCongruentialGenerator()
println("Here's a random number: \(generator.random())")
// 輸出 : "Here's a random number: 0.37464991998171"
println("And another one: \(generator.random())")
// 輸出 : "And another one: 0.729023776863283"

Mutating Method Requirements

能在方法或函式內部改變實例型別的方法稱為突變方法。Swift預設不允許修改型別,因此需要前綴mutating關鍵字用來表示該函式中能夠修改型別)

如下所示,Togglable協定含有toggle函式。根據函式名稱推測,toggle可能用於切換或恢復某個屬性的狀態。mutating關鍵字表示它為突變方法:

protocol Togglable {
    mutating func toggle()
}

當使用列舉或結構來實作Togglabl協定時,必須在toggle方法前加上mutating關鍵字。

enum OnOffSwitch: Togglable {
    case Off, On
    mutating func toggle() {
        switch self {
        case Off:
            self = On
        case On:
            self = Off
        }
    }
}
var lightSwitch = OnOffSwitch.Off
lightSwitch.toggle()
//lightSwitch 現在的值為 .On

泛型所解決的問題(The Problem That Generics Solve)

泛型程式碼可以讓你寫出根據自我需求定義、適用於任何型別的,靈活且可重用的函式和型別。它的可以讓你避免重複的程式碼,用一種清晰和抽像的方式來表達程式碼的意圖。

泛型是 Swift 強大特征中的其中一個,許多 Swift 標準函式庫是通過泛型程式碼構建出來的。
泛型函式

泛型函式可以工作於任何型別,這裡是一個swapTwoInts(交換int的值)函式的泛型版本,用於交換兩個值:

func swapTwoValues<T>(inout a: T, inout b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

swapTwoValues函式主體和swapTwoInts函式是一樣的,它只在第一行稍微有那麼一點點不同於swapTwoInts,如下所示:

func swapTwoInts(inout a: Int, inout b: Int)
func swapTwoValues<T>(inout a: T, inout b: T)

這個函式的泛型版本使用了占位型別名字(通常此情況下用字母T來表示)來代替實際型別名(如In、String或Doubl)。占位型別名沒有提示T必須是什麼型別,但是它提示了a和b必須是同一型別T,而不管T表示什麼型別。只有swapTwoValues函式在每次呼叫時所傳入的實際型別才能決定T所代表的型別。
swapTwoValues函式除了要求傳入的兩個任何型別值是同一型別外,也可以作為swapTwoInts函式被呼叫。每次swapTwoValues被呼叫,T所代表的型別值都會傳給函式。

在下面的兩個範例中,T分別代表Int和String:

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"

沒有留言:

張貼留言