枚舉(Enumerations)
枚舉在Swift里面得到了很大的拓展, 使其變得更加簡(jiǎn)單, 易用且強(qiáng)大.
- 枚舉語法(Enumeration Syntax)
與ObjC一樣, 枚舉通過enum來聲明, 例如:
enum CompassPoint {
case North
case South
case East
case West
}
// 如果想簡(jiǎn)單點(diǎn), 寫成一行也是可以的
enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
獲得枚舉值也是很簡(jiǎn)單, 而且蘋果還特意進(jìn)行了一些簡(jiǎn)化:
var directionToHead = CompassPoint.West
directionToHead = .East // 根據(jù)類型就已經(jīng)知道了East就是CompassPoint的East
如果你夠好奇的話, 肯定已經(jīng)猜到了這個(gè)簡(jiǎn)化還是類型推導(dǎo)的功勞, 下面的代碼可以驗(yàn)證:
var directionToHead: CompassPoint = .East
- 用Switch語句來匹配枚舉值(Matching Enumeration Values with a Switch Statement)
直接看代碼應(yīng)該會(huì)更清晰:
directionToHead = .South
switch directionToHead {
case .North:
print("Lots of planets have a north")
case .South:
print("Watch out for penguins")
case .East:
print("Where the sun rises")
case .West:
print("Where the skies are blue")
}
// 之前說過的, switch必須要涵蓋全部范圍, 所以除了枚舉外, 一般都要有default,
// 因?yàn)槲覀兌x的枚舉是可以窮舉的, 所以全部寫上就不會(huì)報(bào)錯(cuò)了,
// 個(gè)人還是建議對(duì)枚舉沒有特殊要求不要加default, 這樣編譯器會(huì)強(qiáng)制我們處理所有的分支,
// 如果以后加了一個(gè)枚舉值, 而且這個(gè)枚舉用的比較多, 可以由編譯器進(jìn)行提醒
- 關(guān)聯(lián)值(Associated Values)
簡(jiǎn)單說來, 就是Swift里面的枚舉并不單單是一個(gè)值可以, 它還可以關(guān)聯(lián)(我感覺叫存儲(chǔ)還容易理解些)一些別的值, 比如WWDC里面的例子:
enum TrainStatus {
case OnTime
case Delay(Int) // 晚點(diǎn)了幾分鐘
}
var status = TrainStatus.Delay(10) // 晚點(diǎn)了10分鐘
那么假如要取這個(gè)值怎么辦呢? 繼續(xù)看:
```
switch status {
case .OnTime:
print("The train is ontime")
case .Delay(let minutes):
print("Delayed (minutes) minutes")
}
// 如果有多個(gè)參數(shù), 常規(guī)寫法是.Delay(let minutes, let station, let reason):
// 所以蘋果又幫我們簡(jiǎn)化了, 可以直接 case let .Delay(minutes, station, reason):
4. 原始值(Raw Values)
和別的語言不同, Swift不會(huì)對(duì)默認(rèn)對(duì)枚舉進(jìn)行一個(gè)"賦初值"的動(dòng)作, 也就是說, CompassPoint.North和0并不是天然綁定的, 除非我們自己手動(dòng)寫上. 例如:
enum CompassPoint: Int { // 注意這里要寫類型了的
case North = 1 // 后面的不寫就遞增, 當(dāng)然了, 僅限數(shù)值類型的(Double, Float也行)
case South // 第一個(gè)都不寫就是0
case East // 如果是String的話, 默認(rèn)就是其字面值, 如.South就是"South"
case West // 其它類型都是全部指定原始值
}
如果有了原始值, 那么我們就能通過原始值來獲取枚舉了, 例如:
let direction = CompassPoint(rawValue: 1) // .North
// 注意:如果你的rawValue超過了枚舉范圍, 就會(huì)返回nil, 所以這個(gè)方式初始化出來的枚舉值是Optional的, 使用前要拆包
5. 遞歸枚舉(Recursive Enumerations)
聽起來貌似很負(fù)責(zé)的樣子, 為此蘋果又來了一個(gè)關(guān)鍵字叫, indirect case(必須搭配case或者enum使用, 單獨(dú)一個(gè)indirect是不成關(guān)鍵字的). 其實(shí)它的本質(zhì)就是如果我在枚舉里面還要使用這個(gè)枚舉類型怎么辦? 回顧一下C語言實(shí)現(xiàn)鏈表的時(shí)候, struct Node里面還有struct Node, 所有我們的next要用struct Node *的形式. 同樣的, 這里的枚舉也會(huì)遇到這個(gè)問題, 但是Swift已經(jīng)沒有指針了, 自然就要引入新的關(guān)鍵字了.
直接看官網(wǎng)的例子吧:
enum ArithmeticExpression {
case Number(Int)
// 如果去掉indirect就會(huì)報(bào)錯(cuò), 提示你加入
indirect case Addition(ArithmeticExpression, ArithmeticExpression)
indirect case Multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 當(dāng)然, 多個(gè)indirect是可以簡(jiǎn)化的, 直接寫在enum前面, 告知一下
indirect enum ArithmeticExpression {
case Number(Int)
case Addition(ArithmeticExpression, ArithmeticExpression)
case Multiplication(ArithmeticExpression, ArithmeticExpression)
}
使用起來的大概是這樣的, 感受一下:
func evaluate(expression: ArithmeticExpression) -> Int {
switch expression {
case .Number(let value):
return value
case .Addition(let left, let right):
return evaluate(left) + evaluate(right)
case .Multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
// evaluate (5 + 4) * 2
let five = ArithmeticExpression.Number(5)
let four = ArithmeticExpression.Number(4)
let sum = ArithmeticExpression.Addition(five, four)
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2))
print(evaluate(product))
// prints "18"
這個(gè)東西, 一般用的可能不多, 但是要用的時(shí)候要是發(fā)現(xiàn)編不過還是很郁悶的, 順便提一句, Swift里面貌似不能用struct來實(shí)現(xiàn)鏈表了, 試過給struct加indirect會(huì)報(bào)錯(cuò), 哈哈, 不過可以用class...如果有方法可以用struct實(shí)現(xiàn), 請(qǐng)一定要指點(diǎn)我一下...
6. 枚舉可以有運(yùn)算屬性(Computed Properties)和函數(shù)
在Swift里面, 屬性分為兩種, 一種叫存儲(chǔ)屬性(Store Properties), 其實(shí)就是我們一般的屬性, 一種叫運(yùn)算屬性, 二者的區(qū)別, 我總結(jié)起來就是看有沒有實(shí)體, 如果有實(shí)體的話, 就是存儲(chǔ)屬性, 否則就是運(yùn)算屬性, 舉個(gè)例子, 很多類,結(jié)構(gòu)體或者枚舉的description都是運(yùn)算屬性:
enum CompassPoint: Float {
var description:String {
return "value is (self)" // 運(yùn)算屬性分get和set, 目前這種寫法是get, 以后再講set
}
case North
case South
case East
case West
func desc() {
print("value is (self)")
}
}
var cp: CompassPoint = .South
cp.description // 打印出"value is South"
cp.desc() // 打印出"value is South\n"
想象力豐富的同學(xué)肯定想到了, 雖然不能有存儲(chǔ)屬性, 但是我們可以有關(guān)聯(lián)值, 從某種程度上來代替存儲(chǔ)屬性來實(shí)現(xiàn)一些方案也是可以的.
好了枚舉差不多到這里, 具體細(xì)節(jié)參考[官方文檔](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145)
###類與結(jié)構(gòu)體(Classes and Structures)
這一章只對(duì)類和結(jié)構(gòu)體進(jìn)行一些簡(jiǎn)單的介紹, 著重會(huì)介紹蘋果對(duì)使用類還是結(jié)構(gòu)體的一些指導(dǎo)意見.
1. 類與結(jié)構(gòu)體對(duì)比(Comparing Classes and Structures)
二者都能實(shí)現(xiàn)的:
a. 定義存儲(chǔ)屬性
b. 定義方法
c. 定義下標(biāo)(subscripts)來訪問它們的值
d. 定義初始化方法
e. 可以拓展默認(rèn)實(shí)現(xiàn)
f. 可以實(shí)現(xiàn)協(xié)議
和大多數(shù)語言一樣, 下面是只有類能實(shí)現(xiàn)的:
a. 繼承
b. 類型轉(zhuǎn)換允許你在運(yùn)行時(shí)檢查和解釋實(shí)例類型
c. 析構(gòu)方法(Deinitializers)允許你在釋放類實(shí)例持有的一些資源
d. 引用計(jì)數(shù)
2. 基本語法
和其它語言沒有多大區(qū)別, 直接看例子吧:
//定義類和結(jié)構(gòu)體:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
// 獲取二者實(shí)例:
let someResolution = Resolution()
let someVideoMode = VideoMode()
// 訪問屬性:
print("The width of someResolution is (someResolution.width)")
print("The width of someVideoMode is (someVideoMode.resolution.width)")
// 設(shè)置屬性:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now (someVideoMode.resolution.width)")
// 結(jié)構(gòu)體的成員初始化函數(shù):
let vga = Resolution(width: 640, height: 480) // 編譯器自動(dòng)合成
3. 結(jié)構(gòu)體和枚舉值是值類型:
用例子可以證明之:
// 結(jié)構(gòu)體:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
print("cinema is now (cinema.width) pixels wide")
// prints "cinema is now 2048 pixels wide"
print("hd is still (hd.width) pixels wide")
// prints "hd is still 1920 pixels wide"
// 枚舉:
enum CompassPoint {
case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
print("The remembered direction is still .West")
}
// prints "The remembered direction is still .West"
4. 類是引用類型
這個(gè)例子也不需要舉了, 肯定是這樣的, 主要講下接下來的2個(gè)細(xì)節(jié)吧:
a. 全等操作符(===), 也有叫恒等的: 引入這個(gè)東西還是因?yàn)闆]有指針啊, 所以需要一個(gè)操作符來比較2個(gè)實(shí)例是不是同一個(gè)東西, 所以就有了===. 這個(gè)和相等操作符的區(qū)別是, 相等操作符會(huì)比較其內(nèi)容, 而不是地址.
b. 蘋果特意解釋了一些關(guān)于指針的事情: Swift中指向引用類型的常/變量和C語言的指針很類似, 但是卻不是直接指向內(nèi)存中的地址, 并且我們也不需要寫*號(hào)來表示我們要?jiǎng)?chuàng)建一個(gè)引用類型, 引用類型和Swift里面的其它類型的定義是基本一致的. (感覺說了等于沒說, 其實(shí)就是告訴我們, 沒指針了)
5. 類與結(jié)構(gòu)體的選擇(Choosing Between Classes and Structures)
蘋果給出了一系列的指導(dǎo)意見, 如果滿足下面的一個(gè)或者多個(gè)條件, 就考慮用結(jié)構(gòu)體吧:
a. 這個(gè)結(jié)構(gòu)的主要目的是為了封裝一些相對(duì)簡(jiǎn)單的數(shù)據(jù)
b. 這個(gè)結(jié)構(gòu)值被賦值或者被傳遞的時(shí)候, 它被復(fù)制是合理的
c. 這個(gè)結(jié)構(gòu)內(nèi)部的存儲(chǔ)屬性類型也是值類型, 并且它們也期望被復(fù)制而不是被引用
d. 這個(gè)結(jié)構(gòu)不需要從其它類型中集成屬性
下面是一些例子:
a. 幾何圖形: 可能只會(huì)封裝width和height, 它們都是Double類型的
b. 表示范圍: 可能只會(huì)封裝start和length, 它們都是Int類型的
c. 3D坐標(biāo)系: 可能只會(huì)封裝x,y,z 它們都是Double類型的
6. 字符串,數(shù)組和字典的賦值和復(fù)制(Assignment and Copy Behavior for Strings, Arrays, and Dictionaries)
如之前所說, 這三種類型都是值類型, 查看一下定義就知道它們是用結(jié)構(gòu)體實(shí)現(xiàn)的. 需要注意的是, 雖說這三個(gè)值類型在我們的代碼中總是會(huì)被復(fù)制的, 然而, Swift會(huì)很智能地只在真正需要復(fù)制的時(shí)候才會(huì)復(fù)制. Swift會(huì)管理所有的值復(fù)制來優(yōu)化性能, 所以我們不需要不敢賦值, 唯恐影響性能.
對(duì)蘋果的聲明, 我做了一個(gè)測(cè)試, 可惜并沒有得到預(yù)想的結(jié)果, 不知道是不是測(cè)試的姿勢(shì)不對(duì), 也可能是沒開啟優(yōu)化選項(xiàng)? 測(cè)試代碼和結(jié)果如下:
let str = "123"
let str2 = str
print("(str)(str2)")
let str3 = str
let str4 = str
print("(str3)(str4)")
let str5 = str
let str6 = str
print("(str5)_(str6)")
print("======")
上面的代碼, 如果是有優(yōu)化的話, 上面6個(gè)String應(yīng)該只用一份就夠了, 但是我得到的結(jié)果是這樣的:
(lldb) po unsafeAddressOf("123")
? 0x00007fdd58519f10
- pointerValue : 140588646244112
(lldb) po unsafeAddressOf(str)
? 0x00007fdd5860fba0
- pointerValue : 140588647250848 // a
(lldb) po unsafeAddressOf(str2)
? 0x00007fdd5860fba0
- pointerValue : 140588647250848 // a
(lldb) po unsafeAddressOf(str3)
? 0x00007fdd5b9669b0
- pointerValue : 140588701084080 // b
(lldb) po unsafeAddressOf(str4)
? 0x00007fdd5b9669b0
- pointerValue : 140588701084080 // b
(lldb) po unsafeAddressOf(str5)
? 0x00007fdd5b96a310
- pointerValue : 140588701098768
(lldb) po unsafeAddressOf(str6)
? 0x00007fdd58401b70
- pointerValue : 140588645096304
從地址可以看出來, 6份按道理同樣的字符串, 只有2塊區(qū)域被復(fù)用了, 而且只有一次, 說明內(nèi)存里面是有4份一樣是String(如果不算"123"這個(gè)全局變量), 如果有正確的測(cè)試姿勢(shì), 請(qǐng)回復(fù)一下哈. (誤!)
// 2016-3-17更正:
上面用unsafeAddressOf打出不同的地址應(yīng)該是和String實(shí)現(xiàn)的方式有關(guān), 實(shí)際上真正字符串?dāng)?shù)據(jù)存儲(chǔ)的地方在String._core._baseAddress(在Debug模式下, 展開String可以看到), 因此上面這個(gè)例子不能說明蘋果沒有優(yōu)化, 實(shí)際上, 對(duì)于值類型, 蘋果的原則是"copy on write", 也就是說, 只有在真正修改的時(shí)候才會(huì)執(zhí)行copy操作, 可以通過下面的代碼進(jìn)行驗(yàn)證:
let str = "123"
var str2 = str // 這個(gè)時(shí)候可以看到str2._core._baseAddress與str1的相同, 直到...
str2.appendContentsOf("4") // str2._core._baseAddress被修改了
好了, 結(jié)構(gòu)體和類的基本概念就到這, 明天出差, 之后再更新咯~~~
細(xì)節(jié)參考[官方文檔](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-ID82)