1. 程式人生 > >Swift4 學習筆記——基礎篇

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(_:)