Swift與泛型編程第五彈:總結 一、協議 二、泛型 三、不透明類型 四、類型擦除

前幾彈大部分是根據《Swift程序設計語言》而寫,這本書爲了照顧入門的讀者,所以內容比較拖沓與細碎。爲了再次的理解與記憶,本彈筆者試圖擰乾水分,通過簡潔、直觀的方式進行總結。

一、協議

  1. 協議與泛型的關係
  • 由於Swift嚴格的類型檢查限制,許多模板代碼都需要使用協議來做類型的約束,所以泛型與協議關係密切
  1. 協議是什麼
  • 定義某事、某物、某類等的抽象接口
  1. 協議可以包含什麼
  • 類或對象屬性(計算、存儲都可以,協議不關注這個)、類或對象方法
  1. 協議可以提供默認實現嗎
  • 通過擴展協議,在擴展裏提供實現
  • 可以在協議擴展裏添加限制條件,當限制條件滿足,才能使用此默認實現
extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}
  1. 協議的語法是什麼
protocol ProtocolName {
    // 類屬性、方法
    // 對象屬性、方法
}
  1. 都誰可以實現/遵循協議,如何遵循協議
// 類、結構體、枚舉都可以遵循協議

// 可以實現多個協議,多個協議以逗號分隔
// 可以同時繼承類且實現協議,此時父類名要緊跟在冒號之後
class SomeClass: SuperClassNmae, ProtocolA, ProtocolB {

}
  1. 在協議裏怎麼聲明屬性
  • 協議可以包含類型屬性,此時屬性聲明前可以添加static(通用性更強,適用於3中類型)、class(只適用於類類型)
  • 協議聲明必須有此屬性,但不要求屬性是存儲屬性還是計算屬性
  • 協議可以要求屬性是隻讀或可寫,遵循此協議的實現,必須滿足,但可大於協議的要求
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

protocol FullyNamed {
    var fullName: String { get }
}

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

class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }
    var fullName: String {
        return (prefix != nil ? prefix! + " " : "") + name
    }
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName 爲 "USS Enterprise"
  1. 在協議裏怎麼聲明方法
  • 可以包含類型方法,此時方法聲明前可以添加static(通用性更強,適用於3中類型)、class(只適用於類類型)
  • 只能包含方法簽名,不能包含方法實現
  • 可以定義具有可變參數的方法,但不支持具有默認參數的方法聲明
  • 如果方法可能會改變自身實例,則最好在方法名前添加matuting,如果不添加對於類類型是可以的,但是對於值類型則不可以
protocol Togglable {
    mutating func toggle()
}
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
  1. 在協議裏怎麼聲明初始化方法
  • 可以聲明初始化方法
  • 無論方法實際是指定的、還是便利的,都必須在實現此方法時,添加required關鍵字
  • 如果某類既繼承了父類的初始化方法,由實現了協議的初始化方法,並且兩個方法相同,則必須添加required以及override兩個關鍵字
protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
        // 這裏是構造器的實現部分
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // 因爲遵循協議,需要加上 required
    // 因爲繼承自父類,需要加上 override
    required override init() {
        // 這裏是構造器的實現部分
    }
}
  • 協議定義的初始化方法可以是可失敗的,在實現此初始化方法時,可以使用任何初始化方法來實現
  1. 協議可以作爲類型來使用嗎
  • 大部分情況下協議可以作爲類型使用
  • 含有關聯類型的協議不能用作類型一般用作類型約束,可以通過類型擦除來使用關聯類型的協議
  • 協議可以作爲哪些類型?所有用到類型的地方都可以!
    • 作爲函數、方法或構造器中的參數類型或返回值類型
    • 作爲常量、變量或屬性的類型
    • 作爲數組、字典或其他容器中的元素類型
  1. 協議與擴展
  • 可以通過擴展現有類型來遵循協議
protocol TextRepresentable {
    var textualDescription: String { get }
}

// 可以通過擴展現有類型來使其遵循某協議
extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}
  • 如果某類型實際已經遵循了某協議,可以在擴展裏添加遵循協議的聲明來明確此類型遵循協議
  1. 符合條件後才遵循/實現協議的方法是什麼
  • 可以通過類型約束來實現類型在符合某條件後才遵循協議
extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// 打印 "[A 6-sided dice, A 12-sided dice]"
  1. Swift編譯器可以默認合成協議嗎
  • 在某些情況下Swift會使一些類型自動遵循某些協議(Equatable、Hashable 和 Comparable)
  1. 協議與集合
  • 協議可以作爲某些基類類型的類型參數
let things: [TextRepresentable] = [game, d12, simonTheHamster]
for thing in things {
    print(thing.textualDescription)
}
  1. 協議與繼承
  • 協議能夠繼承一個或多個其他協議(多個協議之間以逗號分隔)
  • 可以在繼承的協議的基礎上增加新的要求
  • 協議的繼承語法與類的繼承相同
  1. 協議可以只能被類實現嗎
  • 繼承自AnyObject的協議只能被類類型實現
  1. 協議可以自動合成嗎
  • 某個協議繼承多個協議,那麼就可以說此協議合成了多個協議,此種合成是手動合成
  • 可以通過在多個協議間添加&符號來自動合成協議
protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”
  1. 檢查協議
  • is 用來檢查實例是否遵循某個協議,若遵循則返回 true,否則返回 false;
  • as? 返回一個可選值,當實例遵循某個協議時,返回類型爲協議類型的可選值,否則返回 nil;
  • as! 將實例強制向下轉換到某個協議類型,如果強轉失敗,將觸發運行時錯誤。
  1. 什麼是可選協議
  • 協議可以定義可選要求,遵循協議的類型可以選擇是否實現這些要求
  • 在協議中使用 optional 關鍵字作爲前綴來定義可選要求
  • 可選要求用在你需要和 Objective-C 打交道的代碼中
  • 協議和可選要求都必須帶上 @objc 屬性
  • 標記 @objc 特性的協議只能被繼承自 Objective-C 類的類或者 @objc 類遵循,其他類以及結構體和枚舉均不能遵循這種協議

二、泛型

  1. 泛型是什麼?
  • 泛型是功能強大的宏,具有官方背書的宏
  1. 泛型的使用廣泛嗎?
  • 十分廣泛
  • 官方支持的Array、Dictionary、Set、Optional、Result等都是泛型實現
  1. 泛型的主要是用來解決什麼問題的?
  • 最主要的用途是解決由於類型不同而導致的重複代碼問題
// 適用於Int類型的函數
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// 使用實例
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印“someInt is now 107, and anotherInt is now 3”

// 爲了對String類型進行相同的操作需要另外編寫函數
func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// 爲了對Double類型進行相同的操作需要另外編寫函數
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

// 但是編寫一個泛型函數就可以使用與上面的多種類型
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

注意
雖然按照上述例子,編寫一個泛型函數,可以使用與多種數據類型,其實編譯器根據每次的調用,會生成不同的函數。比如第一次使用Int類型調用,那麼就會生成一個Int版的方法,第二次根據String調用就會生成String版的函數,第三次還是Int調用,大部分編譯器會複用之前的那個Int版本的,但是某些情況下、少部分編譯器會重新生成一個Int版本的函數,由此導致的問題叫做——泛型的二進制級別的爆炸。

  1. 如何定義泛型函數?
  • 在方法名與方法參數之間添加<>
  • 在<>內部填寫以逗號分隔的類型佔位符
  • 這些類型佔位符可在函數體內充當類型名
  • 類型參數一般命名爲T,但是也可以起更有意義的名字例如Dictionary<Key, Value>
  1. 如何定義泛型類型?
  • 枚舉、結構體、類都可以被定爲爲泛型
  • 在類型名與類型定義的左大括號之間添加<>
  • 在<>內部可以添加類型名
  • 這些類型佔位符可在函數體內充當類型名
  • 類型參數一般命名爲T,但是也可以起更有意義的名字例如Dictionary<Key, Value>
// 泛型類型示例
struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 棧中現在有 4 個字符串
  1. 如何擴展泛型類型?
  • 對泛型擴展時不需要提供泛型類型的聲明
  • 原始類型定義中聲明的類型參數列表在擴展中可以直接使用
  • 並且這些來自原始類型中的參數名稱會被用作原始定義中類型參數的引用
extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}
  • 對泛型類型進行擴展的時候可以添加where子句,添加後,符合where條件限制的才能使用此擴展
extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}
  1. 泛型中的類型約束?
  • 類型約束指定類型參數必須繼承自指定類、遵循特定的協議或協議組合
  • 類型約束存在的原因是:Swift是類型要求極其嚴格的語言,所以之後添加相關類型約束後,泛型代碼才能通過編譯器的檢查
  • 在一個類型參數名後面放置一個類名或者協議名,並用冒號進行分隔,來定義類型約束
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 這裏是泛型函數的函數體部分
}

// 如果不對T限制爲符合Equatable協議,則if判斷無法通過語法檢查,因爲if判斷中的兩個類型可能沒有==運算符
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
  1. 關聯類型是什麼
  • 首先協議不支持上面提到的類型泛型的語法形式,爲了達到讓協議支持泛型,那麼久發明出來了關聯類型(不理解爲什麼這麼搞,有理解的同學可以私信我)
  • 關聯類型爲協議中的某個類型提供了一個佔位符名稱
  • 其代表的實際類型在協議被遵循時纔會被指定
  • 關聯類型通過 associatedtype 關鍵字來指定
protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}

struct IntStack: Container {
    // IntStack 的原始實現部分
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }

    // Container 協議的實現部分
    // 通過此條語句告知編譯器,Item的實際類型
    // 但這個其實不是必須的,因爲編譯器可以通過append等協議方法來推斷出來Item的類型,所以實際上忽略這條語句也是可以的
    typealias Item = Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}
  1. 如何擴展現有類型來指定關聯類型
// Array已經符合Container協議,可以通過擴展使其遵循Container協議
// 此時不需要通過類型推斷,Item的實際類型就是數組的Element類型
extension Array: Container {}
  1. 如何給關聯類型指定約束
  • 指定類型約束的語法如下
protocol Container {
    associatedtype Item: Equatable
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
  • 可以通過自身來約束關聯類型
protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}
  1. 泛型中的where子句
  • 總體where子句
// C1 必須符合 Container 協議(寫作 C1: Container)。
// C2 必須符合 Container 協議(寫作 C2: Container)。
// C1 的 Item 必須和 C2 的 Item 類型相同(寫作 C1.Item == C2.Item)。
// C1 的 Item 必須符合 Equatable 協議(寫作 C1.Item: Equatable)。

func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable {

        // 檢查兩個容器含有相同數量的元素
        if someContainer.count != anotherContainer.count {
            return false
        }

        // 檢查每一對元素是否相等
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }

        // 所有元素都匹配,返回 true
        return true
}
  • 具體where子句
extension Container {
    func average() -> Double where Item == Int {
        var sum = 0.0
        for index in 0..<count {
            sum += Double(self[index])
        }
        return sum / Double(count)
    }
    func endsWith(_ item: Item) -> Bool where Item: Equatable {
        return count >= 1 && self[count-1] == item
    }
}
let numbers = [1260, 1200, 98, 37]
print(numbers.average())
// 輸出 "648.75"
print(numbers.endsWith(37))
// 輸出 "true"
  1. 具有泛型 Where 子句的關聯類型
protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }

   // 可以對關聯類型添加where子句的約束
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
    func makeIterator() -> Iterator
}
  1. 如何定義泛型下標
// 在尖括號中的泛型參數 Indices,必須是符合標準庫中的 Sequence 協議的類型。
//下標使用的單一的參數,indices,必須是 Indices 的實例。
// 泛型 where 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 Int 類型。這樣就能確保在序列(Sequence)中的索引和容器(Container)裏面的索引類型是一致的。
extension Container {
    subscript<Indices: Sequence>(indices: Indices) -> [Item]
        where Indices.Iterator.Element == Int {
            var result = [Item]()
            for index in indices {
                result.append(self[index])
            }
            return result
    }
}

三、不透明類型

  1. 什麼是不透明類型
  • 對外隱藏函數或方法的返回值的實際類型的一種方法
protocol MyProtocol {}

class MyBaseClass1 : MyProtocol {}

class MyClass2 : MyBaseClass1 {}

class MyClass3 : MyClass2 {}

func test1() -> some MyClass2 {
    let b = false
    if b {
        return MyClass3()
    } else {
        return MyClass3()
    }
}

func test2() -> some MyProtocol {
    let b = false
    if b {
        return MyClass3()
    } else {
        return MyClass3()
    }
}
  1. 不透明類型與泛型
  • 泛型一般由調用者確定泛型函數、方法的返回值類型
  • 不透明類型與泛型正好相反,由被調用方決定返回的類型
  1. 不透明類型與協議的區別
  • 函數或方法的返回值是協議的時候,其返回類型只要是符合此協議的對象即可
  • 函數或方法的返回值是不透明的時候,其所有分支路徑返回的類型必須是同一種實際類型

四、類型擦除

  1. 類型擦除的本質是什麼
  • 由於具有關聯對象的協議不能用做類型,所有通過泛型的包括,逃避編譯器的檢查的一種方法
  • 如何實現類型擦除
protocol CarFactoryProtocol {
  associatedtype CarType
  func produce() -> CarType
}

struct ElectricCar {
  let brand: String
}

struct PetrolCar {
  let brand: String
}


struct TeslaFactory: CarFactoryProtocol {
  typealias CarType = ElectricCar
  
  func produce() -> TeslaFactory.CarType {
    print("producing tesla electric car ...")
    return ElectricCar(brand: "Tesla")
  }
}

struct BMWFactory: CarFactoryProtocol {
  typealias CarType = ElectricCar
  
  func produce() -> BMWFactory.CarType {
    print("producing bmw electric car ...")
    return ElectricCar(brand: "BMW")
  }
}

struct ToyotaFactory: CarFactoryProtocol {
  typealias CarType = PetrolCar

  func produce() -> ToyotaFactory.CarType {
    print("producing toyota petrol car ...")
    return PetrolCar(brand: "Toyota")
  }
}

let bmwFactory = BMWFactory()
bmwFactory.produce()  // producing bmw electric car ...

let toyotaFactory = ToyotaFactory()
toyotaFactory.produce() // producing toyota petrol car ...

// 下面語句有以下錯誤
// Protocol 'CarFactoryProtocol' can only be used as a generic constraint because it has Self or associated type requirements
//let electricCarFactories: [CarFactoryProtocol]


// 爲了解決以上問題,通過引入一個包裝的泛型類型來處理
struct AnyCarFactory<CarType>: CarFactoryProtocol {
  private let _produce: () -> CarType
    
  //只接受實現了CarFactoryProtocol的實例對象
  init<Factory: CarFactoryProtocol>(_ carFactory: Factory) where Factory.CarType == CarType {
    _produce = carFactory.produce
  }
 
  func produce() -> CarType {
      return _produce()
  }
}

let factories = [AnyCarFactory(TeslaFactory()), AnyCarFactory(BMWFactory())]
factories.map() { $0.produce() }
// Output:
// producing tesla electric car ...
// producing bmw electric car ...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章