Protocol-Oriented Programming with Swift 笔记(上)

最近买了《Protocol-Oriented Programming with Swift》,大部分都是讲基础,也就设计模式那里有点看头,既然看了就做点笔记记录一下,做点微小的贡献。

第一章 面向对象和面向协议编程

面向对象编程的三个特征:封装、继承、多态;当我们想使用一个单一的接口来表示多种类型,就可以使用多态,多态可以让我们用统一的方式与多种类型进行交互。

面向对象编程的缺点:

  1. 子类初始化时要先初始化父类,漏掉初始化子类参数的话,容易导致沿用父类参数(要对父类很熟悉)
  2. 面向对象编程只能使用引用类型,而且新手容易出错
  3. 一个类只允许有一个父类,不允许多继承

面向协议编程可以使用值类型,我们将一组行为定义成一个协议,可以分类定义多组协议。然后根据需要遵守这些协议,面向协议编程的优点:

  1. 遵循协议的对象,其所有变量都是由自己完成初始化的,而不是依赖父类
  2. 面向协议编程可以使用引用类型和值类型
  3. 可以同时遵守多个协议
  4. 我们可以通过 extension 的方式添加协议的默认实现
  5. 更加容易扩展

相同点:无论面向协议编程还是面向对象编程,我们都使用多态来通过单一接口与多种不同类型进行交互:

  • 在面向对象编程模式中,使用父类提供的接口和所有子类进行交互
  • 在面向协议编程模式中,使用协议和协议扩展提供的接口与所有遵守协议的类型进行交互

第二章 类型选择

其他语言中的一些基本类型比如 numbers,strings,characters 和 Boolean 都可以使用 Swift 的结构体在其基本库中实现。我们也能通过 extensions 扩展这些基本类型的行为。

Swift 的类型可以分为命名类型的和复合类型,前者指在定义时就给定名字的类型,包括 classes, structures, enumerations 和 protocols。除了用户自定义的,基本库中也有 arrays, sets 和 dictionaries 等类型。

Swift 中还有一种复合类型,即 function typestuple typesfunction types 表示 closuresfunctionsmethods。我们可以使用 typealias 给复合类型起别名。

Swift 中类型的两种分类:引用类型和值类型。协议既不属于引用类型也不属于值类型,因为不能创建协议的实例。

Class

面向对象最常见的类型,可以封装属性、方法和初始化方法。在 Swift 中,我们在需要引用类型时常使用 class

Structure

苹果官方是推崇值类型的,以至于在基本库中大量的使用了 Structure,它允许我们封装属性、方法、和实例初始化方法。

结构体默认会为我们提供一初始化,因此可以不用写。因为结构体是值类型,实例方法默认是不能修改属性的,要修改必须加前缀 mutating

Enumerations

枚举对象可以看做是一组值组成的类型,他也是值类型。

  1. 它可以指定原始值,并使用 rawValue 获取原始值

    enum Devices: String {
        case IPod = "iPod"
        case IPhone = "iPhone"
        case IPad = "iPad"
    }
    Devices.IPod.rawValue
    
  2. 可以在 case value 中存储相关值

    enum Devices {
        case IPod(model: Int, year: Int, memory: Int)
        case IPhone(model: String, memory: Int)
        case IPad(model: String, memory: Int)
    }
    var myPhone = Devices.IPhone(model: "6", memory: 64)
    var myTablet = Devices.IPad(model: "Pro", memory: 128)
    
    
    switch myPhone {
    case .IPod(let model, let year, let memory):
        print("iPod: \(model) \(memory)")
    case .IPhone(let model, let memory):
        print("iPhone: \(model) \(memory)")
    case .IPad(let model, let memory):
        print("iPad: \(model) \(memory)")
    }
    
  3. 就像结构体和类一样,还可以包含计算属性、初始化和方法(这里不能包含存储属性,只能是计算属性)

    enum BookFormat {
        case PaperBack (pageCount: Int, price: Double)
        case HardCover (pageCount: Int, price: Double)
        case PDF (pageCount: Int, price: Double)
        case EPub (pageCount: Int, price: Double)
        case Kindle (pageCount: Int, price: Double)
        var pageCount: Int {
            switch self {
            case .PaperBack(let pageCount, _):
                return pageCount
            case .HardCover(let pageCount, _):
                return pageCount
            case .PDF(let pageCount, _):
                return pageCount
            case .EPub(let pageCount, _):
                return pageCount
            case .Kindle(let pageCount, _):
                return pageCount
            } 
        }
        var price: Double {
            switch self {
            case .PaperBack(_, let price):
                return price
            case .HardCover(_, let price):
                return price
            case .PDF(_, let price):
                return price
            case .EPub(_, let price):
                return price
            case .Kindle(_, let price):
                return price
            } 
        }
        func purchaseTogether(otherFormat: BookFormat) -> Double {
            return (self.price + otherFormat.price) * 0.80
        }
    }
    
    
    var paperBack = BookFormat.PaperBack(pageCount: 220, price: 39.99)
    var pdf = BookFormat.PDF(pageCount: 180, price: 14.99)
    var total = paperBack.purchaseTogether(pdf)
    

Tuple

有序列表元素,苹果官方用来作为函数的多个返回值。

func calculateTip(billAmount: Double,tipPercent: Double) ->  
       (tipAmount: Double, totalAmount: Double) {
           let tip = billAmount * (tipPercent/100)
           let total = billAmount + tip
           return (tipAmount: tip, totalAmount: total)
}

var tip = calculateTip(31.98, tipPercent: 20)  
print("\(tip.tipAmount) - \(tip.totalAmount)")  

元组是值类型,也是复合类型,我们通常会给它用 typealias 起一个别名

typealias myTuple = (tipAmount: Double, totalAmount: Double)  

值类型和引用类型

二者主要的不同在于作为实例进行传递时的行为:

  • 引用类型传递的是指针
  • 值类型传递的是拷贝

苹果提供了 inout 关键字,使传递值类型参数达到引用类型的效果,注意使用 inout 关键字时,实际调用要传递值类型的指针 &xxx

func getGradeForAssignment(inout assignment: MyValueType) {  
    ...
}

var mathAssignment = MyValueType()  
getGradeForAssignment(&mathAssignment)  

递归数据类型(只能是引用类型)

一个递归数据类型包含一个属性,该属性的类型与这个数据类型完全相同。递归类型通常用来定义一些动态数据类型,比如列表和树。这些数据结构可以根据需要动态地增长。

链表就是一个绝佳的例子,每一个节点都包含一些数据以及指向下一个节点的指针。

class LinkedListReferenceType {  
    var value: String
    var next: LinkedListReferenceType?
    init(value: String) {
        self.value = value
    } 
}

通常不能用结构体(值类型)定义递归数据类型,因为每次它都会进行拷贝操作,如果非要用值类型实现递归数据类型,必须使用 indirect 关键字

enum List<Element> {  
    case End
    indirect case Node(Element, next: List<Element>)
}

Inheritance(仅限引用类型)

子类继承父类,并且可以重载父类的方法,只能单继承。

Swift 内建的数据结构和类型

Swift 在基本库中内建了许多基本的数据类型,我们可以使用 extension 来为他们添加扩展方法。

第三章 捕获错误

错误处理是我们在设计框架时必须要考虑的事情。最简单的错误处理是让函数返回一个 Bool 类型的值来指示成功与否。如果需要了解具体的错误信息,可以添加 NSErrorPointer 类型的参数来说明具体的错误(可以用枚举类型)

使用 guard

使用 guard 可以让我们减少嵌套

错误处理之使用返回值

这是 Objective-C 和 Swift 1.x 时代错误处理的主要方式,返回值除了 Bool 类型,还可以是枚举类型:

enum DrinkTemperature {  
    case TooHot
    case TooCold
    case JustRight
}

mutating func temperatureChange(change: Double) -> DrinkTemperature {  
    temperature += change
    guard temperature >= 35 else {
        return .TooCold
    }
    guard temperature <= 45 else {
        return .TooHot
    }
    return .JustRight
}

根据返回值做不同的处理

var results = myDrink.temperatureChange(-5)  
    switch results {
    case .TooHot:
        print("Drink too hot")
    case .TooCold:
        print("Drink too cold")
    case .JustRight:
        print("Drink just right")
}

不过使用返回值处理错误的缺点就是容易被忽视。

错误处理之使用 NSError

我们使用 NSError 实例提供错误的细节,这里使用了 NSError inout 参数

struct ErrorConstants {  
    static let ERROR_DOMAIN = "com.masteringswift.nserrorexample"
    static let ERROR_INSUFFICENT_VOLUME = 200
    static let ERROR_TOO_HOT = 201
    static let ERROR_TOO_COLD = 202
}

mutating func drinking(amount: Double, error: NSErrorPointer) -> Bool  
{
    guard amount <= volume else {
        error.memory = NSError(
            domain: ErrorConstants.ERROR_DOMAIN,
            code: ErrorConstants.ERROR_INSUFFICENT_VOLUME,
            userInfo: ["Error reason":"Not enough volume for drink"])
            return false
    }
    volume -= amount
    return true 
}   

发生错误时,我们设置了 NSErrorPointer 参数的 memory 属性为一个 NSError 实例。

NSErrorPointer 其实是 AutoreleasingUnsafeMutablePointer 结构体,等价于 Objective-C 中的 NSError**

在具体调用时,创建一个 error 传递进去

var myDrink = Drink(volume: 23.5, caffeine: 280,  
    temperature: 38.2, drinkSize: DrinkSize.Can24,
    description: "Drink Structure")
var myError: NSError?  
    if myDrink.drinking(50.0, error: &myError) {
        print("Had a drink")
    } else {
        print("Error: \(myError?.code)")
    }

Swift 2.0 中的错误处理

ErrorType

首先引入了可以 throw 的 ErrorType 类型,可以带关联值耶

enum DrinkErrors: ErrorType {  
    case insufficentVolume
    case tooHot (temp: Double)
    case tooCold (temp: Double)
}

throw

接着我们可以 throw 错误了出来了,不用 return 啦

/**
    This method will change the temperature of the drink.
    - Parameter change:  The amount to change, can be negative
        or positive
    - Throws:
        - DrinkError.tooHot if the drink is too hot
        - DrinkError.tooCold if the drink is too cold
*/
mutating func temperatureChange(change: Double) throws {  
    temperature += change
    guard temperature > 35 else {
        throw DrinkErrors.tooCold(temp: temperature)
    }
    guard temperature < 45 else {
        throw DrinkErrors.tooHot(temp: temperature)
    } 
}

捕获错误

我们需要捕获那些函数 throw 出来的错误

// 捕获指定类型的错误
do {  
    try myDrink.drinking(50.0)
} catch DrinkErrors.insufficentVolume {
    print("Error taking drink")
}

// 捕获具体错误
do {  
    // our statements
} catch let error {
    print("Error:  \(error)")
}

// 捕获错误的相关值
do {  
    try myDrink.temperatureChange(20.0)
} catch DrinkErrors.tooHot(let temp) {
    print("Drink too hot: \(temp) degrees ")
} catch DrinkErrors.tooCold(let temp) {
    print("Drink too cold: \(temp) degrees ")
}

有错误返回 nil 就好了,或者使用 if let 可选绑定

try? myDrink.temperatureChange(20.0)  
// 可选绑定
if let value = try? myMethod(42) {  
    print("Value returned \(value)")
}

如果在一个函数可能内部发生错误,我们也可以把它抛出去,注意需要在最外层函数使用 throw 关键字

func myFunc() throws {  
    try myDrink.temperatureChange(20.0)
}

defer

我们可以使用 defer 退出函数前做一些清理工作,defer block 总是在离开作用域之前执行,即使有错误抛出。

func hello() {  
    defer {
        print("4")
    }
    if true {
        defer {
            print("2")
        }
        defer {
            print("1")
        }
    }
    print("3")
}

hello()  

最终结果是: 1,2,3,4,注意是离开作用域前执行,这里的作用域指 {...},括号之内都算一个作用域。

总结

三种方式处理错误:

  1. Error handling with return values (可以是 Bool 也可以是枚举值)
  2. Error handling with NSError
  3. Error handling with do-catch blocks

-EOF-

如果感觉此文对你有帮助,请随意打赏支持作者 😘

chengway

认清生活真相之后依然热爱它!

Subscribe to Talk is cheap, Show me the world!

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!