最近买了 《Protocol-Oriented Programming with Swift》 ,大部分都是讲基础,也就设计模式那里有点看头,既然看了就做点笔记记录一下,做点微小的贡献。
面向对象编程的三个特征:封装、继承、多态;当我们想使用一个单一的接口来表示多种类型,就可以使用多态,多态可以让我们用统一的方式与多种类型进行交互。
面向对象编程的缺点:
面向协议编程可以使用值类型,我们将一组行为定义成一个协议,可以分类定义多组协议。然后根据需要遵守这些协议,面向协议编程的优点:
相同点:无论面向协议编程还是面向对象编程,我们都使用多态来通过单一接口与多种不同类型进行交互:
其他语言中的一些基本类型比如 numbers,strings,characters 和 Boolean 都可以使用 Swift 的结构体在其基本库中实现。我们也能通过 extensions 扩展这些基本类型的行为。
Swift 的类型可以分为命名类型的和复合类型,前者指在定义时就给定名字的类型,包括 classes, structures, enumerations 和 protocols。除了用户自定义的,基本库中也有 arrays, sets 和 dictionaries 等类型。
Swift 中还有一种复合类型,即 function types 和 tuple types 。 function types 表示 closures
, functions
和 methods
。我们可以使用 typealias
给复合类型起别名。
Swift 中类型的两种分类:引用类型和值类型。协议既不属于引用类型也不属于值类型,因为不能创建协议的实例。
面向对象最常见的类型,可以封装属性、方法和初始化方法。在 Swift 中,我们在需要引用类型时常使用 class
苹果官方是推崇值类型的,以至于在基本库中大量的使用了 Structure,它允许我们封装属性、方法、和实例初始化方法。
结构体默认会为我们提供一初始化,因此可以不用写。因为结构体是值类型,实例方法默认是不能修改属性的,要修改必须加前缀 mutating
枚举对象可以看做是一组值组成的类型,他也是值类型。
它可以指定原始值,并使用 rawValue 获取原始值
enum Devices: String { case IPod = "iPod" case IPhone = "iPhone" case IPad = "iPad" } Devices.IPod.rawValue
可以在 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)") }
就像结构体和类一样,还可以包含计算属性、初始化和方法(这里不能包含存储属性,只能是计算属性)
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)
有序列表元素,苹果官方用来作为函数的多个返回值。
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>) }
子类继承父类,并且可以重载父类的方法,只能单继承。
Swift 在基本库中内建了许多基本的数据类型,我们可以使用 extension 来为他们添加扩展方法。
错误处理是我们在设计框架时必须要考虑的事情。最简单的错误处理是让函数返回一个 Bool 类型的值来指示成功与否。如果需要了解具体的错误信息,可以添加 NSErrorPointer 类型的参数来说明具体的错误(可以用枚举类型)
使用 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 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)") }
首先引入了可以 throw 的 ErrorType
类型,可以带关联值耶
enum DrinkErrors: ErrorType { case insufficentVolume case tooHot (temp: Double) case tooCold (temp: Double) }
接着我们可以 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 block 总是在离开作用域之前执行,即使有错误抛出。
func hello() { defer { print("4") } if true { defer { print("2") } defer { print("1") } } print("3") } hello()
最终结果是: 1,2,3,4 ,注意是离开作用域前执行,这里的作用域指 {...},括号之内都算一个作用域。
三种方式处理错误: