這篇是對一文鑑定是Swift的王者,還是青銅文章中問題的解答。這些問題僅僅是表層概念,屬於知識點,在我看來即使都很清楚也並不能代表上了王者,如果非要用段位類比的話,黃金還是合理的😄。
Swift是一門上手容易,但是精通較難的語言。即使下面這些內容都不清楚也不妨礙你開發業務需求,但是瞭解之後它能夠幫助我們寫出更加Swifty的代碼。
一、 協議 Protocol
ExpressibleByDictionaryLiteral
ExpressibleByDictionaryLiteral
是字典的字面量協議,該協議的完整寫法爲:
public protocol ExpressibleByDictionaryLiteral {
/// The key type of a dictionary literal.
associatedtype Key
/// The value type of a dictionary literal.
associatedtype Value
/// Creates an instance initialized with the given key-value pairs.
init(dictionaryLiteral elements: (Self.Key, Self.Value)...)
}
複製代碼
首先字面量(Literal)的意思是:用於表達源代碼中一個固定值的表示法(notation)。
舉個例子,構造字典我們可以通過以下兩種方式進行:
// 方法一:
var countryCodes = Dictionary<String, Any>()
countryCodes["BR"] = "Brazil"
countryCodes["GH"] = "Ghana"
// 方法二:
let countryCodes = ["BR": "Brazil", "GH": "Ghana"]
複製代碼
第二種構造方式就是通過字面量方式進行構造的。
其實基礎類型基本都是通過字面量進行構造的:
let num: Int = 10
let flag: Bool = true
let str: String = "Brazil"
let array: [String] = ["Brazil", "Ghana"]
複製代碼
而這些都有對應的字面量協議:
ExpressibleByNilLiteral // nil字面量協議
ExpressibleByIntegerLiteral // 整數字面量協議
ExpressibleByFloatLiteral // 浮點數字面量協議
ExpressibleByBooleanLiteral // 布爾值字面量協議
ExpressibleByStringLiteral // 字符串字面量協議
ExpressibleByArrayLiteral // 數組字面量協議
複製代碼
Sequence
Sequence翻譯過來就是序列,該協議的目的是一系列相同類型的值的集合,並且提供對這些值的迭代能力,這裏的迭代可以理解爲遍歷,也即for-in
的能力。可以看下該協議的定義:
protocol Sequence {
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
}
複製代碼
Sequence
又引入了另一個協議IteratorProtocol
,該協議就是爲了提供序列的迭代能力。
public protocol IteratorProtocol {
associatedtype Element
public mutating func next() -> Self.Element?
}
複製代碼
我們通常用for-in
實現數組的迭代:
let animals = ["Antelope", "Butterfly", "Camel", "Dolphin"]
for animal in animals {
print(animal)
}
複製代碼
這裏的for-in
會被編譯器翻譯成:
var animalIterator = animals.makeIterator()
while let animal = animalIterator.next() {
print(animal)
}
複製代碼
Collection
Collection譯爲集合,其繼承於Sequence。
public protocol Collection : Sequence {
associatedtype Index : Comparable
var startIndex: Index { get }
var endIndex: Index { get }
var isEmpty: Bool { get }
var count: Int { get }
subscript(position: Index) -> Element { get }
subscript(bounds: Range<Index>) -> SubSequence { get }
}
複製代碼
是一個元素可以反覆遍歷並且可以通過索引的下標訪問的有限集合,注意Sequence
可以是無限的,Collection
必須是有限的。
Collection
在Sequence
的基礎上擴展了下標訪問、元素個數能特性。我們常用的集合類型Array
,Dictionary
,Set
都遵循該協議。
CustomStringConvertible
這個協議表示自定義類型輸出的樣式。先來看下它的定義:
public protocol CustomStringConvertible {
var description: String { get }
}
複製代碼
只有一個description
的屬性。它的使用很簡單:
struct Point: CustomStringConvertible {
let x: Int, y: Int
var description: String {
return "(\(x), \(y))"
}
}
let p = Point(x: 21, y: 30)
print(p) // (21, 30)
//String(describing: <#T##CustomStringConvertible#>)
let s = String(describing: p)
print(s) // (21, 30)
複製代碼
如果不實現CustomStringConvertible
,直接打印對象,系統會根據默認設置進行輸出。我們可以通過CustomStringConvertible
對這一輸出行爲進行設置,還有一個協議是CustomDebugStringConvertible
:
public protocol CustomDebugStringConvertible {
var debugDescription: String { get }
}
複製代碼
跟CustomStringConvertible
用法一樣,對應debugPrint
的輸出。
Hashable
我們常用的Dictionary
,Set
均實現了Hashable
協議。Hash的目的是爲了將查找集合某一元素的時間複雜度降低到O(1),爲了實現這一目的需要將集合元素與存儲地址之間建議一種儘可能一一對應的關係。
我們再看Hashable`協議的定義:
public protocol Hashable : Equatable {
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
}
public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
複製代碼
注意到func hash(into hasher: inout Hasher)
,Swift 4.2 通過引入 Hasher
類型並採用新的通用哈希函數進一步優化 Hashable
。
如果你要自定義類型實現 Hashable
的方式,可以重寫 hash(into:)
方法而不是 hashValue
。hash(into:)
通過傳遞了一個 Hasher
引用對象,然後通過這個對象調用 combine(_:)
來添加類型的必要狀態信息。
// Swift >= 4.2
struct Color: Hashable {
let red: UInt8
let green: UInt8
let blue: UInt8
// Synthesized by compiler
func hash(into hasher: inout Hasher) {
hasher.combine(self.red)
hasher.combine(self.green)
hasher.combine(self.blue)
}
// Default implementation from protocol extension
var hashValue: Int {
var hasher = Hasher()
self.hash(into: &hasher)
return hasher.finalize()
}
}
複製代碼
Codable
Codable
是可Decodable
和Encodable
的類型別名。它能夠將程序內部的數據結構序列化成可交換數據,也能夠將通用數據格式反序列化爲內部使用的數據結構,大大提升對象和其表示之間互相轉換的體驗。處理的問題就是我們經常遇到的JSON轉模型,和模型轉JSON。
public typealias Codable = Decodable & Encodable
public protocol Decodable {
init(from decoder: Decoder) throws
}
public protocol Encodable {
func encode(to encoder: Encoder) throws
}
複製代碼
這裏只舉一個簡單的解碼過程:
//json數據
{
"id": "1283984",
"name": "Mike",
"age": 18
}
// 定義對象
struct Person: Codable{
var id: String
var name: String
var age: Int
}
// json爲網絡接口返回的Data類型數據
let mike = try! JSONDecoder().decode(Person.self, from: json)
print(mike)
//輸出:Student(id: "1283984", name: "Mike", age: 18)
複製代碼
是不是非常簡單,Codable還支持各種自定義解編碼過程,完全可以取代SwiftyJSON
,HandyJSON
等編解碼庫。
Comparable
這個是用於實現比較功能的協議,它的定義如下:
public protocol Comparable : Equatable {
static func < (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: Self, rhs: Self) -> Bool
static func > (lhs: Self, rhs: Self) -> Bool
}
複製代碼
其繼承於Equatable
,即判等的協議。可以很清楚的理解實現了各種比較的定義就具有了比較的功能。這個不做比較。
RangeReplaceableCollection
RangeReplaceableCollection
支持用另一個集合的元素替換元素的任意子範圍的集合。
看下它的定義:
public protocol RangeReplaceableCollection : Collection where Self.SubSequence : RangeReplaceableCollection {
associatedtype SubSequence
mutating func append(_ newElement: Self.Element)
mutating func insert<S>(contentsOf newElements: S, at i: Self.Index) where S : Collection, Self.Element == S.Element
/* 拼接、插入、刪除、替換的方法,他們都具有對組元素的操作能力 */
override subscript(bounds: Self.Index) -> Self.Element { get }
override subscript(bounds: Range<Self.Index>) -> Self.SubSequence { get }
}
複製代碼
舉個例子,Array支持該協議,我們可以進行如下操作:
var bugs = ["Aphid", "Damselfly"]
bugs.append("Earwig")
bugs.insert(contentsOf: ["Bumblebee", "Cicada"], at: 1)
print(bugs)
// Prints "["Aphid", "Bumblebee", "Cicada", "Damselfly", "Earwig"]"
複製代碼
這裏附一張Swift中Array遵循的協議關係圖,有助於大家理解上面講解的幾個協議之間的關係:
圖像來源:swiftdoc.org/v3.1/type/a…
二、@propertyWrapper
閱讀以下代碼,print 輸出什麼
@propertyWrapper
struct Wrapper<T> {
var wrappedValue: T
var projectedValue: Wrapper<T> { return self }
func foo() { print("Foo") }
}
struct HasWrapper {
@Wrapper var x = 0
func foo() {
print(x) // 0
print(_x) // Wrapper<Int>(wrappedValue: 0)
print($x) // Wrapper<Int>(wrappedValue: 0)
}
}
複製代碼
這段代碼看似要考察對@propertyWrapper
的理解,但是有很多無用內容,導致代碼很奇怪。
@propertyWrapper
的意思就是屬性包裝,它可以將一系列相似的屬性方法進行統一處理。舉個例子,如果我們需要在UserDefaults
中加一個是否首次啓動的值,正常可以這樣處理:
extension UserDefaults {
enum Keys {
static let isFirstLaunch = "isFirstLaunch"
}
var isFirstLaunch: Bool {
get {
return bool(forKey: Keys.isFirstLaunch)
}
set {
set(newValue, forKey: Keys.isFirstLaunch)
}
}
}
複製代碼
如果我們需要加入很多這樣屬性的話,就需要寫大量的get
、set
方法。而@propertyWrapper
的作用就是爲屬性的這種設置提供一個模板寫法,以下是使用屬性包裝的寫法。
@propertyWrapper
struct UserDefaultWrapper<T> {
private let key: String
private let defaultValue: T
init(key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
class UserDefaults {
@UserDefaultWrapper(key: Keys.isFirstLaunch, defaultValue: false)
var isFirstLaunch: Bool
}
複製代碼
@propertyWrapper
約束的對象必須要定義wrappedValue
屬性,因爲該對象包裹的屬性會走到wrappedValue
的實現。
回到實例代碼,定義了wrappedValue
卻並沒有添加任何實現,這是允許的。所以訪問x的時候其實是訪問Wrapper
的wrappedValue
,因爲沒有給出任何實現所以直接打印出0
。而_x
和$x
對應的就是Wrapper
自身。
三、關鍵字
public open
public
open
爲權限關鍵詞。對於一個嚴格的項目來說,精確的最小化訪問控制級別對於代碼的維護來說相當重要的。完整的權限關鍵詞,按權限大小排序如下:
open > public > internal > fileprivate > private
open
權限最大,允許外部module訪問,繼承,重寫。public
允許外部module訪問,但不允許繼承,重寫。internal
爲默認關鍵詞,在同一個module內可以共用。fileprivate
表示代碼可以在當前文件中被訪問,而不做類型限定。private
表示代碼只能在當前作用域或者同一文件中同一類型的作用域中被使用。
這些權限關鍵詞可以修飾,屬性,方法和類型。需要注意:當一個類型的某一屬性要用public修飾時,該類型至少要用public(或者open)權限的關鍵詞修復。可以理解爲數據訪問是分層的,我們爲了獲取某一屬性或方法需要先獲取該類型,所以外層(類型)的訪問權限要滿足大於等於內層(類型、方法、屬性)權限。
static class final
原文中final
跟權限關鍵詞放在一起了,其實是不合理的,就將其放到這裏來討論。
static
靜態變量關鍵詞,來源於C語言。
在Swift中常用語以下場景:
// 僅用於類名前,表示該類不能被繼承。僅支持class類型
final class Manager {
// 單例的聲明
static let shared = Manager()
// 實例屬性,可被重寫
var name: String = "Ferry"
// 實例屬性,不可被重寫
final var lastName: String = "Zhang"
// 類屬性,不可被重寫
static var address: String = "Beijing"
// 類屬性,可被重寫。注意只能作爲計算屬性,而不能作爲存儲屬性
class var code: String {
return "0122"
}
// 實例函數,可被重寫
func download() {
/* code... */
}
// 實例函數,不可被重寫
final func download() {
/* code... */
}
// 類函數,可被重寫
class func removeCache() {
/* code... */
}
// 類函數,不可被重寫
static func download() {
/* code... */
}
}
struct Manager {
// 單例的聲明
static let shared = Manager()
// 類屬性
static var name: String = "Ferry"
// 類函數
static func download() {
/* code... */
}
}
複製代碼
struct
和enum
因爲不能被繼承,所以也就無法使用class
和final
關鍵詞,僅能通過static
關鍵詞進行限定
mutating inout
mutating用於修飾會改變該類型的函數之前,基本都用於struct
對象的修改。看下面例子:
struct Point {
var x: CGFloat
var y: CGFloat
// 因爲該方法改變了struct的屬性值(x),所以必須要加上mutating
mutating func moveRight(offset: CGFloat) {
x += offset
}
func normalSwap(a: CGFloat, b: CGFloat) {
let temp = a
a = b
b = temp
}
// 將兩個值交換,需傳入對象地址。注意inout需要加載類型名前
func inoutSwap(a: inout CGFloat, b: inout CGFloat) {
let temp = a
a = b
b = temp
}
}
var location1: CGFloat = 10
var location2: CGFloat = -10
var point = Point.init(x: 0, y: 0)
point.moveRight(offset: location1)
print(point) //Point(x: 10.0, y: 0.0)
point.normalSwap(a: location1, b: location2)
print(location1) //10
print(location2) //-10
// 注意需帶取址符&
point.inoutSwap(a: &location1, b: &location2)
print(location1) //-10
print(location2) //10
複製代碼
inout
需要傳入取值符,所以它的改變會導致該對象跟着變動。可以再回看上面說的Hashable
的一個協議實現:
func hash(into hasher: inout Hasher) {
hasher.combine(self.red)
hasher.combine(self.green)
hasher.combine(self.blue)
}
複製代碼
只有使用inout
才能修改傳入的hasher的值。
infix operator
infix operator
即爲中綴操作符,還有prefix、postfix後綴操作符。
它的作用是自定義操作符。比如Python裏可以用**
進行冪運算,但是Swift裏面,我們就可以利用自定義操作符來定義一個用**
實現的冪運算。
// 定義中綴操作符
infix operator **
// 實現該操作符的邏輯,中綴需要兩個參數
func ** (left: Double, right: Double) -> Double {
return pow(left, right)
}
let number = 2 ** 3
print(value) //8
複製代碼
同理我們還可以定義前綴和後綴操作符:
//定義階乘操作,後綴操作符
postfix operator ~!
postfix func ~! (value: Int) -> Int {
func factorial(_ value: Int) -> Int {
if value <= 1 {
return 1
}
return value * factorial(value - 1)
}
return factorial(value)
}
//定義輸出操作,前綴操作符
prefix operator <<
prefix func << (value: Any) {
print(value)
}
let number1 = 4~!
print(number1) // 24
<<number1 // 24
<<"zhangferry" // zhangferry
複製代碼
前綴和後綴僅需要一個操作數,所以只有一個參數即可。
關於操作符的更多內容可以查看這裏:Swift Operators。
注意,因爲該文章較早,其中對於操作符的一些定義已經改變。
@dynamicMemberLookup,@dynamicCallable
這兩個關鍵詞我確實沒有用過,看到dynamic
可以知道這兩個特性是爲了讓Swift具有動態性。
@dynamicMemberLookup
中文叫動態查找成員。在使用@dynamicMemberLookup
標記了對象後(對象、結構體、枚舉、protocol),實現了subscript(dynamicMember member: String)
方法後我們就可以訪問到對象不存在的屬性。如果訪問到的屬性不存在,就會調用到實現的 subscript(dynamicMember member: String)
方法,key 作爲 member 傳入這個方法。 舉個例子:
@dynamicMemberLookup
struct Person {
subscript(dynamicMember member: String) -> String {
let properties = ["nickname": "Zhuo", "city": "Hangzhou"]
return properties[member, default: "undefined"]
}
}
//執行以下代碼
let p = Person()
print(p.city) //Hangzhou
print(p.nickname) //Zhuo
print(p.name) //undefined
複製代碼
我們沒有定義Person的city
、nickname
,name
屬性,卻可以用點語法去嘗試訪問它。如果沒有@dynamicMemberLookup
這種寫法會被編譯器檢查出來並報錯,但是加了該關鍵詞編譯器就不會管它是不是存在都予以通過。
@dynamicCallable
struct Person {
// 實現方法一
func dynamicallyCall(withArguments: [String]) {
for item in withArguments {
print(item)
}
}
// 實現方法二
func dynamicallyCall(withKeywordArguments: KeyValuePairs<String, String>){
for (key, value) in withKeywordArguments {
print("\(key) --- \(value)")
}
}
}
let p = Person()
p("zhangsan")
// 等於 p.dynamicallyCall(withArguments: ["zhangsan"])
p("zhangsan", "20", "男")
// 等於 p.dynamicallyCall(withArguments: ["zhangsan", "20", "男"])
p(name: "zhangsan")
// 等於 p.dynamicallyCall(withKeywordArguments: ["name": "zhangsan"])
p(name: "zhangsan", age:"20", sex: "男")
// 等於 p.dynamicallyCall(withKeywordArguments: ["name": "zhangsan", "age": "20", "sex": "男"])
複製代碼
@dynamicCallable
可以理解成動態調用,當爲某一類型做此聲明時,需要實現dynamicallyCall(withArguments:)
或者dynamicallyCall(withKeywordArguments:)
。編譯器將允許你調用併爲定義的方法。
一個動態查找成員變量,一個動態方法調用,帶上這兩個特性Swift就可以變成徹頭徹尾的動態語言了。所以作爲靜態語言的Swift也是可以具有動態特性的。
更多關於這兩個動態標記的討論可以看卓同學的這篇:細說 Swift 4.2 新特性:Dynamic Member Lookup
where
where一般用作條件限定。它可以用在for-in、swith、do-catch中:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for item in numbers where item % 2 == 1 {
print("odd: \(item)") // 將輸出1,3,5,7,9等數
}
numbers.forEach { (item) in
switch item {
case let x where x % 2 == 0:
print("even: \(x)") // 將輸出2,4,6,8等數
default:
break
}
}
複製代碼
where
也可以用於類型限定。
我們可以擴展一個字典的merge函數,它可以將兩個字典進行合併,對於相同的Key
值以要合併的字典爲準。並且該方法我只想針對Key
和Value
都是String
類型的字典使用,就可以這麼做:
// 這裏的Key Value來自於Dictionary中定義的泛型
extension Dictionary where Key == String, Value == String {
//同一個key操作覆蓋舊值
func merge(other: Dictionary) -> Dictionary {
return self.merging(other) { _, new in new }
}
}
複製代碼
@autoclosure
@autoclosure
是使用在閉包類型之前,做的事情就是把一句表達式自動地封裝成一個閉包 (closure)。
比如我們有一個方法接受一個閉包,當閉包執行的結果爲 true
的時候進行打印,分別使用普通閉包和加上autoclosure
的閉包實現:
func logIfTrueNormal(predicate: () -> Bool) {
if predicate() {
print("True")
}
}
// 注意@autoclosure加到閉包的前面
func logIfTrueAutoclosure(predicate: @autoclosure () -> Bool) {
if predicate() {
print("True")
}
}
// 調用方式
logIfTrueNormal(predicate: {3 > 1})
logIfTrueAutoclosure(predicate: 3 > 1)
複製代碼
編譯器會將logIfTrueAutoclosure
函數參數中的3 > 1
這個表達式轉成{3 > 1}
這種尾隨閉包樣式。
那這種寫法有什麼用處呢?我們可以從一個示例中體會一下,在Swift系統提供的幾個短路運算符(即表達式左邊如果已經確定結果,右邊將不再運算)中均採用了@autoclosure
標記的閉包。那??
運算符舉例,它的實現是這樣的:
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T)
rethrows -> T {
switch optional {
case .some(let value):
return value
case .none:
return try defaultValue()
}
}
// 使用
var name: String? = "ferry"
let currentName = name ?? getDefaultName()
複製代碼
因爲使用了@autoclosure
標記閉包,所以??
的defaultValue
參數我們可以使用表達式,又因爲是閉包,所以當name
非空時,直接返回了該值,不會調用getDefaultName()
函數,減少計算。
參考:@AUTOCLOSURE 和 ??,注意因爲Swift版本問題,實例代碼無法運行。
@escaping
@escaping
也是閉包修飾詞,用它標記的閉包被稱爲逃逸閉包,還有一個關鍵詞是@noescape
,用它修飾的閉包叫做非逃逸閉包。在Swift3及之後的版本,閉包默認爲非逃逸閉包,在這之前默認閉包爲逃逸閉包。
這兩者的區別主要在於聲明週期的不同,當閉包作爲參數時,如果其聲明週期與函數一致就是非逃逸閉包,如果聲明週期大於函數就是逃逸閉包。結合示例來理解:
// 非逃逸閉包
func logIfTrueNormal(predicate: () -> Bool) {
if predicate() {
print("True")
}
}
// 逃逸閉包
func logIfTrueEscaping(predicate: @escaping () -> Bool) {
DispatchQueue.main.async {
if predicate() {
print("True")
}
}
}
複製代碼
第二個函數的閉包爲逃逸閉包是因爲其是異步調用,在函數退出時,該閉包還存在,聲明週期長於函數。
如果你無法判斷出應該使用逃逸還是非逃逸閉包,也無需擔心,因爲編譯器會幫你做出判斷。第二個函數,如果我們不聲明逃逸閉包編譯器會報錯,警告我們:Escaping closure captures non-escaping parameter 'predicate'
。當然我們還是應該理解兩者的區別。
四、高階函數
Filter, Map, Reduce, flatmap, compactMap
這幾個高階函數都是對數組對象使用的,我們通過示例去了解他們吧:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// filter 過濾
let odd = numbers.filter { (number) -> Bool in
return number % 2 == 1
}
print(odd) // [1, 3, 5, 7, 9]
//map 轉換
let maps = odd.map { (number) -> String in
return "\(number)"
}
print(maps) // ["1", "3", "5", "7", "9"]
// reduce 累計運算
let result = odd.reduce(0, +)
print(result) // 25
// flatMap 1.數組展開
let numberList = [[1, 2, 3], [4, 5], [[6]]]
let flatMapNumber = numberList.flatMap { (value) in
return value
}
print(flatMapNumber) // [1, 2, 3, 4, 5, [6]]
// flatMap 2.過濾數組中的nil
let country = ["cn", "us", nil, "en"]
let flatMap = country.flatMap { (value) in
return value
}
print(flatMap) //["cn", "us", "en"]
// compactMap 過濾數組中的nil
let compactMap = country.compactMap { (value) in
return value
}
print(compactMap) // ["cn", "us", "en"]
複製代碼
filter,reduce其實很好理解,map、flatMap、compactMap剛開始接觸時確實容易搞混,這個需要多加使用和練習。
注意到flatMap
有兩種用法,一種是展開數組,將二維數組降爲一維數組,一種是過濾數組中的nil
。在Swift4.1版本已經將flatMap
過濾數組中nil的函數標位deprecated
,所以我們過濾數組中nil的操作應該使用compactMap
函數。
參考:Swift 燒腦體操(四) - map 和 flatMap
五、幾個Swift中的概念
柯里化什麼意思
柯里化指的是從一個多參數函數變成一連串單參數函數的變換,這是實現函數式編程的重要手段,舉個例子:
// 該函數返回類型爲(Int) -> Bool
func greaterThan(_ comparer: Int) -> (Int) -> Bool {
return { number in
return number > comparer
}
}
// 定義一個greaterThan10的函數
let greaterThan10 = greaterThan(10)
greaterThan10(13) // => true
greaterThan10(9) // => false
複製代碼
所以柯里化也可以理解爲批量生成一系列相似的函數。
POP
與 OOP
的區別
OOP(object-oriented programming)面向對象編程:
在面向對象編程世界裏,一切皆爲對象,它的核心思想是繼承、封裝、多態。
POP(protocol-oriented programming)面向協議編程:
面向協議編程則主要通過協議,又或叫做接口對一系列操作進行定義。面向協議也有繼承封裝多態,只不過這些不是針對對象建立的。
爲什麼Swift演變成了一門面向協議的編程語言。這是因爲面向對象存在以下幾個問題:
1、動態派發的安全性(這應該是OC的困境,在Swift中Xcode是不可能讓這種問題編譯通過的)
2、橫切關注點(Cross-Cutting Concerns)問題。面向對象無法描述兩個不同事物具有某個相同特性這一點。
3、菱形問題(比如C++中)。C++可以多繼承,在多繼承中,兩個父類實現了相同的方法,子類無法確定繼承哪個父類的此方法,由於多繼承的拓撲結構是一個菱形,所以這個問題有被叫做菱形缺陷(Diamond Problem)。
參考文章:
Any
與AnyObject
區別
AnyObject: 是一個協議,所有class都遵守該協議,常用語跟OC對象的數據轉換。
Any:它可以代表任何型別的類(class)、結構體 (struct)、枚舉 (enum),包括函式和可選型,基本上可以說是任何東西。
rethrows
和 throws
有什麼區別呢?
throws是處理錯誤用的,可以看一個往沙盒寫入文件的例子:
// 寫入的方法定義
public func write(to url: URL, options: Data.WritingOptions = []) throws
// 調用
do {
let data = Data()
try data.write(to: localUrl)
} catch let error {
print(error.localizedDescription)
}
複製代碼
將一個會有錯誤拋出的函數末尾加上throws
,則該方法調用時需要使用try
語句進行調用,用於提示當前函數是有拋錯風險的,其中catch
句柄是可以忽略的。
rethrows
與throws
並沒有太多不同,它們都是標記了一個方法應該拋出錯誤。但是 rethrows
一般用在參數中含有可以 throws
的方法的高階函數中(想一下爲什麼是高階函數?下期給出答案)。
查看map
的方法聲明,我們能同時看到 throws
,rethrows
:
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
複製代碼
不知道你們第一次見到map
函數本體的時候會不會疑惑,爲什麼map
裏的閉包需要拋出錯誤?爲什麼我們調用的時候並沒有用try
語法也可以正常通過?
其實是這樣的,transform
是需要我們定義的閉包,它有可能拋出異常,也可能不拋出異常。Swift作爲類型安全的語言就需要保證在有異常的時候需要使用try去調用,在沒有異常的時候要正常調用,那怎麼兼容這兩種情況呢,這就是rethrows
的作用了。
func squareOf(x: Int) -> Int {return x * x}
func divideTenBy(x: Int) throws -> Double {
guard x != 0 else {
throw CalculationError.DivideByZero
}
return 10.0 / Double(x)
}
let theNumbers = [10, 20, 30]
let squareResult = theNumbers.map(squareOf(x:)) // [100, 400, 9000]
do {
let divideResult = try theNumbers.map(divideTenBy(x:))
} catch let error {
print(error)
}
複製代碼
當我們直接寫let divideResult = theNumbers.map(divideTenBy(x:))
時,編譯器會報錯:Call can throw but is not marked with 'try'
。這樣就實現了根據情況去決定是否需要用try-catch
去捕獲map裏的異常了。
參考:錯誤和異常處理
break return continue fallthough 在語句中的含義(switch、while、for)
這個比較簡單,只說相對特別的示例吧,在Swift的switch語句,會在每個case結束的時候自動退出該switch判斷,如果我們想不退出,繼續進行下一個case的判斷,可以加上fallthough
。