Swift 2.0增加了一個(gè)很厲害的新特性,其名為選項(xiàng)集合(Option Sets),這個(gè)特性讓我們可以用炒雞簡(jiǎn)單的方式來對(duì)位掩碼進(jìn)行操作。
位掩碼
如果你從未使用過位掩碼,你可能會(huì)問,這到底是什么鬼?
請(qǐng)容我來稍微解釋一下。
假設(shè)我們?cè)趯懸粋€(gè)角色扮演的游戲(比如說傳奇,嗯?),游戲的角色可能擁有各種裝備,比如盔甲,劍以及頭盔等等,你的第一反應(yīng)可能是用 Bool 屬性來對(duì)各種裝備進(jìn)行表示,類似下面這樣:
var hasSword = true // 裁決之杖
var hasArmor = true // 戰(zhàn)神寶甲
var hasHelmet = false // 圣戰(zhàn)頭盔
其實(shí),還可以有另一種做法,就是定義一個(gè)整數(shù)并使用它的比特位來進(jìn)行表示。由于每個(gè)比特位只能存儲(chǔ) 0 或者 1,可以使用它來對(duì)每個(gè)裝備進(jìn)行表示,這就是所謂的位掩碼。如下圖所示:

遠(yuǎn)古時(shí)期的位掩碼操作方法
其實(shí)操作位掩碼對(duì) Swift 來說也不是什么新鮮事了,早在 Swift 1.2 就有一個(gè) RawOptionSetType 類型?用來定義位掩碼。不過由于其定義方法過于繁瑣,甚至有點(diǎn)反人類,在這里就不進(jìn)行展示了。如果實(shí)在有興趣,可以自行 Google,或者直接上官網(wǎng)。
這里只稍微講一下使用定義好的位掩碼:
let inventory: Inventory = .Sword | .Armor
if inventory & .Sword != nil {
println("屠龍?jiān)谑?,天下我?)
}
如果對(duì)位操作符用得比較少,這種代碼看起來的確會(huì)令人比較頭疼。如果代碼更加復(fù)雜,狀態(tài)更多的話,我們可能得花更多的時(shí)間來理解這段代碼到底是在進(jìn)行什么操作。
新型的位掩碼操作方法
在 Swift 2.0 的新時(shí)代,位掩碼的操作方式大大改善了,只因它推出了一個(gè)新的 OptionSetType 類型。
要定義位掩碼相當(dāng)簡(jiǎn)單,只需要定義一個(gè)結(jié)構(gòu)體,并讓它遵守 OptionSetType 協(xié)議就行了:
struct Inventory: OptionSetType {
let rawValue: Int
static let Sword = Inventory(rawValue: 1)
static let Armor = Inventory(rawValue: 1 << 1)
static let Helmet = Inventory(rawValue: 1 << 2)
}
這里聲明了一個(gè) rawValue 的屬性,這個(gè) Int 類型的屬性就是用來存儲(chǔ)所有要表示的比特位。同時(shí)還使用位移操作定義了三個(gè)類型,使用位移操作可以方便地指定整數(shù)中的哪個(gè)位用來表示哪個(gè)屬性,而不用手動(dòng)進(jìn)行計(jì)算。
定義好類型之后,我們可以像使用普通的 Set 集合類型一樣來使用它,Swift 在底層會(huì)自己使用位掩碼來處理,作為使用者,我們不必操心:
var inventory: Inventory = [.Sword, .Shield]
if inventory.contains(.Shield) {
print("屠龍?jiān)谑?,天下我?)
}
這段代碼與上面一小節(jié)的代碼實(shí)現(xiàn)了相同的功能。但是這段代碼看起來更加簡(jiǎn)潔明了,同時(shí)寫起來也更加順手,我們直接使用了高層的 API 來對(duì)底層的比特位進(jìn)行操作,這種好處是顯而易見的。
Show Me The Code
下面我們使用一個(gè)小 Demo 來進(jìn)行下實(shí)際操作。
假設(shè)我們想用一個(gè)類型來表示一個(gè)程序員的技能樹,類似他有沒有自己的個(gè)人博客,有沒有 GitHub,以及是否有 StackOverflow 的帳號(hào)。
打開 Xcode 7,并新建一個(gè) Playground,定義技能類型:
struct Skills: OptionSetType {
let rawValue: Int
static let LOL = Skills(rawValue: 1)
static let GitHub = Skills(rawValue: 1 << 1)
static let PersonalBlog = Skills(rawValue: 1 << 2)
static let StackOverflow = Skills(rawValue: 1 << 3)
}
再定義一個(gè)程序員類型:
struct Programmer {
var possibleSkills: Skills = [.LOL]
mutating func quitLOL() {
if possibleSkills.contains(.LOL) {
print("不要再玩了,快去寫代碼吧")
possibleSkills.subtractInPlace(.LOL)
}
}
mutating func signUpStackOverflow() {
if !possibleSkills.contains(.StackOverflow) {
possibleSkills.unionInPlace(.StackOverflow)
print("StackOverflow 帳號(hào)注冊(cè)完畢,可以上去提問題了")
} else {
print("你已經(jīng)有 StackOverflow 賬號(hào)了,先去回答幾個(gè)問題吧")
}
}
mutating func signUpGitHub() {
if !possibleSkills.contains(.GitHub) {
possibleSkills.unionInPlace(.GitHub)
print("GitHub 帳號(hào)注冊(cè)完畢,快去騙 star 吧.")
} else {
print("你已經(jīng)有 GitHub 了,請(qǐng)不要重復(fù)注冊(cè).")
}
}
}
首先,定義一個(gè) possibleSkills 屬性,用來表示這個(gè)程序員擁有的技術(shù)(現(xiàn)在這貨很廢材,就只會(huì) LOL),注意我們把這個(gè)屬性定義成了 var 類型,因?yàn)橹笪覀冃枰淖兯?/p>
接著,我們定義了三個(gè)方法,由于要在方法里修改結(jié)構(gòu)體中的屬性,所以都得加上 mutating 修飾符。三個(gè)方法里都使用了 Set 集合的方法來對(duì)程序員的技能進(jìn)行改變。
接著,來實(shí)際使用下這個(gè)定義好的類型:
var programmer = Programmer()
programmer.quitLOL()
programmer.signUpGitHub()
programmer.signUpStackOverflow()
這個(gè)代碼很簡(jiǎn)單,先實(shí)例化一個(gè)程序員,然后讓他戒掉了 LOL,接著讓他去注冊(cè)了 GitHub 跟 StackOverflow。這貨要好好學(xué)習(xí),然后當(dāng)上總經(jīng)理,出任 CEO 了,迎娶白富美,從此走向人生巔峰,想想還是有點(diǎn)小激動(dòng)啊。是不是很勵(lì)志呢,嗯?
完整的 Playgroud 代碼可以在 我的GitHub 上下載到。