Swift 標準庫中有許多 protocols,其中很多看起來貌似很抽象,并且感覺并沒有什么卵用,RawRepresentable 就是其中之一,也許你平時都沒有直接用到它,但事實上,這個協(xié)議有著很重要的意義。
它是什么
首先我們簡單看一下這個東西是用來描述什么問題的,它的定義如下:
public protocol RawRepresentable {
associatedtype RawValue
public init?(rawValue: Self.RawValue)
public var rawValue: Self.RawValue { get }
}
簡單來說呢,遵循這個協(xié)議的類型可以表示另一個類型,并且可以通過 rawValue 這個屬性得到它表示的值。
這時你可能會說

Q: 這。。。這就完了??
A: Exactly.
Q: 就是表示一個值?
A: Exactly.
但是這個協(xié)議有沒有它存在的道理呢?當然有!這也是為什么面向協(xié)議編程是 Swift 的一大核心之一。Swift 這個語言本身的組成也必須要求 RawRepresentable 這個協(xié)議,因為基本類型 enum 就遵循了它。當然還有很多系統(tǒng)類庫的類型,比如 OptionSet 和一些從 Objective-C 遷移過來的 NS_ENUM 都遵循了這個協(xié)議。
Well, How to Use It?
首先我們來寫一個簡單的 OptionSet:
struct Directions: OptionSet {
typealias RawValue = UInt8
var rawValue: UInt8
static let up = Directions(rawValue: 1 << 0)
static let down = Directions(rawValue: 1 << 1)
static let left = Directions(rawValue: 1 << 2)
static let right = Directions(rawValue: 1 << 3)
}
由于 OptionSet 也是一個協(xié)議,而這個協(xié)議并沒有指定 RawRepresentable 中的一個 associatedtype RawValue,因此我們用第一行的語句指定:我們這個類型表示了一個 UInt8 類型。由于協(xié)議規(guī)定我們需要有個 rawValue 屬性,所以我們還需要再聲明一個 rawValue 成員變量。
下面我們需要定義幾個選項,使用的方式就是靜態(tài)常量,至于數(shù)值的設(shè)計本文不展開敘述了,大家可以參考位運算相關(guān)文章以及回憶自己高中時學的排列組合。
使用的時候也很方便,我們可以通過 [.up, .left] 或者 .down 來表示一個 Direction 變量,這得益于 Swift 的類型推斷和便利的語法。
One More Thing
是不是覺得好無聊啊,沒關(guān)系,下面來點來自 Apple Sample Code 的干貨。
UserDefaults 這個東西相信大家都用過,但是它的存取需要寫很長的方法調(diào)用,感覺很笨拙。我們?yōu)楹尾挥?subscript 來改造它呢?
在此之前,我們用一用本文中的主角 —— RawRepresentable。蘋果十分不提倡將一些 Name 用 hardcode 的方式來表示,不要直接用 String 表示一個 asset 名稱,不要用 tag 來獲取 IB 中拖拽的 view。這些行為十分影響軟件的可維護性。所以我們將應(yīng)用中的 UserDefaults Key 用某種方式來表示:
struct PreferenceName<Type>: RawRepresentable {
typealias RawValue = String
var rawValue: String
init?(rawValue: PreferenceName.RawValue) {
self.rawValue = rawValue
}
}
這小段代碼很精巧,既通過 rawValue 存儲了 key,同時通過范型在類型中注入了 value 的類型信息。
下面我們擴展 UserDefaults 類
extension UserDefaults {
subscript(key: PreferenceName<Bool>) -> Bool {
set { set(newValue, forKey: key.rawValue) }
get { return bool(forKey: key.rawValue) }
}
subscript(key: PreferenceName<Int>) -> Int {
set { set(newValue, forKey: key.rawValue) }
get { return integer(forKey: key.rawValue) }
}
subscript(key: PreferenceName<Any>) -> Any {
set { set(newValue, forKey: key.rawValue) }
get { return value(forKey: key.rawValue) }
}
}
借助范型,我們將 subscript 用不同類型分開,這樣存取不同類型的值時就不用去取分方法名稱了,是不是很方便呢?
最后,我們寫一個結(jié)構(gòu)體來存儲所有配置項的 Key:
struct PreferenceNames {
static let maxCacheSize = PreferenceName<Int>(rawValue: "MaxCacheSize")
static let badgeType = PreferenceName<Int>(rawValue: "BadgeType")
static let backgroundImageURL = PreferenceName<URL>(rawValue: "BackgroundImageURL")
}
別問我為什么不寫 PreferenceNames 里`,首先不利于擴展,其次 Swift 壓根也不讓你在范型類型里這么干。
然后使用的時候就這樣:
UserDefaults.standard[PreferenceNames.maxCacheSize] = 30
效率是不是瞬間爆炸???蘋果工程師很機智有木有?
P.S. 我覺得有人可能會吐槽我的 Int,為什么一個 badge type 要用 Int 這么奢侈的一個類型,UInt8 不行嗎?其實沒什么影響,反正存到磁盤就是 Plist 二進制了,你用什么類型占用空間都一樣,存取的時候 cast 一下就行了。
Wrap Up
到這里大家應(yīng)該明白了 RawRepresentable 就是為了讓某類數(shù)值能具有更抽象的展現(xiàn)形式,并賦予更多相關(guān)的操作能力。
- EOF -