最近买了 《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 ,注意是离开作用域前执行,这里的作用域指 {...},括号之内都算一个作用域。
三种方式处理错误: