SwiftUI:硬件

原創(chuàng):知識點總結性文章
創(chuàng)作不易,請珍惜,之后會持續(xù)更新,不斷完善
個人比較喜歡做筆記和寫總結,畢竟好記性不如爛筆頭哈哈,這些文章記錄了我的IOS成長歷程,希望能與大家一起進步
溫馨提示:由于簡書不支持目錄跳轉,大家可通過command + F 輸入目錄標題后迅速尋找到你所需要的內容

目錄

  • 一、存儲 Memory
    • 1、設備自身的存儲方式
    • 2、代碼在內存中的存儲
    • 3、區(qū)分 Struct 與 Class 用法上的差異
  • 二、CPU 的運作方式
    • 1、中央處理器
    • 2、中央調度系統(tǒng)
  • Demo
  • 參考文獻

一、存儲 Memory

1、設備自身的存儲方式

電子設備,無論是身形較小的 Apple Watch、主流的 iPhone、還是大屏的 Mac, 都具備兩種硬件來存儲信息,這兩種硬件一個叫做硬盤、另一個叫做內存。如下圖所示,硬盤負責永久存儲信息,內存用于臨時存儲信息。

一般來說,當用戶下載你的應用程序后,應用程序中所包含的所有代碼、設計素材等都會被存放在設備的硬盤中。這些信息不會因為設備開關機而丟失,所以硬盤這種存儲介質也被大家稱作永久存儲 Permanent Storage。

與之相對應的,內存中所存放的信息,會在設備關機后徹底丟失,因此也被叫做臨時存儲 Temporary Storage。與用戶硬盤的的較大空間不同,內存的空間則相對較小,以 iPhone 12 Pro 為例,其硬盤的大小可能為 512 GB,而內存則僅有 6 GB。

內存的空間有限,因此僅被用來臨時存放正在運行的應用。比如你的應用叫「睡眠助手」,只有當用戶啟動該應用時,系統(tǒng)才會將「睡眠助手」應用的全部信息從硬盤挪到內存中。若用戶同時在幾個應用程序中來回切換,操作系統(tǒng)會盡可能地讓這些應用都放在內存中。在察覺到內存即將耗盡時,操作系統(tǒng)會自動將用戶最早使用的應用移出內存。


2、代碼在內存中的存儲

進一步講,我們學習的所有 Swift 代碼,比如整數(shù) Int、浮點數(shù) Double、類 Class、數(shù)組 Array、結構 Struct 等,實際都運行在設備的內存中。你可以將內存想象為線性排列且數(shù)量眾多的小格子,我們所編寫的代碼便存儲在格子中。

對于Swift 中的絕大多數(shù)類型,如 Int、Double、Bool、Enum、Struct、Array、Dictionary,其使用內存的方式都很直接,就是直接將信息存儲在下圖的小格子中。對于這些類型的常量或變量來說,其指代便是內存中的信息本身,因此以上類型也被稱作值類型 Value Type。比如下圖中的常量 number,實際存儲的信息便是內存格子中的值 12345。

值類型

然而對于某些特殊的類型,如 Class 的常量或變量,它使用小格子的方法有些區(qū)別。假如我們把內存中的小格子當作一棟公寓樓并為其標上門牌號 A - JClass 的常量或變量存儲的不是小格子中的信息的值,而是信息所對應值的門牌號,因此 Class 也被稱為引用類型 Reference Type。比如下圖中的類實體 civicCar,當把值 Car(brand: "Honda") 放在小格子中后,civicCar 存儲的信息實際上是門牌號 G。

引用類型

總的來說,在 Swift 編程語言中,你看到的絕大多數(shù)類型都是值類型。如 Struct 的實體實際存儲的是內存中對應的值。與之相反,引用類型如 Class,其實體所存儲的是內存中值所對應的門牌號。


3、區(qū)分 Struct 與 Class 用法上的差異

上文中,你了解到了 structclass 分別作為值類型和引用類型在存儲邏輯上的差異。本小節(jié)中,我將使用一個車輛顏色的案例,讓你看看這兩種存儲方式在實際使用中的差異。

如下所示,用 structclass 分別創(chuàng)建新類型車輛顏色 CarColor,其只存儲一個字符串顏色 color。為幫助你區(qū)分二者,我在命名上標注出了其歸屬,其中 struct 創(chuàng)建的新類型叫做 CarColorByStructclass 創(chuàng)建的新類型叫做 CarColorByClass。class 要求我們必須提供初始化器,因此下圖使用 init(color:) 寫明,而 struct 默認自帶這個初始化器,因此沒有寫??偟膩碚f,下面的兩個類型除 structclass 關鍵詞不同外完全一致。

struct CarColorByStruct
{
    var color: String
}

class CarColorByClass
{
    var color: String
    
    init(color: String) { self.color = color}
}
Struct 版本

首先我們先來看值類型 struct 實體的表現(xiàn)。如下所示,創(chuàng)建了一個新實體 carOne,并將其顏色賦值為黑色。之后創(chuàng)建一個新變量 carTwo,并將 carOne 的值賦給 carTwo。最后將 carTwo 的顏色更改為白色 carTwo.color = "白色",因 carTwocarOne 這兩個變量分別代表內存中的兩個格子,互不干擾,因此修改完 carTwo 顏色為白色后,carOne 的顏色仍舊為黑色,沒有被改變。

// carOne 賦值為黑色
var carOne = CarColorByStruct(color: "黑色")
print("carOne 所具備的顏色是: \(carOne.color)")

// 將 carOne 賦值給 carTwo,并修改 carTwo 顏色
var carTwo = carOne
carTwo.color = "白色"

// 修改后,carOne 顏色沒有被改變,仍為黑色
print("carOne 所具備的顏色是: \(carOne.color)")

輸出結果為:

carOne 所具備的顏色是: 黑色
carOne 所具備的顏色是: 黑色

用內存的格子來闡釋上述 struct 代碼的過程如下圖所示。第一步創(chuàng)建了 carOne,其格子中的值為黑色。第二步將 carOne 的值賦給新變量 carTwocarTwo 擁有了自己的格子,值與 carOne 相同。第三步將 carTwo 的顏色修改,對 carOne 沒有影響。

Struct 版本
Class 版本

與上文中完全相同的流程,若采用引用類型的 class 實體,效果如下。在下面中,創(chuàng)建了一個新實體 carThree,并將其顏色設置為黑色。然后將 carThree 的值賦值給 carFour,僅修改 carFour 的顏色為白色后,你會發(fā)現(xiàn) carThree 的顏色也被修改。這是因為 carThreecarFour 存儲的是內存中的同一個門牌號。

// carThree 賦值為黑色
var carThree = CarColorByClass(color: "黑色")
print("carThree 所具備的顏色是: \(carThree.color)")

// 將 carFour 賦值給 carThree,并修改 carFour 顏色
var carFour = carThree
carFour.color = "白色"

// 修改后,carThree 被改變,變?yōu)榘咨?print("carThree 所具備的顏色是: \(carThree.color)")

輸出結果為:

carThree 所具備的顏色是: 黑色
carThree 所具備的顏色是: 白色

用內存的格子來闡釋上述 class 代碼的過程如下圖所示。第一步創(chuàng)建了 carThree,其格子中放置了顏色為黑色。第二步將 carThree 賦值給新變量 carFour,此時被賦值的并非 carThree 的值,而是 carThree 所指代的門牌號 E。第三步將 carFour 的顏色修改,carThree 的值也同時發(fā)生改變。這是因為本質上來說,carThreecarFour 都指的是門牌號 E,修改的是門牌號 E 對應的同一個格子的內容。

Class 版本

上文所述的門牌號,用計算機學科的專業(yè)術語來講,叫做指針 Pointer。鑒于引用類型對比指針的特殊性,Swift 專門給了特殊的運算符用于對比指針。在 Swift 語言中,符號 ==!= 用于判定運算符兩邊的值是否相同,符號 ===!== 用于判斷運算符兩邊的指針是否相同。如下所示,carThreecarFour 指針的指向位置相同。

print("是否指向同一個門牌號:\(carThree === carFour)")

輸出結果為:

是否指向同一個門牌號:true

什么時候選用 struct 或 class?

對于 Apple 官方框架來說,適用于反復使用的框架一般定義為 class。如 Message UI 框架,其用途是提供一個發(fā)送短信或郵件的窗口,定義為 class 的好處便是整個應用程序中只需要使用一個該 class 的實體 instance,程序中用到的所有實體 instance 都可以指向該門牌號,從而避免重復占用過多內存而導致資源浪費。

Message UI

同樣是 Apple 的官方框架,不適合反復使用的實體常被定義為 struct,如 SwiftUI 框架的文本 Text。對于 UI 視圖Text 來說,應用界面中的文本很少相同,且不存在一個文本繼承另一個文本的情況,無需用到繼承的屬性,因此定義為 struct 更合理。

在書寫你的應用的過程中,Apple 官方文檔的建議是當需要創(chuàng)建一個新的自定義類別時,可以先將其定義為 struct。只有你需要用到 class 繼承的特性,或者是作為引用類型的特性時,再將其關鍵詞換為 class。


二、CPU 的運作方式

1、中央處理器

CPU 也叫中央處理器,是 A 系列芯片 Soc 上負責邏輯運算的重要硬件,我們寫的代碼主要通過設備中的 CPU 來運行。設備中的 CPU 通常又進一步分為性能核心和低功耗核心,操作系統(tǒng)會根據應用的實際需求來自動決定其運行場所,以達到最優(yōu)的能耗表現(xiàn)。以下圖中 iPhone 12 上的 A14 芯片為例,它包含 2 個高性能核心,以及 4 個低功耗核心。

A14 芯片

2、中央調度系統(tǒng)

你可以將我們的應用程序想象成無數(shù)個需要完成的子任務,這些任務由操作系統(tǒng)的工作人員,也叫做 GCD 來負責分配。GCD 的全稱是 Grand Central Dispatch,翻譯過來是中央調度系統(tǒng),其任務便是將代碼自動在恰當?shù)臅r機分配給 CPU 中的不同核心來處理。

通常來說,為避免打亂應用程序的運行邏輯,GCD 會讓所有代碼在按其編寫的先后順序 Serial 運行。雖然沒有明說,但目前為止,我們所寫的所有代碼都會被 GCD 運行在「主隊列 main」中。其使用方法是 DispatchQueue.main.async {},如下所示。

DispatchQueue.main.async
{
    print("默認情況下,即使不寫明,所有代碼都運行在這里")
}

將以上信息用圖表的方式展示出來,主隊列有點像銀行排著的長隊,隊伍中的每個人分別肩負著不同任務,這些任務可能是你的一個個函數(shù),也可能是等著要求 CPU 將其存放在內存中的常量與變量等等。隨著時間的推移,GCD 這個工作人員會讓隊伍中的每個人依次在 CPU 那里辦理業(yè)務。

主隊列

這種方法看似非常完美,但還存在一些問題。依舊以銀行為例,有時候會有顧客辦一個特別冗長的貸款業(yè)務,這個業(yè)務可能一辦就是幾個小時,以至于后面排隊的人都被它堵在后面了。比如下圖中的序號 5,辦理的就是這種貸款業(yè)務,把后面要辦理業(yè)務的 6,7,8,9 全都堵住了。

特別冗長的貸款業(yè)務

一個人辦復雜業(yè)務卡住所有人的情況在應用程序中也時有發(fā)生。比如你在制作一款圖片處理應用,你想給圖片加上一個復雜的濾鏡,而濾鏡運算需要時間,因此就會堵住后面的人辦事。比如你在制作一款需要讀取網站文章的客戶端,發(fā)送網絡請求的過程可能會由于網速過慢,而等待很久才能完成。

更可怕的是,應用代碼中的所有 UI 代碼也在主隊列中運行,如果主隊列被某個任務堵住了,應用程序就好像卡死了一樣。這種卡死并非是你的應用罷工了,而是某個任務運行太慢而卡住了主隊列,導致后續(xù) UI 代碼無法運行。然而用戶無法得知你的應用程序是真卡死了還是只是在運行較慢的任務,若他們看見應用程序按鈕沒反應了,就會下意識地認為它壞了。

為解決某個復雜業(yè)務堵住主隊列的問題,GCD 給出的方法是加開銀行窗口。這種加開窗口的辦法與順序 Serial 運行相反,也叫做并發(fā)運行 Concurrent,它指的是在你的明確指示下,讓復雜任務到別的窗口運行,不要卡在主隊伍中。比如讓辦理貸款業(yè)務的客戶去專門的大客戶柜臺辦理。在 Swift 中,默認提供的并發(fā)隊列叫做全局隊列 Global。如下圖中的復雜任務 A 和復雜任務 B,分別交給了兩個新窗口「全局隊列」負責處理。

把任務從主隊列分配給全局隊列很可能只是因為它需要的時間更長而已,不一定意味著該任務不著急執(zhí)行。就像銀行中提供加急業(yè)務一樣,我們不能只是單純地把任務分配出去,還需要考慮任務的重要性。負責告知全局隊列中任務優(yōu)先級的參數(shù)是 Quality of Service,簡稱 QOS。全局隊列的優(yōu)先級 QOS 共有 4 種,如下圖所示。

QOS

QOS 優(yōu)先級從最著急要到慢慢來的排序下:.userInteractive (UI)、.userInitiated (用戶發(fā)起的任務)、.utility (雜項)、.background (后臺)

如果你希望分配到全局隊列的任務與 UI 相關,應將 QOS 設置為 UI 級,也就是 userInteractive。此時 GCD 會將任務分配到高性能核心上盡快完成。反之若該項任務不急著要,比如只是在例行備份一些資料,也可以把 QOS 設置為后臺級 .background 慢慢備份,執(zhí)行代碼如下。這時候 GCD 分工時就會看到開發(fā)者不急著要這里面的結果,因此若需要省電時則這部分內容可以放在低功耗核心上運算。若有其它更緊要的任務則可以把低優(yōu)先級的任務先緩一緩,緊著最著急的來。

DispatchQueue.global(qos: .background).async
{
    print("執(zhí)行復雜任務")
}

Demo

Demo在我的Github上,歡迎下載。
SwiftUIDemo

參考文獻

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者。

相關閱讀更多精彩內容

  • 〇、前言 本文共108張圖,流量黨請慎重! 歷時1個半月,我把自己學習Python基礎知識的框架詳細梳理了一遍。 ...
    Raxxie閱讀 19,582評論 17 410
  • 目錄 1. 計算機早期歷史-Early Computing 11:53[https://www.bilibili...
    weiwei_js閱讀 1,010評論 0 1
  • C++入門基礎 namespace專題講座 namespace概念 所謂namespace,是指標識符的各種可見范...
    蔡俊宇閱讀 874評論 0 2
  • 1. Python 內置數(shù)據結構1.1. 數(shù)值型1.2. math 模塊1.3. round 圓整1.4. 常用的...
    靜堂先生閱讀 398評論 0 1
  • 1.zone的作用是為了防止內存出現(xiàn)碎片化,p142.類的引用計數(shù)統(tǒng)一存在哈希表里,以對象內存為key, p193...
    PerTerbin閱讀 1,096評論 1 1

友情鏈接更多精彩內容