字符串和字符(Strings and Characters)
- 在新版Swift中, 對String進行了本質(zhì)性的修改, 之前String是字符的集合, 所以, 那個時候可以這樣遍歷字符串:
for c in "hello" {
print(c)
}
現(xiàn)在還這么玩就要報錯了, 原來是字符數(shù)組被放入了一個叫characters的成員變量中, 所以最新的遍歷姿勢是
for c in "hello".characters {
print(c)
}
// 同樣的, String的長度一般由"hello".characters.count來獲取
- 可變數(shù)組:
正如筆記一里面說的, 如果String用var來聲明, 就是可變的, 在Swift里面, 數(shù)組的追加更加方便, 應該是得益于操作符重載的功勞, 如:
var variableString = "Horse"
variableString += " and carriage" // variableString變?yōu)?Horse and carriage"
字符串是值類型:
所謂值類型就是賦值的時候會copy一遍, 賦值一次多一份, 與之對應的是引用類型, 只copy地址, 賦值多少次都只有一份. 在Swift里面, String, Array, Dictionary和Set都是值類型, 這也就是為什么說Swift安全的原因之二.字符串與字符數(shù)組的轉(zhuǎn)換:
二者之前的轉(zhuǎn)換非常自然, String有characters返回字符數(shù)組, 而字符數(shù)組可以通過String的初始化函數(shù)轉(zhuǎn)換為String, 如:
let catCharacters: [Character] = ["C", "a", "t", "!", "??"]
let catString = String(catCharacters)
Unicode編碼:
這一塊內(nèi)容很多, 值得了解, 屬于重要的內(nèi)容但是用的時候不會太多, 可以到時候遇到問題再來翻翻文檔即可.字符串索引
關(guān)于這塊內(nèi)容, 一開始看的時候覺得蘋果設(shè)計的很奇怪, 先看例子吧:
let greeting = "Guten Tag!"
greeting[greeting.startIndex] // startIndex肯定是0啦
// G
greeting[greeting.endIndex.predecessor()] // endIndex是最后的index+1, 這點會讓人比較困惑, 為什么不是最后的index呢
// !
greeting[greeting.startIndex.successor()] // successor從當前往后移一位
// u
let index = greeting.startIndex.advancedBy(7) // 往前移7位
greeting[index]
// 所以下面這樣導致runtime error的:
greeting[greeting.endIndex] // error
greeting.endIndex.successor() // error
一般情況下我們要對String的字符進行訪問, 會直接寫index, 但是這樣會比較危險, 說不定就越界了, 所以蘋果專門有個Index的類型, 還有一些操作的函數(shù)來輔助
字符數(shù)組有個indices的屬性, 所以它的的訪問方式還可以是這樣的:
for index in greeting.characters.indices {
print("\(greeting[index]) ")
}
插入和刪除
如果理解了上面的Index, 那么插入就很常規(guī)了, 可以插入字符, 字符串和字符數(shù)組, 主要還是要操作好index比較字符串
仍然得益于操作符重載, 現(xiàn)在可以直接==來判斷2個字符串相等了, 例如:
let greeting = "hello"
//2016-3-17更正:
//let aGreeting = "hell"+"o" // 這么寫兩個常量實際指向同一份數(shù)據(jù),
var aGreeting = "hell"
aGreeting.appendContentsOf("o") // 這么寫才能保證指向不同的數(shù)據(jù)
for index in greeting.characters.indices {
print("\(greeting[index])", separator: "|", terminator: ".")
}
if greeting == aGreeting {
print("OK")
}
// 用unsafeAddressOf(greeting)和unsafeAddressOf(aGreeting)可以看到2個變量的地址, 發(fā)現(xiàn)并不一樣, 說明的確是進行了字符串內(nèi)容的比較, 而不是比較地址
// 2016-3-17 更正
上面用unsafeAddressOf()函數(shù)獲取地址的方法貌似并不準確, 關(guān)于String的地址, 目前可以通過str._core._baseAddress來觀察, Xcode的Debug查看變量也能看到, 這樣最直觀.
值得一說的是, 因為String里面有Unicode的原因, 所以對Unicode字符的比較會更加科學, 具體的例子參考官方文檔看字符串比較的那一大段
集合類型
數(shù)組, 字典和集合(Set)都是集合類型, 在Swift中CollectionType是一個協(xié)議, 只要你實現(xiàn)了這個協(xié)議, 都算集合, 并且如前文所述, 這些集合都是值類型.
這些集合都支持通用類型(generic types), 可以理解為C++的模板. 在ObjC的最新加的三大特性之一也是這個.
以Array為例子, Set和Dictionary基本上都差不多, 有特別的會單獨點出來.
- 聲明方式:
var someInts = [Int]() // 貌似以前不是這么玩的
print("someInts is of type [Int] with \(someInts.count) items.")
var threeDoubles = [Double](count: 3, repeatedValue: 0.0)// 默認值的方式
var shoppingList: [String] = ["Eggs", "Milk"]
// 字典聲明也差不多:
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
- 操作:
追加什么的都比較常規(guī), 直接看一些得益于操作符重載的用法吧:
shoppingList += ["Baking Powder"]
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// 注意不能直接 += "Apple"
比較有特色的玩法是:
shoppingList[4...6] = ["Bananas", "Apples"]//把index為4,5,6的替換為后面的數(shù)組, 但是前面是3個元素, 后面是2個, 多出的那個呢? 被丟掉了...
// 注意: 左邊多右邊少是可以的, 反過來就錯了
Dictionary里面用下標賦值為nil是不會crash的, 與Objc里面setValue:forKey:一樣會移除原有值(如有), 但是, 調(diào)用updateValue 設(shè)置為nil則會crash, 同時, Dictionary里面取出的值是Optional的, 記得拆包再用
- 遍歷:
和之前字符串那個差不多, 看一下元組在這里的應用:
for (index, value) in shoppingList.enumerate() {
print("Item (index + 1): (value)")
}
// cmd+單擊可以看到enumerate()返回一個EnumerateSequence對象, 再點進去是一個結(jié)構(gòu)體, 發(fā)現(xiàn)有一個next函數(shù), 是這樣的
//func next() -> (index: Int, element: Base.Element)?
這是所有可遍歷對象需要實現(xiàn)的方法, 返回的就是一個元素, 包含index和element
// 同樣Dictionary也可以這么遍歷:
for (airportCode, airportName) in airports {
print("(airportCode): (airportName)")
}
// 如果想單獨遍歷key或者value, .keys和.values可以拯救
4. 排序:
提供了sort(), 調(diào)用即可:
var numberArray = [3,1,4,8,2]
numberArray.sort() // 默認升序
當然還可以自己寫, 然后傳給sort, 這里牽涉到閉包, 會有很多寫法, 最簡單的是:
numberArray.sort(>) // 降序排列
4. 集合關(guān)系
intersect: 2個集合的交集
union: 2個集合的并集
exclusiveOr: 2個集合并集減去交集
subtract: 集合減去與另一集合的交集
官方圖:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/setVennDiagram_2x.png
簡單介紹了下集合類型, 其實里面包含了不少東西, 也牽涉到了蘋果對于Swift的設(shè)計理念--面向協(xié)議編程, 和閉包的東西. 暫時先介紹到這里, 更進一步的可以看看[官方文檔](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-ID105)
###流程控制
1. for循環(huán):
之前已經(jīng)講過一些了, 比如:
for i in 1...5 {
print("(index) times 5 is (index * 5)")
}
//比較有意思的是下劃線(_)在這里的使用
for _ in 1...5 {
// do something five times
}
// 用下劃線代表這個值我不需要
// 當然也保留了傳統(tǒng)的方式, 只是會用的人應該會很少了
for var index = 0; index < 3; ++index {
print("index is (index)")
}
2. while循環(huán):
和別的語言差不多, 有區(qū)別的是引入了一個repeat, 替換掉了do-while, 因為do因為用在do-try-catch里面去了, 解釋好牽強...
3. if條件語句
基本上也差不多, 只是不需要加括號了, 同時判斷的值必須要是實現(xiàn)了BooleanType協(xié)議的, 不然會報錯, 例如:
var i = 1
if i { // 會報錯, 因為Int沒有實現(xiàn)BooleanType
// print("i is not 0")
}
4. switch:
switch在Swift得到了大量的增強, 因為switch涉及到模式匹配, 而Swift的模式匹配十分強大.
4.1 switch不僅僅匹配整數(shù), 還可以匹配字符,字符串, 嚴格說來是任何只要實現(xiàn)了Equatable協(xié)議的對象
4.2 switch的case默認自帶break, 但是如果這一case一行語句都沒有, 是需要手動加上break的. 同時如果你想讓這個case不自動break, 就要加上關(guān)鍵字fallthrough,
4.3 范圍匹配, 以官網(wǎng)例子說明:
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var naturalCount: String
switch approximateCount {
case 0:
naturalCount = "no"
case 1..<5:
naturalCount = "a few"
case 5..<12:
naturalCount = "several"
case 12..<100:
naturalCount = "dozens of"
case 100..<1000:
naturalCount = "hundreds of"
default:
naturalCount = "many"
}
print("There are (naturalCount) (countedThings).")
值得說明的是, 范圍必須包含條件的全部可能的范圍, 簡單來說就是, 如果不是枚舉, 就把default寫上
4.4 元組在switch里面也得到了很廣的使用, 還是用例子來說明:
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
case (_, 0): // 下劃線代表任意值都可以匹配上
print("((somePoint.0), 0) is on the x-axis")
case (0, _):
print("(0, (somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
print("((somePoint.0), (somePoint.1)) is inside the box")
default:
print("((somePoint.0), (somePoint.1)) is outside of the box")
}
4.5 值綁定, 依然例子:
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
print("on the x-axis with an x value of (x)")
case (0, let y):
print("on the y-axis with a y value of (y)")
case let (x, y):
print("somewhere else at ((x), (y))")
}
// 同時還可以加條件判斷, 不過是用where:
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("((x), (y)) is on the line x == y")
case let (x, y) where x == -y:
print("((x), (y)) is on the line x == -y")
case let (x, y):
print("((x), (y)) is just some arbitrary point")
}
5. 流程跳轉(zhuǎn):
其實也就是break, continue, fallthrough, return和throw, 這些關(guān)鍵字都可能打破當前的流程, 跳到別的流程中去, fallthrough已經(jīng)介紹過, 其余的都和別的語言一致
6. 標簽:
與C語言的標簽可以跳來跳去不通, Swift的標簽配合循環(huán)使用, 個人感覺用的范圍應該不多, 有興趣的在官網(wǎng)看下例子就好.
7. guard:
Swift2引入了一個新的關(guān)鍵字, guard, 可以在函數(shù)中對參數(shù)進行一些判斷, 以官網(wǎng)例子來看代碼:
func greet(person: [String: String]) {
guard let name = person["name"] else { // 必須保證有name, 否則就返回
return
}
print("Hello (name)!")
guard let location = person["location"] else { // 必須保證有l(wèi)ocation, 否則就返回
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in (location).")
}
如果一定要問guard和if的區(qū)別是什么, 那么直接援引一個例子來感受一下吧(更多guard優(yōu)于if的例子請[查看原文](http://swift.gg/2015/08/06/swift-guard-better-than-if/)):
struct Person {
let name: String
var age: Int
}
func createPerson() throws -> Person {
guard let age = age, let name = name
where name.characters.count > 0 && age.characters.count > 0 else {
throw InputError.InputMissing
}
guard let ageFormatted = Int(age) else {
throw InputError.AgeIncorrect
}
return Person(name: name, age: ageFormatted)
}
// 如果用if來寫, 會是這樣的:
func createPersonNoGuard() -> Person? {
if let age = age, let name = name
where name.characters.count > 0 && age.characters.count > 0
{
if let ageFormatted = Int(age) {
return Person(name: name, age: ageFormatted)
} else {
return nil
}
} else {
return nil
}
}
需要不停地if-let, 還要寫 else {return nil}, 如果再多一點屬性需要判斷, 就更多的if-let和else{return nil}...
8. 檢查版本可用性
這個是Swift2帶來的大殺器, 在iOS或者OSX版本更迭中, 總有一些新的API出現(xiàn), 也有舊的API廢棄, 如果你的APP需要兼容多個系統(tǒng)版本, 那么勢必要為這些API進行甄別, 但是這樣的成本過高, 必須要對這些API什么時候引入, 什么時候廢除有譜, 或者每次去查看, 這都對APP出現(xiàn)unrecognized selector異常埋下隱患, 所以這是Swift安全的原因之三, 同時, 如果你寫了一些API是高于你在工程中設(shè)置的最低版本的話, 編譯器是會給你報錯, 強制你去判斷的, 我覺得這點非常人性化. 判斷的具體寫法是這樣的:
if #available(iOS 9, OSX 10.10, ) {// 最后的這個是通配符, 主要是為以后拓展做的準備, 假如將來來了一個CarOS或者HouseOS, 那么就能匹配上了
// Use iOS 9 APIs on iOS, and use OS X v10.10 APIs on OS X
} else {
// Fall back to earlier iOS and OS X APIs
}
同時, 如果你的某個類或者某個函數(shù)使用了高版本的API, 那么你就要在前面寫上@available, 這樣編譯器就會為你做檢查了:
@available(iOS 9.2, OSX 10.10.3, *)
class MyClass {
// class definition
}
簡單介紹到這里, 這一篇就算結(jié)束了, 前面基本算都是基礎(chǔ)知識, 慣例, 細節(jié)部分參考[官方文檔](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID120)