iosSwiftBlogSwift开发

Swfit 2.0之选项集合(Option Sets)

2015-07-11  本文已影响601人  请叫我小锅

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 上下载到。

上一篇下一篇

猜你喜欢

热点阅读