Swift 2.0增加了一个很厉害的新特性,其名为选项集合(Option Sets),这个特性让我们可以用炒鸡简单的方式来对位掩码进行操作。
位掩码
如果你从未使用过位掩码,你可能会问,这到底是什么鬼?
请容我来稍微解释一下。
假设我们在写一个角色扮演的游戏(比如说传奇,嗯?),游戏的角色可能拥有各种装备,比如盔甲,剑以及头盔等等,你的第一反应可能是用 Bool
属性来对各种装备进行表示,类似下面这样:
var hasSword = true // 裁决之杖
var hasArmor = true // 战神宝甲
var hasHelmet = false // 圣战头盔
其实,还可以有另一种做法,就是定义一个整数并使用它的比特位来进行表示。由于每个比特位只能存储 0 或者 1,可以使用它来对每个装备进行表示,这就是所谓的位掩码。如下图所示:
远古时期的位掩码操作方法
其实操作位掩码对 Swift 来说也不是什么新鲜事了,早在 Swift 1.2 就有一个 RawOptionSetType
类型�用来定义位掩码。不过由于其定义方法过于繁琐,甚至有点反人类,在这里就不进行展示了。如果实在有兴趣,可以自行 Google
,或者直接上官网。
这里只稍微讲一下使用定义好的位掩码:
let inventory: Inventory = .Sword | .Armor
if inventory & .Sword != nil {
println("屠龙在手,天下我有")
}
如果对位操作符用得比较少,这种代码看起来的确会令人比较头疼。如果代码更加复杂,状态更多的话,我们可能得花更多的时间来理解这段代码到底是在进行什么操作。
新型的位掩码操作方法
在 Swift 2.0 的新时代,位掩码的操作方式大大改善了,只因它推出了一个新的 OptionSetType
类型。
要定义位掩码相当简单,只需要定义一个结构体,并让它遵守 OptionSetType
协议就行了:
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)
}
这里声明了一个 rawValue
的属性,这个 Int
类型的属性就是用来存储所有要表示的比特位。同时还使用位移操作定义了三个类型,使用位移操作可以方便地指定整数中的哪个位用来表示哪个属性,而不用手动进行计算。
定义好类型之后,我们可以像使用普通的 Set
集合类型一样来使用它,Swift 在底层会自己使用位掩码来处理,作为使用者,我们不必操心:
var inventory: Inventory = [.Sword, .Shield]
if inventory.contains(.Shield) {
print("屠龙在手,天下我有")
}
这段代码与上面一小节的代码实现了相同的功能。但是这段代码看起来更加简洁明了,同时写起来也更加顺手,我们直接使用了高层的 API 来对底层的比特位进行操作,这种好处是显而易见的。
Show Me The Code
下面我们使用一个小 Demo 来进行下实际操作。
假设我们想用一个类型来表示一个程序员的技能树,类似他有没有自己的个人博客,有没有 GitHub,以及是否有 StackOverflow 的帐号。
打开 Xcode 7,并新建一个 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)
}
再定义一个程序员类型:
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 帐号注册完毕,可以上去提问题了")
} else {
print("你已经有 StackOverflow 账号了,先去回答几个问题吧")
}
}
mutating func signUpGitHub() {
if !possibleSkills.contains(.GitHub) {
possibleSkills.unionInPlace(.GitHub)
print("GitHub 帐号注册完毕,快去骗 star 吧.")
} else {
print("你已经有 GitHub 了,请不要重复注册.")
}
}
}
首先,定义一个 possibleSkills
属性,用来表示这个程序员拥有的技术(现在这货很废材,就只会 LOL),注意我们把这个属性定义成了 var
类型,因为之后我们需要改变它。
接着,我们定义了三个方法,由于要在方法里修改结构体中的属性,所以都得加上 mutating
修饰符。三个方法里都使用了 Set
集合的方法来对程序员的技能进行改变。
接着,来实际使用下这个定义好的类型:
var programmer = Programmer()
programmer.quitLOL()
programmer.signUpGitHub()
programmer.signUpStackOverflow()
这个代码很简单,先实例化一个程序员,然后让他戒掉了 LOL,接着让他去注册了 GitHub 跟 StackOverflow。这货要好好学习,然后当上总经理,出任 CEO 了,迎娶白富美,从此走向人生巅峰,想想还是有点小激动啊。是不是很励志呢,嗯?
完整的 Playgroud
代码可以在 我的GitHub 上下载到。