溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Swift 使用 Option Pattern 如何改善可選項的 API 設計

發(fā)布時間:2020-10-26 15:26:31 來源:億速云 閱讀:142 作者:Leah 欄目:開發(fā)技術(shù)

Swift 使用 Option Pattern 如何改善可選項的 API 設計?相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

SwiftUI 中提供了很多“新穎”的 API 設計思路和 Swift 的使用方式,我們可以進行借鑒,并反過來使用到普通的 Swift 代碼中。PreferenceKey 的處理方式就是其中之一:它通過 protocol 的方式,為子 view 們提供了一套模式,讓它們能將自定義值以類型安全的方式,向上傳到父 view 去。如果有機會,我會再專門介紹 PreferenceKey,但這種設計的模式其實和 UI 無關(guān),在一般的 Swift 里,我們也能使用這種方法來改善 API 設計。

在這篇文章里,我們就來看看要如何做。文中相關(guān)的代碼可以在這里找到。你可以將這些代碼復制到 Playground 中執(zhí)行并查看結(jié)果。

紅綠燈

用一個交通信號燈作為例子。

Swift 使用 Option Pattern 如何改善可選項的 API 設計

作為 Model 類型的 TrafficLight 類型定義了 .stop、.proceed .caution 三種 State,它們分別代表停止、通行和注意三種狀態(tài) (當然,通俗來說就是“紅綠黃”,但是 Model 不應該和顏色,也就是 View 層級相關(guān))。它還持有一個 state 來表示當前的狀態(tài),并在設置時將這個狀態(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 中設定 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 的變化而變更了:

Swift 使用 Option Pattern 如何改善可選項的 API 設計

青色信號

世界很大,有些地方 (比如日本) 會使用傾向于青色,或者實際上應該是綠松色 (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 的記述

Swift 使用 Option Pattern 如何改善可選項的 API 設計

假設我們想要讓 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 的源碼,才能添加這個選項,而且還需要為添加的屬性設置合適的初始值,或者提供額外的 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&#63; T.Value
        &#63;&#63; type.defaultValue
    }
    set {
      options[ObjectIdentifier(type)] = newValue
    }
  }
  
  // ...  
}
  1. 只有滿足 Hashable 的類型,才能作為 options 字典的 key。ObjectIdentifier 通過給定的類型或者是 class 實例,可以生成一個唯一代表該類型和實例的值。它非常適合用來當作 options 的 key。
  2. 通過 key 在 options 中尋找設置的值。如果沒有找到的話,返回默認值 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 上設置 preferredGreenLightColor 來使用這個選項,而且它已經(jīng)不是 TrafficLight 的存儲屬性了。只要不進行設置,它便不會帶來額外的開銷。

light.preferredGreenLightColor = .turquoise

有了 TrafficLightOption,現(xiàn)在想要為 TrafficLight 添加選項時,就不需要對類型本身的代碼進行改動了,我們只需要聲明一個滿足 TrafficLightOption 的新類型,然后為它實現(xiàn)合適的計算屬性就可以了。這大幅增加了原來類型的可擴展性。

總結(jié)

Option Pattern 是一種受到 SwiftUI 的啟發(fā)的模式,它幫助我們在不添加存儲屬性的前提下,提供了一種向已有類型中以類型安全的方式添加“存儲”的手段。

這種模式非常適合從外界對已有的類型進行功能上的添加,或者是自下而上地對類型的使用方式進行改造。這項技術(shù)可以對 Swift 開發(fā)和 API 設計的更新產(chǎn)生一定有益的影響。反過來,了解這種模式,相信對于理解 SwiftUI 中的很多概念,比如 PreferenceKey alignmentGuide 等,也會有所助益。

看完上述內(nèi)容,你們掌握Swift 使用 Option Pattern 如何改善可選項的 API 設計的方法了嗎?如果還想學到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI