Swift4 學習筆記——基礎篇
示例程式碼來源於 《iOS 11 Programming Fundamentals with Swift》
概覽
語句分隔符
Swift的語句是可以通過分析斷句的,如果一個語句結束後換行就開始下一個語句,如果寫了分號也表示結束這個語句,但是有了換行就不需要分號了,寫上也沒有問題。如果一個語句沒有結束,換行沒有實際效果。下邊的程式碼都是合法的:
print("hello")
print("world")
print("hello"); print("world")
print("hello");
print("world");
print(
"world")
註釋
依舊是://和/…/,其中/* … */可以巢狀
物件的型別
Swift中一切都是物件。按照型別劃分,有6種:
- struct, Bool,Int, Double, String, Array,Dictionary等
- enum, Optional型別
class, 使用者自定義型別。這篇文章提到了Swift中有3個預定義的class型別,但是沒有指出是哪個。
protocol,ExpressibleByIntegerLiteral
- tuple,函式返回多值時使用。
- function,print,自定義函式。
舉個例子:
字面變數是物件:
let s = 1.description
#### 值型別和引用型別
按照記憶體管理來劃分,Swift物件有值型別和引用型別,值型別在賦值的時候是copy的(不考慮Swift優化),引用型別是共享記憶體的。
官方文件對值型別和引用型別的解釋:
Types in Swift fall into one of two categories: first, “value types”, where each instance keeps a unique copy of its data, usually defined as a struct, enum, or tuple. The second, “reference types”, where instances share a single copy of the data, and the type is usually defined as a class.
在Swift中,class和function是引用型別,struct,enum,tuple都是值型別。protocol本身不允許有例項,但是採用protocol的可以是struct, enum或者class。
資料型別
變數與常量
let one = 1
var two = 2
使用let宣告的是常量,使用var宣告的是變數
型別推斷
從上面例子可以看出,如果在宣告變數的時候就賦值,有時候是可以不寫型別的,讓編譯器推斷。上文中one和two都是Int型別。
那麼什麼時候需要些型別呢?
- 只宣告,不初始化。
var x : Int
- 想要的型別和推斷的型別不符合
let separator : CGFloat = 2.0
- 不能推斷出型別
let opts : UIViewAnimationOptions = [.autoreverse, .repeat]
- 還有一種情況是提醒自己這個變數是啥型別
let duration : CMTime = track.timeRange.duration
基本型別用法
Bool
- Bool是一個struct型別
- 只有true和false兩個值,不能做它解釋。
Int
- Int是struct型別
- Int的取值在Int.max和Int.min之間,平臺相關
Double
- Double是struct型別
- 64位架構處理器上,Double的精度是15位
- Double的邊界是Double.infinity,還有Double.pi等
- 使用isZero來判斷Double是否為0
數字型別轉換
只有字面變數可以被隱式轉換!
let d : Double = 10
將字面變數10轉換成了Double型別,但是變數就不可以,下列的程式碼不能通過編譯:
let i = 10
let d : Double = i // compile error
正確的寫法是:
let i = 10
let d : Double = Double(i)
String
let str = "Hello World" //歐耶,終於不用寫@了
多行字面變數的寫法:
func f() {
let s = """
Line 1
Line 2
Line 3
"""
// ...
}
func f() {
let s = """
Line "1"
Line 2 \
and this is still Line 2
"""
// ...
}
在String字面變數中使用(…)來計算表示式
let n = 5
let s = "You have \(n) widgets."
String支援+號和+=號
let s = "hello"
let s2 = " world"
let greeting = s + s2
String的utf8編碼:
let s = "\u{BF}Qui\u{E9}n?"、
for i in s.utf8 {
print(i) // 194, 191, 81, 117, 105, 195, 169, 110, 63
}
String和數值的轉化:
let i = 7
let s = String(i) // "7"
let i = 31
let s = String(i, radix:16) // "1f"
Range
Range是一個struct。 字面變數: a…b表示區間[a, b] a..< b表示區間[a, b)
最常見的就是在for迴圈中使用:
for ix in 1...3 {
print(ix) // 1, then 2, then 3
}
Range 有例項方法:
let ix = // ... an Int ...
if (1...3).contains(ix) { // ...
let s = "hello"
let ix2 = s.index(before: s.endIndex)
let s2 = s[..<ix2] // "hell"
Tuple
tuple是一個有序的輕量級的collection。
tuple的宣告:
var pair : (Int, String)
初始化:
var pair : (Int, String) = (1, "Two")
var pair = (1, "Two")
tuple可以同時給多個變數賦值:
let ix: Int
let s: String
(ix, s) = (1, "Two")
tuple在for-in中的應用:
let s = "hello"
for (ix,c) in s.enumerated() {
print("character \(ix) is \(c)")
}
對Tuple中值的引用:
let pair = (1, "Two")
let ix = pair.0 // now ix is 1
如果在宣告的時候給值一個label,可以通過label引用:
let pair : (first:Int, second:String) = (1, "Two")
//or: let pair = (first:1, second:"Two")
var pair = (first:1, second:"Two")
let x = pair.first // 1
pair.first = 2
let y = pair.0 // 2
還可以給Tuple起一個別名
typealias Point = (x:Int, y:Int)
func piece(at p:Point) -> Piece? {
let (i,j) = p
// ... error-checking goes here ...
return self.grid[i][j]
}
可選型別
Swift中變數如果不初始化是不能使用的。這點和OC不同,OC中值型別會有一個預設值,引用型別預設為nil。Swift中如何表示nil呢?答案就是Optional(可選型別)
Optional型別的底層是enum型別,可以包裝一個其他型別,具體內部實現這裡不討論。
比如:
var stringMaybe = Optional("howdy")
就定義了一個包裝了String的Optional型別。包裝不同型別的Opational也是不同的型別,不能互相賦值。Optional(String)型別可以簡寫為String?
如果沒有給Optional的變數裝箱一個值,那麼它就是空的,空的Optional變數可以和nil比較:
var stringMaybe : String? = "Howdy"
print(stringMaybe) // Optional("Howdy")
if stringMaybe == nil {
print("it is empty") // does not print
}
stringMaybe = nilprint(stringMaybe) // nil
if stringMaybe == nil {
print("it is empty") // prints
}
在Swift中nil是一個關鍵字,不是一個值,可以將nil賦值給Optional的型別。
自動裝箱,將一個值直接值給包裝它的Optional型別。
var stringMaybe: String? = "farewell
根據自動裝箱機制,可以在任何需要Optional型別的地方傳入原始型別,但是反過來不行。
let stringMaybe : String? = "howdy"
let upper = stringMaybe.uppercased() // compile error
不能給Optional型別直接傳送訊息,需要拆箱得到原始資料。
拆箱
let stringMaybe : String? = "howdy"
let upper = stringMaybe!.uppercased()
在變數後邊加上歎號,就拆箱得到原始型別。
自動拆箱,在定義變數的時候使用!而不是?就定義了一個自動拆箱的Opational變數,在需要使用原始型別的地方,直接傳入自動解包的Opational變數即可。
func realStringExpecter(_ s:String) {}
var stringMaybe : String! = "howdy"
realStringExpecter(stringMaybe) // no problem
注意,如果自動解包的Optional是nil,會引起Crash。不能給一個是nil的Optional型別解壓,這是Swift最重要的規則之一。 所以,如果不是必須,最好不要使用這個特性,因為這樣就失去了Swift中可選型別的安全特性。
!定義的Optional和?定義的Optional是同一個型別,比如self.view是一個UIView!,但是如下程式碼卻產生編譯錯誤。
var stringMaybe : String! = "howdy"
var anotherStr = stringMaybe //ok
var pureStr: String = stringMaybe //ok
var errStr: String = anotherStr // compile error
stringMaybe是自動拆箱的String?,所以賦值給String型別是可以的;但是anotherStr卻沒有自動拆箱的標誌,僅僅是一個String?,所以不能賦值給String型別。
Optianal Chain是Swift中很重要的一個概念。
拆箱nil會引起Crash,那麼如果每次拆箱都得判斷是否為nil,程式碼就會很難看。於是Swift提供了語法糖:
var stringMaybe : String?
// ... stringMaybe might be assigned a real value here ...
let upper = stringMaybe?.uppercased()
在拆箱的時候,不用!而是用?,這叫做選擇性拆箱。英文很有意思:unwarp the Optional optionally。
選擇性拆箱實際上替你做了判斷工作,就是如果stringMaybe是nil,那麼什麼也不做,如果不是nil,拆箱得到String,然後傳送uppercased訊息。
這很好,但是如果“什麼也不做”返回值upper是啥?答案是nil。那麼nil是不能賦值給String型別的,於是又引入一個規則:
如果一個Optional Chain上有一個可能的Optional的型別(選擇性拆包才有),那麼返回值就是Optional的。
也就是說雖然uppercased方法返回的是String型別,但是因為它在一個Optional Chain中,所以返回值自動被裝箱,成為String?型別。一個Optional Chain返回一個Optional的型別也合情合理。
因為自動裝箱,給一個Opational Chain賦值會比較簡單。
// self is a UIViewController
self.navigationController?.hidesBarsOnTap = true
同樣,如果navigationController是nil,什麼也不會發。那麼如何知道賦值成功了呢?
let ok : Void? = self.navigationController?.hidesBarsOnTap = true
如果ok不是nil,就是賦值成功。
Optional型別是可以和原始型別直接比較的。下邊的程式碼沒有問題。
let s : String? = "Howdy"
if s == "Howdy" { // ... they _are_ equal!
如果s是nil,返回false,如果s不是nil,拆箱之後再和”Howdy”比較。
但是不能比較不等關係,下邊的程式碼是不能通過編譯的:
let i : Int? = 2
if i < 3 { // compile error
因為Swift不能確定如果是nil,結果是什麼。
函式
函式的定義
func sum (_ x:Int, _ y:Int) -> Int {
let result = x + y
return result
}
- func 是keyword,sum是函式名。
- 括號內部是引數,引數標籤,變數名,冒號後是型別。和OC結構一樣。
- -> Int表示返回值是Int型別。如果函式返回Void,可以寫成->()
- 函式體在大括號內部。
- 引數前邊的”_”符號表示忽略引數的標籤。
引數標籤
func echoString(_ s:String, times:Int) -> String {
var result = ""
for _ in 1...times { result += s }
return result
}
times就是引數的外部名字(external name),也可以叫引數標籤。這是和OC語言的引數名字和變數名字分開是一致的。
呼叫的程式碼應該是這樣:
let s = echoString("hi", times:3)
- 預設的,引數的變數名(internal name)就是引數標籤(external name)。
- 如果使用了_,表示沒有標籤,呼叫方也不能使用標籤呼叫了。
- 具有相同的函式簽名,但是引數標籤不同的函式,是兩個不同的函式。
函式引數預設是不可變的。意思是,不能在函式中給函式引數再次賦值。對於引用型別,是可以改變內部屬性的。
func say(_ s:String, times:Int, loudly:Bool) {
loudly = true // compile error
}
如果想要重新給引數賦值需要滿足以下幾個條件:
- 給函式引數新增intout關鍵字
- 傳入的變數應該是var而不是let的
- 傳入變數的地址。
func removeCharacter(_ c:Character, from s: inout String) -> Int {
var howMany = 0
while let ix = s.index(of:c) {
s.remove(at:ix)
howMany += 1
}
return howMany
}
呼叫
var s = "hello"
let result = removeCharacter("l", from:&s)
Swift中函式是first-class object,意思是函式可以賦值給變數,可以作為函式的引數和返回值。
func doThis(_ f:() -> ()) {
f()
}
func whatToDo() {
print("I did it")
}
doThis(whatToDo)
函式是first-class object這一特點可以衍生出很多程式設計模式,裝飾器,偏函式,函式工廠等等。
class, struct & enum
概覽
enum,struct在Swift中和class很像,都可以定義方法,初始化函式等,但是有兩個重大的區別:
- enum,struct是值型別,class是引用型別
- enum,struct不能繼承
在這3種類型中,可以有的結構是:
- 初始化函式。
- 屬性,分為成員屬性和類屬性。對於struct和enum用static關鍵字,對於class用class關鍵字。
- 方法,成員方法和類方法。
- 下標(subscripts)
- 巢狀定義(值型別的不能巢狀自己的型別)。
在Swift中沒有一個像NSObject那樣的公共基類。
class
初始化方法
由於Swift中不允許使用未經初始化的變數,並且想在編譯階段強制的保證這一點。於是對於class型別的初始化,引入了很多規則。雖然規則條數很多,但都是圍繞這一個原則:從初始化函式中返回的物件的所有屬性也是初始化的,並且在初始化完成之前不能使用這個物件。
- 初始化函式必須初始化所有未初始化的屬性
class Dog {
let name : String
let license : Int
init(name:String = "", license:Int = 0) {
self.name = name
self.license = license
}
}
如果刪除self.license = license,將會產生編譯錯誤,因為license沒有初始化。
- 在初始化所有屬性之前,不能使用self
class Cat {
var name : String
var license : Int
init(name:String, license:Int) {
self.name = name
meow() // too soon - compile error
self.license = license
}
func meow() {
print("meow")
}
}
meow()實際上隱式的使用了self,即self.meow()。應該將meow()的呼叫放到最後。
如果初始化函式之間發生呼叫關係,初始化函式就分成了兩類:designated initializer 和convenience initializer。
designated initializer就是能獨立完成物件的初始化的初始化函式,而convenience initializer必須直接或者間接的呼叫designated initializer來完成初始化工作。
class Dog{
var name: String
var age: Int
init(){
self.name = "test"
self.age = 10
}
convenience init(name:String){
self.init(name: name, age: 10)
}
init(name: String, age: Int){
self.name = name
self.age = age
}
}
在class中designated initializer不需要特別指明,但是convenience initializer必須使用convenience關鍵字。(這一條只是對class來講,如果把class換成struct就不需要使用convenience,這和class是能繼承有關係,稍後會介紹到繼承)
這又有一條規則: convenience initializer在使用self之前,必須呼叫designated initializer。
舉個例子:
class Dog{
var name: String
var age: Int
init(){
self.name = "test"
self.age = 10
}
convenience init(name:String){
self.age = 11
self.name = "haha"
self.init(name: name, age: 10)
}
init(name: String, age: Int){
self.name = name
self.age = age
}
}
上邊的程式碼會發生編譯錯誤,因為convenience初始化函式中在self被designated initializer初始化之前就使用了self。從這一點上看,convenience initializer並不是一個真正的初始化函式,只是能提供初始化功能的一般函式。
在高階篇介紹的繼承體系中,會有更復雜的初始化規則。不過如果你違反了這些規則,編譯器都會提示的很清楚。只要理解這些規則的目的都是確保物件被完全初始化即可。
屬性(對struct和class都適用)
在類的屬性全部被初始化完畢之前,不能使用self。
class Moi {
let first = "Matt"
let last = "Neuburg"
let whole = self.first + " " + self.last // compile error
}
對於靜態屬性的使用,在非靜態函式中應該使用類名.屬性,在靜態函式中可以使用self.屬性或者類名.屬性
class Greeting {
static let friendly = "hello there"
static let hostile = "go away"
static var ambivalent : String {
return self.friendly + " but " + self.hostile
}
}
下標(對struct和class都適用)
下標是一種呼叫例項方法的方式。一般在通過整數引數或者String型別的key獲取元素的時候使用下標。
struct Digit {
var number : Int
init(_ n:Int) {
self.number = n
}
subscript(ix:Int) -> Int {
get {
let s = String(self.number)
return Int(String(s[s.index(s.startIndex, offsetBy:ix)]))!
}
}
}
上述程式碼定義了一個通過位數取數字的下標方法,只讀。
var d = Digit(1234)
let aDigit = d[1] // 2
巢狀定義
class Dog {
struct Noise {
static var noise = "woof"
}
func bark() {
print(Dog.Noise.noise)
}
}
注意:struct不能直接或者間接巢狀自己的型別。
Struct
struct大部分特性都和class一致,可以看做是沒有繼承特性的值型別的class。
一些不同:
- 改變struct屬性的方法需要標記為mutating,在enum章節中會有例子。
- 預設的初始化函式可以提供逐一賦值功能(memberwise),只要能保證所有屬性都初始化。
struct Digit {
var number = 42
var number2
}
var d = Digit(number: 3, number2: 34)
var f = Digit() //compile error
- struct 和enum的類方法或者類屬性使用static關鍵字,class可以使用static或者class,static = final class。
enum
enum Filter {
case albums
case playlists
case podcasts
case books
}
let type = Filter.albums
在能根據上下文推斷出enum的型別的時候,可以簡寫成:
let type : Filter = .albums
RawValue
可以給enum指定一個儲存型別,儲存型別只能是數字或者String
enum PepBoy : Int {
case manny
case moe
case jack
}
enum Filter : String {
case albums
case playlists
case podcasts
case books
}
PepBoy中預設從0開始,Filter中預設值就是case的名字。
要獲取enum中相應case的值,使用rawValue屬性
let type = Filter.albums
print(type.rawValue) // albums
可以通過rawValue初始化enum
let type = Filter(rawValue:"Albums")
Swift中的enum可以有初始化方法
enum Filter : String {
case albums = "Albums"
case playlists = "Playlists"
case podcasts = "Podcasts"
case books = "Audiobooks"
static var cases : [Filter] = [.albums, .playlists, .podcasts, .books]
init(_ ix:Int) {
self = Filter.cases[ix]
}
}
上邊的程式碼就可以通過一個Int來初始化一個儲存型別是String的enum。
enum可以有例項方法和類方法
enum Shape {
case rectangle
case ellipse
case diamond
func addShape (to p: CGMutablePath, in r: CGRect) -> () {
switch self {
case .rectangle:
p.addRect(r)
case .ellipse:
p.addEllipse(in:r)
case .diamond:
p.move(to: CGPoint(x:r.minX, y:r.midY))
p.addLine(to: CGPoint(x: r.midX, y: r.minY))
p.addLine(to: CGPoint(x: r.maxX, y: r.midY))
p.addLine(to: CGPoint(x: r.midX, y: r.maxY))
p.closeSubpath()
}
}
}
上邊的程式碼能根據這個enum實際的值,來建立一個圖形。
如果一個enum的例項方法能夠修改這個enum的值,那需要將方法宣告為mutating
enum Filter : String {
case albums = "Albums"
case playlists = "Playlists"
case podcasts = "Podcasts"
case books = "Audiobooks"
static var cases : [Filter] = [.albums, .playlists, .podcasts, .books]
mutating func advance() {
var ix = Filter.cases.index(of:self)!
ix = (ix + 1) % 4
self = Filter.cases[ix]
}
}
原理是這樣的,enum是一個值型別,值型別是不可變的,要改變enum的值,只有再建立一個enum。這個動作在Swift中是需要開發人員顯示指定的。這一條也適用於struct。
Associated Value
在Swift中enum還可以作為C語言中的Union使用。
enum MyError {
case number(Int)
case message(String)
case fatal
}
- 在MyError中不宣告任何儲存型別
- 在每個case後邊用tuple定義型別
MyErrorj就是一個可能儲存Int或者String的資料型別。
let num = 4
let err : MyError = .number(num)
因為Associated Value是動態賦值的,所以Associated Value型別的enum不能使用enum比較。
if err == MyError.fatal { // compile error
因為Swift不知道如何比較,兩個例項的fatal可能關聯了不同的值,那麼到底是相同還是不相同?
集合資料型別
Array
- Array只能儲存一種資料型別,是指宣告為同一種的資料型別,不是實際型別。
- 如果想儲存混合型別的資料,使用[Any],Any是為了和OC互動定義的資料型別。
- 儲存不同型別的Array屬於不同的資料型別。
- Array是值型別,是struct。
儲存Int型別的Array有兩種寫法:
let arr1 = Array<Int>()
let arr2 = [Int]()
可以使用Range:
let arr3 = Array(1...3)
Array有很多初始化函式,比如還可以接受一個集合型別,創建出一個Array
let arr4 = Array("hey".characters)
有一個初始化函式需要注意:init(repeating:count),如果引數是引用型別,那麼Array中的所有元素將指向同一個元素。
class Person {
var name = "123"
}
var p = Person()
let arr5 = Array(repeatElement(p, count: 3))
//[{name "123"}, {name "123"}, {name "123"}]
arr5[1].name = "555"
//[{name "555"}, {name "555"}, {name "555"}]
Array作為一個整體可以型別轉換:
let dog1 : Dog = NoisyDog()
let dog2 : Dog = NoisyDog()
let arr = [dog1, dog2]
let arr2 = arr as! [NoisyDog]
NoisyDog 是 Dog的子類, arr是[Dog]型別,可以時間用as!或者as?轉換為[NoisyDog]型別。
兩個Array相等的條件是Array中的每一個元素相等(注意並沒有要求兩個Array的型別是一樣的)。和其他語言類似,可以自己提供比較函式。
let nd1 = NoisyDog()
let d1 = nd1 as Dog
let nd2 = NoisyDog()
let d2 = nd2 as Dog
if [d1,d2] == [nd1,nd2] { // they are equal!
Array的下標是支援切片的(slicing),切片僅僅是原來Array的一個映像,底層還是引用的是原來的Array
let arr = ["manny", "moe", "jack"]
let slice = arr[1...2] // ["moe", "jack"]
print(slice[1]) // moe
slice是arr的從1到2閉區間的切片,下標也是從1開始,到2結束。==如果引用了下標0,則會產生執行時錯誤==。如果改變了切片中的元素(前提是可以改變),則原來的陣列也會受到影響。
但是,Array不支援負數下標。
Array有一些常用的屬性:
let arr = ["manny", "moe", "jack"]
arr.count
arr.isEmpty
arr.first
arr.last
arr.startIndex
arr.endIndex
//...
判斷元素是否存在:
let arr = [1,2,3]
let ok = arr.contains(2) // true
let ok2 = arr.contains {$0 > 3} // false
let arr = [1,2,3]
let ok = arr.starts(with:[1,2]) // true
let ok2 = arr.starts(with:[1,-2]) {abs($0) == abs($1)} // true
改變Array元素:
var arr = [1,2,3]
arr.append(4)
arr.append(contentsOf:[5,6])
arr.append(contentsOf:7...8) // arr is now [1,2,3,4,5,6,7,8]
var arr = ["manny", "moe", "jack"]
arr.insert("333", at: 1) //["manny", "333", "moe", "jack"]
arr.remove(at: 1) //arr is ["manny", "moe", "jack"]
let arr = [[1,2], [3,4], [5,6]]
let joined = Array(arr.joined(separator:[10,11]))
// [1, 2, 10, 11, 3, 4, 10, 11, 5, 6]
let arr = [1,2,3,4,5,6]
let arr2 = arr.split {$0 % 2 == 0} // split at evens: [[1], [3], [5]]
遍歷Array元素
let pepboys = ["Manny", "Moe", "Jack"]
for pepboy in pepboys {
print(pepboy) // prints Manny, then Moe, then Jack
}
let pepboys = ["Manny", "Moe", "Jack"]
pepboys.forEach {print($0)} // prints Manny, then Moe, then Jack
let pepboys = ["Manny", "Moe", "Jack"]
for (ix,pepboy) in pepboys.enumerated() {
print("Pep boy \(ix) is \(pepboy)") // Pep boy 0 is Manny, etc.
}
// or:
pepboys.enumerated().forEach {print("Pep boy \($0.0) is \($0.1)")}
let pepboys = ["Manny", "Jack", "Moe"]
let arr1 = pepboys.filter{$0.hasPrefix("M")} // ["Manny", "Moe"]
let arr2 = pepboys.prefix{$0.hasPrefix("M")} // ["Manny"]
let arr3 = pepboys.drop{$0.hasPrefix("M")} // ["Jack", "Moe"]
Array和OC的關係
如果一個NSArray沒有任何額外資訊則轉化為[Any],NSArray中的物件都是class型別。把一個Array轉化為NSArray沒有額外的工作要做。
let arr = [UIBarButtonItem(), UIBarButtonItem()]
self.navigationItem.leftBarButtonItems = arr
在Array上呼叫NSArray的方法需要轉換:
let arr = ["Manny", "Moe", "Jack"]
let s = (arr as NSArray).componentsJoined(by:", ")
// s is "Manny, Moe, Jack"
不能把一個Array轉化成一個NSMutableArray。如果需要呼叫NSMutabelArray的方法,使用NSMutableArray的建構函式建立一個。
var arr = ["Manny", "Moe", "Jack"]
let arr2 = NSMutableArray(array:arr)
arr2.remove("Moe")
arr = arr2 as! [String]
在Xcode7以後,有些OC的API提供了額外的型別資訊,比如:
+ (NSArray<NSString *> *)fontNamesForFamilyName:(NSString *)familyName;
這時候返回的值就能直接轉換為String。
Dictionary
Dictionary的語法:
var d : [String:String] = [:]
var d = [String:String]()
var d = ["CA": "California", "NY": "New York"]
兩個Array,一個儲存Key,一個儲存Value,初始化一個Dictionary
let abbrevs = ["CA", "NY"]
let names = ["California", "New York"]
let tuples = zip(abbrevs, names)
let d = Dictionary(uniqueKeysWithValues: tuples)
如果兩個Array長度不同,zip自動忽略額外的部分,保證成對。
從Dictionary中取出來的值是Opational的,因為如果不存在的話會返回nil。可以使用有預設值的方式獲取
let d = ["CA": "California", "NY": "New York"]
let state = d["MD", default:"N/A"] // state is a String (not an Optional)
使用了default關鍵字返回的就是String而不是String?
Dictionary的遍歷:
遍歷key:
var d = ["CA": "California", "NY": "New York"]
for s in d.keys {
print(s) // NY, then CA
}
遍歷key和value:
var d = ["CA": "California", "NY": "New York"]
for (abbrev, state) in d {
print("\(abbrev) stands for \(state)")
}
可以將Dictionary變成一個Tuple的Array:
var d = ["CA": "California", "NY": "New York"]
let arr = Array(d)
// [(key: "NY", value: "New York"), (key: "CA", value: "California")]
和NSDictionary的關係:
NSDictionary對應[AnyHashable: Any],NSDictionary向Swift轉換:
let prog = n.userInfo?["progress"] as? Double
if prog != nil {
self.progress = prog!
}
Swift中使用Cocoa介面:
UINavigationBar.appearance().titleTextAttributes = [
.font: UIFont(name: "ChalkboardSE-Bold", size: 20)!,
.foregroundColor: UIColor.darkText,
.shadow.: {
let shad = NSShadow()
shad.shadowOffset = CGSize(width:1.5,height:1.5)
return shad
}()
]
Set
let set : Set<Int> = [1, 2, 3, 4, 5]
在Swift中Set沒有字面變數,但是可以用Array構建。
在Array中去重:
let arr = [1,2,1,3,2,4,3,5]
let set = Set(arr)
let arr2 = Array(set) // [5, 2, 3, 1, 4], perhaps
insert 和 update,假設Dog的比較函式是name相等。
var set : Set = [Dog(name:"Fido", license:1)]
let d = Dog(name:"Fido", license:2)
set.insert(d) // [Dog(name: "Fido", license: 1)]
set.update(with:d) // [Dog(name: "Fido", license: 2)]
當已經存在的時候,insert不會改變set,update更新set。
兩個set可以使用==比較,相等的條件是每一個元素相等。
求兩個Set的交集:
intersection(_:) 和 formIntersection(_:)
求