Swift基础 -- 3、协议和扩展、错误处理、范型
目录
- 1、协议和扩展
- 2、错误处理
- 3、泛型
1、协议和扩展
使用 protocol
来声明一个协议。
protocol ExampleProtocol {var simpleDescription: String { get }mutating func adjust()
}
类、枚举和结构体都可以遵循协议。
class SimpleClass: ExampleProtocol {var simpleDescription: String = "A very simple class."var anotherProperty: Int = 69105func adjust() {simpleDescription += " Now 100% adjusted."}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescriptionstruct SimpleStructure: ExampleProtocol {var simpleDescription: String = "A simple structure"mutatingfunc adjust() {simpleDescription += " (adjusted)"}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
练习: 给 ExampleProtocol
增加一个要求。你需要怎么修改 SimpleClass
和 SimpleStructure
才能保证它们仍旧遵循这个协议?
注意在 SimpleStructure
的声明中,使用了 mutating
关键字来标记那些会修改结构体的方法。而 SimpleClass
的声明中不需要将其方法标记为 mutating
,因为类的方法总是可以修改类本身。
可以使用 extension
为已有的类型添加新功能,比如新的方法和计算属性。扩展(extension)还可以为在其他地方声明的类型添加需要遵循的协议,包括那些从库或框架中导入的类型。
extension Int: ExampleProtocol {var simpleDescription: String {return "The number \(self)"}mutating func adjust() {self += 42}}
print(7.simpleDescription)
// 打印“The number 7”
练习: 给 Double
类型写一个扩展,添加一个 absoluteValue
属性。
你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都遵循同一个协议的对象集合。当你处理的是一个封装的协议类型时,协议外定义的方法不可用。
let protocolValue: any ExampleProtocol = a
print(protocolValue.simpleDescription)
// 打印“A very simple class. Now 100% adjusted.”
// print(protocolValue.anotherProperty) // 去掉注释可以看到错误
尽管变量 protocolValue
的运行时类型是 SimpleClass
,但编译器还是会将其视为 ExampleProtocol
类型。这意味着你不能访问在协议之外的方法或者属性。
2、错误处理
你可以使用任何遵循 Error
协议的类型来表示错误。
enum PrinterError: Error {case outOfPapercase noTonercase onFire
}
使用 throw
来抛出错误,并使用 throws
标记可能抛出错误的函数。如果在函数中抛出错误,函数会立即返回,并由调用该函数的代码来处理这个错误。
func send(job: Int, toPrinter printerName: String) throws -> String {if printerName == "Never Has Toner" {throw PrinterError.noToner}return "Job sent"
}
处理错误有多种方式,其中一种是使用 do
-catch
。在 do
代码块中,你需要在可以抛出错误的代码前加上 try
。在 catch
代码块中,除非你另外命名,否则错误会被默认命名为 error
。
do {let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")print(printerResponse)
} catch {print(error)
}
// 打印“Job sent”
练习: 将 printer name 改为 "Never Has Toner"
,使 send(job:toPrinter:)
函数抛出错误。
可以提供多个 catch
块来处理特定的错误。和 switch 中 case
的写法一样,在 catch
后写匹配模式。
do {let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")print(printerResponse)
} catch PrinterError.onFire {print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {print("Printer error: \(printerError).")
} catch {print(error)
}
// 打印“Job sent”
练习: 在 do
代码块中添加抛出错误的代码。你需要抛出哪种错误使其被第一个 catch
块处理?对于第二个和第三个 catch
块,又需要抛出哪种错误呢?
另一种处理错误的方式是使用 try?
将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为 nil
。否则,结果会是一个包含函数返回值的可选值。
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
使用 defer
代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用 defer
可以把初始化代码和扫尾代码写在一起,即使它们在不同的时机执行。
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]func fridgeContains(_ food: String) -> Bool {fridgeIsOpen = truedefer {fridgeIsOpen = false}let result = fridgeContent.contains(food)return result
}
if fridgeContains("banana") {print("Found a banana")
}
print(fridgeIsOpen)
// 打印“false”
3、泛型
在尖括号里写一个名字来创建一个泛型函数或者泛型类型。
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {var result: [Item] = []for _ in 0..<numberOfTimes {result.append(item)}return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
你可以为函数和方法创建泛型,也可以为类、枚举和结构体创建泛型。
// 重新实现 Swift 标准库中的可选类型
enum OptionalValue<Wrapped> {case nonecase some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
在代码主体之前使用 where
来指定对类型的一系列要求——例如,要求类型实现特定协议,要求两个类型是相同的,或者要求某个类具有特定的父类。
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> BoolwhereT.Element: Equatable, T.Element == U.Element
{for lhsItem in lhs {for rhsItem in rhs {if lhsItem == rhsItem {returntrue}}}returnfalse
}
anyCommonElements([1, 2, 3], [3])
练习: 修改 anyCommonElements(_:_:)
函数,使其返回一个数组,该数组包含任意两个序列中共有的元素。
<T: Equatable>
和 <T> ... where T: Equatable
的写法是等价的。