Swift 中如何使用 Option Pattern 改善可選項的 API 設(shè)計
SwiftUI 中提供了很多“新穎”的 API 設(shè)計思路和 Swift 的使用方式,我們可以進行借鑒,并反過來使用到普通的 Swift 代碼中。PreferenceKey
的處理方式就是其中之一:它通過 protocol 的方式,為子 view 們提供了一套模式,讓它們能將自定義值以類型安全的方式,向上傳到父 view 去。如果有機會,我會再專門介紹 PreferenceKey
,但這種設(shè)計的模式其實和 UI 無關(guān),在一般的 Swift 里,我們也能使用這種方法來改善 API 設(shè)計。
在這篇文章里,我們就來看看要如何做。文中相關(guān)的代碼可以在這里找到。你可以將這些代碼復制到 Playground 中執(zhí)行并查看結(jié)果。
紅綠燈
用一個交通信號燈作為例子。
作為 Model 類型的 TrafficLight
類型定義了 .stop
、.proceed
和 .caution
三種 State
,它們分別代表停止、通行和注意三種狀態(tài) (當然,通俗來說就是“紅綠黃”,但是 Model 不應該和顏色,也就是 View 層級相關(guān))。它還持有一個 state
來表示當前的狀態(tài),并在設(shè)置時將這個狀態(tài)通過 onStateChanged
發(fā)送出去:
public class TrafficLight { public enum State { case stop case proceed case caution } public private(set) var state: State = .stop { didSet { onStateChanged?(state) } } public var onStateChanged: ((State) -> Void)? }
其余部分的邏輯和本次主題無關(guān),不過它們也比較簡單。如果你有興趣的話,可以點開下面的詳情查看。但這不影響本文的理解。
TrafficLight 的其他部分
在 (ViewController 中) 使用這個紅綠燈也很簡單。我們按照紅綠黃的顏色,在 onStateChanged 中設(shè)定 view 的顏色:
light = TrafficLight() light.onStateChanged = { [weak self] state in guard let self = self else { return } let color: UIColor switch state { case .proceed: color = .green case .caution: color = .yellow case .stop: color = .red } UIView.animate(withDuration: 0.25) { self.view.backgroundColor = color } } light.start()
這樣,View 的顏色就可以隨著 TrafficLight
的變化而變更了:
青色信號
世界很大,有些地方 (比如日本) 會使用傾向于青色,或者實際上應該是綠松色 (turquoise),來表示“可以通行”。有時候這也是技術(shù)的限制或者進步所帶來的結(jié)果。
The green light was traditionally green in colour (hence its name) though modern LED green lights are turquoise.
– Wikipedia 中關(guān)于 Traffic light 的記述
假設(shè)我們想要讓 TrafficLight
支持青色的綠燈,一個能想到的最簡單的方式,就是在 TrafficLight
里為“綠燈顏色”提供一個選項:
public class TrafficLight { public enum GreenLightColor { case green case turquoise } public var preferredGreenLightColor: GreenLightColor = .green //... }
然后在 ViewController
中使用對應的顏色:
extension TrafficLight.GreenLightColor { var color: UIColor { switch self { case .green: return .green case .turquoise: return UIColor(red: 0.25, green: 0.88, blue: 0.82, alpha: 1.00) } } } light.preferredGreenLightColor = .turquoise light.onStateChanged = { [weak self, weak light] state in guard let self = self, let light = light else { return } // ... // case .proceed: color = .green case .proceed: color = light.preferredGreenLightColor.color }
這樣做當然能夠解決問題,但是也會帶來一些隱患。首先,需要在 TrafficLight
中添加一個額外的存儲屬性 preferredGreenLightColor
,這使得 TrafficLight
示例所使用的內(nèi)存開銷增加了。在上例中,額外的 GreenLightColor
屬性將會為每個實例帶來 8 byte 的開銷。 如果我們需要同時處理很多 TrafficLight
實例,而其中只有很少數(shù)需要 .turquoise
的話,這個開銷就非??上Я?。
嚴格來說,上例的 TrafficLight.GreenLightColor 枚舉其實只需要占用 1 byte。但是 64-bit 系統(tǒng)中在內(nèi)存分配中的最小單位是 8 bytes。
如果想要添加的屬性不是像例子中這樣簡單的 enum,而是更加復雜的帶有多個屬性的類型的話,這一開銷會更大。
另外,如果我們還要添加其他屬性,很容易想到的方法是繼續(xù)在 TrafficLight
上加入更多的存儲屬性。這其實是很沒有擴展性的方法,我們并不能在 extension 中添加存儲屬性:
// 無法編譯 extension TrafficLight { enum A { case a } var myOption: A = .a // Extensions must not contain stored properties }
需要修改 TrafficLight
的源碼,才能添加這個選項,而且還需要為添加的屬性設(shè)置合適的初始值,或者提供額外的 init 方法。如果我們不能直接修改 TrafficLight
的源碼 (比如這個類型是別人的代碼,或者是被封裝到 framework 里的),那么像這樣的添加選項的方式其實是無法實現(xiàn)的。
Option Pattern
可以用 Option Pattern 來解決這個問題。在 TrafficLight
中,我們不去提供專用的 preferredGreenLightColor
,而是定義一個泛用的 options
字典,來將需要的選項值放到里面。為了限定能放進字典中的值,新建一個 TrafficLightOption
協(xié)議:
public protocol TrafficLightOption { associatedtype Value /// 默認的選項值 static var defaultValue: Value { get } }
在 TrafficLight
中,加入下面的 options
屬性和下標方法:
public class TrafficLight { // ... // 1 private var options = [ObjectIdentifier: Any]() public subscript<T: TrafficLightOption>(option type: T.Type) -> T.Value { get { // 2 options[ObjectIdentifier(type)] as? T.Value ?? type.defaultValue } set { options[ObjectIdentifier(type)] = newValue } } // ... }
- 只有滿足
Hashable
的類型,才能作為options
字典的 key。ObjectIdentifier
通過給定的類型或者是 class 實例,可以生成一個唯一代表該類型和實例的值。它非常適合用來當作options
的 key。 - 通過 key 在
options
中尋找設(shè)置的值。如果沒有找到的話,返回默認值type.defaultValue
。
現(xiàn)在,對 TrafficLight.GreenLightColor
進行擴展,讓它滿足 TrafficLightOption
。如果 TrafficLight
已經(jīng)被打包成 framework,我們甚至可以把這部分代碼從 TrafficLight
所在的 target 中拿出來:
extension TrafficLight { public enum GreenLightColor: TrafficLightOption { case green case turquoise public static let defaultValue: GreenLightColor = .green } }
我們將 defaultValue
聲明為了 GreenLightColor
類型,這樣TrafficLightOption.Value
的類型也將被編譯器推斷為 GreenLightColor
。
最后,為這個選項提供 setter 和 getter:
extension TrafficLight { public var preferredGreenLightColor: TrafficLight.GreenLightColor { get { self[option: GreenLightColor.self] } set { self[option: GreenLightColor.self] = newValue } } }
現(xiàn)在,你可以像之前那樣,通過直接在 light
上設(shè)置 preferredGreenLightColor
來使用這個選項,而且它已經(jīng)不是 TrafficLight
的存儲屬性了。只要不進行設(shè)置,它便不會帶來額外的開銷。
light.preferredGreenLightColor = .turquoise
有了 TrafficLightOption
,現(xiàn)在想要為 TrafficLight
添加選項時,就不需要對類型本身的代碼進行改動了,我們只需要聲明一個滿足 TrafficLightOption
的新類型,然后為它實現(xiàn)合適的計算屬性就可以了。這大幅增加了原來類型的可擴展性。
總結(jié)
Option Pattern 是一種受到 SwiftUI 的啟發(fā)的模式,它幫助我們在不添加存儲屬性的前提下,提供了一種向已有類型中以類型安全的方式添加“存儲”的手段。
這種模式非常適合從外界對已有的類型進行功能上的添加,或者是自下而上地對類型的使用方式進行改造。這項技術(shù)可以對 Swift 開發(fā)和 API 設(shè)計的更新產(chǎn)生一定有益的影響。反過來,了解這種模式,相信對于理解 SwiftUI 中的很多概念,比如 PreferenceKey
和 alignmentGuide
等,也會有所助益。
以上就是Swift 中如何使用 Option Pattern 改善可選項的 API 設(shè)計的詳細內(nèi)容,更多關(guān)于Swift 改善api設(shè)計的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
swift 3.0中實現(xiàn)字符串截取、比較的方法示例
時,為了使用現(xiàn)有的字符串生成一個新的字符串,我們可以使用截取字符串的方法實現(xiàn)。下面這篇文章主要給大家介紹了關(guān)于swift 3.0中實現(xiàn)字符串截取的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒。2017-10-10Swift HTTP加載請求Loading Requests教程
這篇文章主要為大家介紹了Swift HTTP加載請求Loading Requests教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02詳解Swift中的數(shù)據(jù)類型類型轉(zhuǎn)換
Swift中的類型轉(zhuǎn)換可以結(jié)合類的繼承等面向?qū)ο蟮木幊烫匦詠磉M行,本文中我們就來詳解Swift中的數(shù)據(jù)類型類型轉(zhuǎn)換,需要的朋友可以參考下2016-07-07Flutter iOS開發(fā)OC混編Swift動態(tài)庫和靜態(tài)庫問題填坑
這篇文章主要為大家介紹了Flutter iOS OC 混編 Swift動態(tài)庫和靜態(tài)庫問題填坑詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07Swift如何調(diào)用Objective-C的可變參數(shù)函數(shù)詳解
這篇文章主要給大家介紹了關(guān)于Swift如何調(diào)用Objective-C的可變參數(shù)函數(shù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用swift具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2018-03-03