本文為私人學習筆記,僅僅做為記錄使用,詳情內容請查閱 中文官方文檔。
內存安全
Swift 自動管理內存,大部分時候你完全不需要考慮內存訪問的事情。并且在默認情況下,Swift 是安全的,它會阻止你代碼不安全的代碼,例如,Swift 需要你保證變量在使用前完成初始化,數據索引會做越界檢測等。
這里討論的是單線程下的內存安全。一般情況下,單線程是不會出現內存訪問下的安全問題。但是在一些特定條件下,依然會發生內存訪問沖突問題。沖突問題會發生在當你有兩個符合下列情況:
- 至少有一個是寫訪問
- 它們訪問的是同一個存儲地址
- 它們的訪問在時間上部分重疊
重疊訪問
導致重疊訪問的主要問題是在使用 in-out
參數的函數或者方法,或者是結構體中的 mutating
方法。
in-out
參數沖突訪問
函數會對它所有的 in-out
參數進行長期寫訪問。in-out
參數的寫訪問會在所有非 in-out
參數處理完成之后開始,直到函數執行完畢為止。如果有多個 in-out
參數,則寫訪問開始的順序與參數的順序一致。
不能在訪問以 in-out
形式傳入后的原始變量。
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize // 訪問原始變量
}
increment(&stepSize)
// 錯誤:stepSize 訪問沖突
沖突的原因是 stepSize
的讀訪問與 number
的寫訪問重疊了。解決的方案就是拷貝一份地址的索引。
// 顯式拷貝
var copyOfStepSize = stepSize
increment(©OfStepSize)
// 更新原來的值
stepSize = copyOfStepSize
// stepSize 現在的值是 2
另外,在多個 in-out
參數時傳入同一個變量同樣容易發生沖突。
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // 正常
balance(&playerOneScore, &playerOneScore)
// 錯誤:playerOneScore 訪問沖突
更多的例子。
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}
extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
oscar.shareHealth(with: &oscar)
// 錯誤:oscar 訪問沖突
因為 Player
是結構體,屬于值類型,修改值的任何一個部分都是對于整個值的修改,這意味著其中的一個屬性的讀寫訪問都需要訪問整個值。和結構體類似的值類型還有 元組、枚舉類型。
下面的代碼展示了一樣的錯誤,對于一個存儲在全局變量里的結構體屬性的寫訪問重疊了。
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // 錯誤
將全局變量改為本地變量,編譯器就可以保證重疊訪問是安全的。
func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // 正常
}
遵循以下原則,可以保證結構體屬性的重疊反問安全。
- 訪問的實例是存儲屬性,而不是計算屬性或類型屬性
- 結構體是本地變量的值,而不是全局變量
- 結構體只能被非逃逸閉包捕獲
訪問控制
模塊和源文件
模塊:每個 target 都會被看作獨立的模塊。如框架和應用程序
源文件:模塊中的源代碼文件
訪問級別
open 和 public
開放級別:包內包外都可以訪問。如框架中的 api。internal
包內級別:只能被包內訪問。一般情況下的默認訪問級別。fileprivate
文件級別:只能在定義的源文件內部訪問。private
私有級別:只能在定義的作用域訪問。
open 針對類和類的成員,它和 public 的區別主要在于 open 限定的類和成員能夠在模塊之外被繼承和重寫。
基本原則
實體不能定義在具有更低級別的實體中。
例如,函數的訪問級別不能高于它的參數類型和返回類型的訪問級別。
實體的訪問級別決定了實體中的所有成員變量的上限。
public class SomePublicClass { // 顯式 public 類
public var somePublicProperty = 0 // 顯式 public 類成員
var someInternalProperty = 0 // 隱式 internal 類成員
fileprivate func someFilePrivateMethod() {} // 顯式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
class SomeInternalClass { // 隱式 internal 類
var someInternalProperty = 0 // 隱式 internal 類成員
fileprivate func someFilePrivateMethod() {} // 顯式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
fileprivate class SomeFilePrivateClass { // 顯式 fileprivate 類
func someFilePrivateMethod() {} // 隱式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
private class SomePrivateClass { // 顯式 private 類
func somePrivateMethod() {} // 隱式 private 類成員
}
- 元組類型
元組的訪問級別由元組中訪問級別最低的類型決定,不能被顯示指定。
- 函數類型
函數的訪問級別根據訪問級別最低的參數類型或者返回類型來決定。
- 枚舉類型
枚舉成員的訪問級別和該枚舉類型相同,不能單獨指定枚舉成員的訪問級別。
原始值或者關聯值的訪問級別不能低于枚舉類型的訪問級別。
- 嵌套類型
嵌套類型的訪問級別和包含它的類型的訪問級別相同。但是 public 除外,在一個 public 的類型中定義嵌套類型,該嵌套類型默認情況下自動擁有 internale
的訪問級別,你可以顯示指定嵌套類型訪問級別為 public
。
- 子類
子類的訪問級別不能高于父類的訪問級別。
但是你可以通過重寫給子類的成員提供更高的訪問級別。
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
// 重寫更改方法的訪問級別
override internal func someMethod() {}
}
- 常量、變量、屬性、下標
常量、變量、屬性不能擁有比它們類型更高的訪問級別,下標也不能擁有比索引類型或返回類型更高的訪問級別。
private var privateInstance = SomePrivateClass()
SomePrivateClass
是私有類,所以變量 privateInstance
必須明確指定訪問級別為私有。
Getter 和 Setter
常量、變量、屬性、下標的 Getters 和 Setters 的訪問級別和它們所屬類型的訪問級別相同。但是 Setter 可以擁有更低的訪問級別,這樣可以控制讀寫權限。
下面例子限制了結構體中 numberOfEdits 寫的訪問級別,只讀。
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
- 構造器
必要構造器的訪問級別和所屬類型的訪問級別相同。便利構造器的訪問級別則不能高于所屬類型的訪問級別,因為便利構造器需要橫向使用到必要構造器。
- 協議
協議中的每個方法或屬性都必須具有和該協議相同的訪問級別,不能將協議中的方法或屬性設置為其他訪問級別。
和其他類型不同,具有
public
訪問級別的協議,其成員也都是public
。而其他類型,如類,訪問級別為public
,其成員的訪問級別除非你顯示指定,否則默認都是internal
。
協議繼承
繼承來的新協議只能擁有比原始協議更低的訪問級別。
協議遵循
一個類型可以遵循比它訪問級別更低的協議,但是所遵循的上下文級別取類型和協議級別最小的那個。如果一個類型是 public 級別,但它要遵循的協議是 internal 級別,那么這個類型對該協議的遵循上下文就是 internal 級別。
當你編寫或擴展一個類型讓它遵循一個協議時,你必須確保該類型對協議的每一個要求的實現,至少與遵循協議的上下文級別一致。例如,一個 public 類型遵循一個 internal 協議,這個類型對協議的所有實現至少都應是 internal 級別的。
- Extension
Extension 的新增成員具有和原始類型成員一致的訪問級別。當然你可以顯示為新增成員指定更低的級別。
在同一文件類,原始類型聲明中的私有成員,可以在多個 extension 中訪問,extension 中私有成員同樣可以在另一個 extension 或原始類型聲明中訪問。因此,你可以通過 extension 來任意組織你的代碼。這一點比 OC 方便得多。
- 泛型
泛型類型或者泛型函數的訪問級別取決于泛型類型或者泛型函數本身的訪問級別,另外還需要結合類型參數的類型約束的訪問級別,選擇最小的訪問級別。
高級運算符
- 位運算符
~
:按位取反,比特位取反。
&
:按位與,兩個數對應位都為 1 時,新數的對應位才為 1。
|
:按位或,兩個數對應位中有任意一個為 1 時,新數的對應位就為 1。
^
:按位異或,當兩個數對應位不相同時,新數的對應位就為 1,并且對應位相同時則為 0。
- 溢出運算符
&+
:溢出加法
&-
:溢出減法
&*
:溢出乘法
- 運算符重載
類和結構體可以為現有的運算符提供自定義的實現,以達到類和結構體進行運算。
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 是一個值為 (-3.0, -4.0) 的 Vector2D 實例
let alsoPositive = -negative
// alsoPositive 是一個值為 (3.0, 4.0) 的 Vector2D 實例
前后綴運算符
要實現前綴或者后綴運算符,需要在聲明運算符函數的時候在 func 關鍵字之前指定 prefix
或者 postfix
修飾符。
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
復合賦值運算符
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original 的值現在為 (4.0, 6.0)
等價運算符
通常情況下,自定義的類和結構體沒有對等價運算符進行默認實現,等價運算符通常被稱為相等運算符(==)與不等運算符(!=)。
與其它中綴運算符一樣, 并且增加對標準庫 Equatable 協議的遵循。
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
}
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
print("These two vectors are equivalent.")
}
// 打印“These two vectors are equivalent.”
多數簡單情況下,您可以使用 Swift 為您提供的等價運算符默認實現。Swift 為以下數種自定義類型提供等價運算符的默認實現:
- 只擁有存儲屬性,并且它們全都遵循 Equatable 協議的結構體
- 只擁有關聯類型,并且它們全都遵循 Equatable 協議的枚舉
- 沒有關聯類型的枚舉
例如:
struct Vector3D: Equatable {
var x = 0.0, y = 0.0, z = 0.0
}
- 自定義運算符
新的運算符要使用 operator
關鍵字在全局作用域內進行定義,同時還要指定 prefix
、infix
或者 postfix
修飾符。
例如:
prefix operator +++