swift3學習筆記

一直沒有時間好好看一下swift,最近複習了一遍語法,這裏記錄swift學習過程當中遇到的一些問題和要點,和Object-C的一些相關特性這裏也不作介紹,只記錄swift特有的一些特性
swift借鑑了不少語言的語法,特別是腳本語言,在swift裏,能夠看到python語言的一些影子,還有其餘編程語言的影子html

本篇文章能夠做爲oc到swift3的過渡,能夠當成文檔查python

1、基礎語法

  • swift語句結束不須要分號(寫了也沒有問題),有一種狀況須要分號,若是一行代碼中有多條語句,這時候就必需要分號隔開git

  • swift字符串,數組語法糖,字典語法糖不須要@標示github

  • swift是類型安全的語言,全部的類型都不會自動轉換(如:Int和UInt類型不能直接運算),同時swift具備強大的類型推測,因此不少時候咱們不須要聲明類型express

  • swift的多行註釋支持嵌套編程

    /* 這是第一個多行註釋的開頭
    /* 這是第二個被嵌套的多行註釋 */
    這是第一個多行註釋的結尾 */
  • swift的布爾值使用小寫true和false,判斷語句只能使用Bool類型json

2、數據類型

  • 與objc同樣,swift支持之前(objc)使用的全部數據類型,swift的類型名字首字母大寫,如Int, Float, NSIntegerswift

  • swift支持可選類型(Optionals)類型,至關於C#中的可空類型,標識變量可能爲空,基礎數據類型也可爲空,可選類型不能直接賦非可選類型api

    var a: Int? = 10
    var b: Int = a          // 報錯,不一樣類型不能賦值
  • swift的布爾類型使用true/false,而不用YES/NO數組

  • swift支持使用_來分割數值來加強可讀性而不影響值,如一億能夠表示爲下面形式

    let oneMillion = 1_000_000
  • swift數值類型進行運算符計算的時候不會自動進行類型轉換,一般能夠經過類型的構造方法進行類型轉換

    var a: Int = 12
    var b: Float = 23
    var c = a + b           // 報錯
    var d = Float(a) + b    // 正確
  • swift的基礎數據類型與對象類型一視同仁,能夠混用,不須要裝箱和拆箱

TODO:Any, AnyObject,

3、常量變量

  • C/Obj-C不一樣,swift的常量更爲廣義,支持__任意類型__,常量只能賦值一次

  • swift的變量和常量在聲明的時候類型就已經肯定(由編譯器自動識別或開發者指定)

  • 使用let聲明的集合爲可變集合,使用var聲明的集合爲不可變集合

  • 若是你的代碼中有不須要改變的值,請使用 let 關鍵字將它聲明爲常量。只將須要改變的值聲明爲變量。這樣能夠儘可能數據安全,而且常量是線程安全

// 常量:使用let聲明,賦值後就不能再修改
let a = NSMutableArray()
let b = 12
let c: Float = 12       // 類型標註(type annotation)
let d = b + 12
a.addObject(11)         // str == [11]
let e = a               // str == [11], d == [11]
a.addObject(12)         // str == [11, 12], d == [11, 12]

// 變量:使用var聲明
var f: Double? = 12
var g = "hello world"

類型標註

在聲明變量和常量的時候能夠若是能夠由編譯器自動識別,能夠不用制定類型,以下

let a = 12    //常量a會編譯爲Int類型
var b = 1.3   //變量b會編譯爲Double類型

咱們也能夠指定類型

let a: Double = 12
let b: Float = 1.3

能夠在一行聲明多個變量/常量,在最後一個聲明類型

var red, green, blue: UInt

4、序列和集合

1. 數組Array

swift的數組能夠是有類型的(泛型),存放同類型的數據,若是添加一個錯誤的類型會報編譯錯誤,默認狀況下編譯器會自動識別

//1. 數組的寫法爲:Array<Int>,也能夠簡寫成[Int]
//2. 數組初始化與NSArray相似,直接用中括號括起來,裏面值用逗號隔開
var array0 = [Int]()
var array1: [Int] = [1, 3, 5, 7, 9]
var array2: Array<Int> = array1

array1.append(11)             // [1, 3, 5, 7, 9, 11]
array1.insert(0, atIndex: 0)  // [0, 1, 3, 5, 7, 9, 11]
array1.isEmpty                // False
array1.count                  // 7

// 3. 若是初始化時不指定類型,而編譯器也不能識別出類型,這時候,會被當成NSArray處理
var array3 = []                       // array3 爲 NSArray類型的空數組

// 4. 若是聲明的時候使用不一樣的類型,編譯器會把數組識別爲NSObject類型
var array4 = ["fdsa", 121]            // array4 爲 Array<NSObject> 類型

// 5. 集合支持加法運算,至關於NSMutableArray的addObjectsFromArray
array1 += [2, 4, 6, 8, 10]    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

// 6. 使用let聲明的數組不可變,不能修改數組array3
let array5: [Int] = [1, 3, 5, 7, 9]
//array5.append(2)              // 報編譯錯誤

// 7. 集合使用下標索引,支持區間索引,區間不可越界
var array6: [Int] = [1, 3, 5, 7, 9]
array6[1] = 4                       // [1, 3, 5, 7, 9]
array6[1...3] = [2, 3, 4]           // [1, 2, 3, 4, 9]
array6[0...2] = array6[1...3]       // [2, 3, 4, 4, 9]

// 8. 迭代數組的時候,若是須要索引,能夠用enumerate方法
for (index, value) in array4.enumerated() {
    //do something
}

2. 字典Dictionary

與數組類型同樣,字典也支持泛型,其鍵值類型均可以指定或有編譯器識別,其中Key的類型,必須是可Hash的,swift中基礎數據類型都是可hash的(String、Int、Double和Bool)

// 1. 用法與oc相似,初始化不須要@
var dict1 = ["key1": 1, "key2": 2, "key3": 3]

// 2. 聲明方式
var dict2: Dictionary<String, Int> = dict1        //dict2與dict1不是一個對象
var dict3: [String: Int] = dict1                  //一般採用這種方式聲明類型


// 3. 不聲明類型,編譯器又沒法識別,則爲NSDictionary
var dict4 = [:]
var dict5: [Int: String] = [:]

// 4. 修改或添加鍵值對
dict1["key3"] = 4

// 5. 刪除鍵
dict1["key3"] = nil

// 6. key不存在不報錯,返回可空類型nil
let value4 = dict1["key4"]

// 7. 字典迭代返回key/value元組,相似python
for (key, value) in dict1 {
    print("\(key) = \(value)")
}

數組(Array)或字典(Dictionary),若是聲明爲變量(var),則爲可變,若是爲常量(let),則爲不可變
常量數組或字典編譯器會對其進行優化,因此儘可能把不可變的數組定義爲常量數組

3. Set

Set集合用於存放無序不重複的對象,用法與數組相似,重複的項會被忽略

var s: Set<Int> = [1, 3, 5, 6, 7, 4, 3, 7]    // [1, 3, 4, 5, 6, 7]
s.count
s.isEmpty
s.insert(3)
s.remove(3)
s.contains(3)

集合操做

let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

//合操做
oddDigits.union(evenDigits).sort()                // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

//交操做
oddDigits.intersection(evenDigits).sorted()       // []

//減操做
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()           // [1, 9]

//不重疊集合
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()   // [1, 2, 9]
  • 使用「是否相等」運算符( == )來判斷兩個 合是否包含所有相同的值。

  • 使用 isSubset(of:) 方法來判斷一個 閤中的值是否也被包含在另一個 閤中。

  • 使用 isSuperset(of:) 方法來判斷一個 閤中包含另外一個 閤中全部的值。

  • 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法來判斷一個 合是不是另一個 合的子 合或 者父 合而且兩個 合併不相等。

  • 使用 isDisjoint(with:) 方法來判斷兩個 合是否不含有相同的值(是否沒有交 )

4. 元組Tuple

與python相似,swift也支持元組,能夠很方便的使用元組包裝多個值,也使得函數返回多個值變得更加方便,特別是臨時組建值得時候

  • 支持任意類型

  • 支持同時賦值

  • 支持自定義key,支持索引

  • 元組不是對象,不是AnyObject類型,因爲swift是強類型的,因此元組有時不能當作普通的對象使用,例如不能把元組加到數組裏面,元組內的全部類型必須是明確的

// 1. 聲明一個元組,元組支持任意類型
let httpError1 = (404, "Not Found")
let point = (100, 50)

// 2. 能夠分別賦值
let (x, y) = point
print(x)      // 100
print(y)      // 50

// 3. 使用下標取元組元素,下標從0開始
print(httpError1.0)      // 404
print(httpError1.1)      // Not Found

// 4. 能夠給數組元素取名
let httpError2 = (code: 404, errorMessage: "Not Found")
print(httpError2.code)               // 404
print(httpError2.errorMessage)       // Not Found

// 5. 能夠用下劃線表示忽略部分值
let (a, _) = point

元組在臨時組織值得時候頗有用,能夠不用從新定義數據結構

5. 字符串String

swift字符串是由Character字符組成的集合,支持+操做符,能夠與NSString無縫橋接,swift的字符串徹底兼容unicode
字符串與值類型(與Int, Float)同樣,是值類型,在傳值的時候都會進行拷貝,固然這回帶來必定的性能損耗,swift編譯器在編譯的時候會進行優化,保證只在必要的狀況下才進行拷貝

// 1. 與NSString不一樣,聲明不須要@前綴,支持轉移字符
let name1 = "bomo\n"

// 2. 空串(下面兩種方式等價)
let name2 = ""
let name3 = String()

// 3. 字符串由字符Character組成,定義字符
let character1: Character = "!"

// 4. 常見屬性,方法
name1.isEmpty                   // 判空
name1.characters.count          // 獲取字符串的字符數
name1.uppercaseString
name1.lowercaseString
name1.hasPrefix("bo")
name1.hasSuffix("mo")

// 5. 加法運算
let hello = "hello " + name1   // hello bomo\n

// 6. 比較(比較值,而不是地址)
let name4 = "b" + "omo\n"
name4 == name1                 // True

// 7. 字符串插值(使用反斜槓和括號站位)
let city = "廣州"
let hello2 = "I'm \(name1) from \(city)"

// 8. 格式化字符串
let f = 123.3233
var s = String(format: "%.2f", f)     //123.32

6. 集合的賦值和拷貝行爲

swift的集合一般有Array和Dictionary,他們在賦值或傳遞的時候,行爲上有所不一樣,字典類型Dictionary或數組類型Array在賦值給變量或常量的時候,只要有作修改,就會進行值拷貝,而且不會做用到原來變量上

var dict1 = ["a": 1, "b": 2]
var dict2 = dict1
print(dict1 == dict2)         // true
dict2["a"] = 3                // 修改dict2
print(dict1 == dict2)         // false


var arr1 = ["a", "b"]
var arr2 = arr1
print(arr1 == arr2)           // true
arr1[0] = "c"                 // 修改arr1
// arr1.append("c")
print(arr1 == arr2)           // false

當數組或字典做爲參數傳遞給函數的時候,因爲在Swift3中不推薦使用變量參數,故全部函數參數不可變,故也不進行拷貝

5、可選類型(可空類型)

swift加入了可空類型讓咱們使用數據的時候更爲安全,咱們須要在可空的地方使用可選類型聲明該變量可爲空,不能給非可選類型設值nil值,在使用的時候能夠明確的知道對象是否可能爲nil,有點像ObjC的對象,對象能夠爲nil,也能夠不爲nil,而swift得可選類型範圍更廣能夠做用於任何類型(基礎類型,類,結構體,枚舉)

1. 聲明

// 1. 聲明可選類型,在類型後面加上?
var obj1: NSObject?
obj1 = NSObject()
obj1 = nil

// 2. 不能給一個可選類型賦nil,下面會報錯,
var obj = NSObject()
obj = nil

// 3. 若是聲明可選變量時沒有賦值,則默認爲nil
var i: Int?

// 4. 一個函數返回一個可選類型
func getdog() -> String? {
    return "wangcai"
}

// 5. 不能把可選類型賦值給非可選類型,下面會報錯
let cat: String = dog

2. 強制解析

可選類型不能直接使用,須要經過取值操做符!取得變量的值,才能使用,若是變量有值,則返回該值,若是變量爲空,則會運行時錯誤

var b: Int?
var a: Int
a = 12
b = 13
let c = a + b!              // 先對b取值,再運算

var b: Bool? = nil
if b! {                     // b爲空,編譯不報錯,運行時報錯
    print("true")
} else {
    print("false")
}

3. 可選綁定

使用可選綁定能夠判斷一個可選類型是否有值,若是有值,則綁定到變量上,若是沒有值,返回false,使用if-let組合實現

var i: Int? = nil
if let number = i {
    print("\(number)")
} else {
    print("nil")
}

可選綁定還支持綁定條件

var i: Int? = nil
if let number = i where i > 10 {
    print("i不爲空且大於10 \(number)")
} else {
    print("nil")
}

可選綁定還支持多個綁定,不準全部的綁定都知足才返回true

if let firstNumber = 1, let secondNumber = 2)
}
// 輸出 "4 < 42 < 100"
 if let firstNumber = Int("4") {
     if let secondNumber = Int("42") {
         if firstNumber < secondNumber && secondNumber < 100 {
             print("\(firstNumber) < \(secondNumber) < 100")
} }
}

4. 隱式解析

聲明類型的時候可使用隱式解析,即在使用可選變量的時候自動取值,不須要調用!操做符,

// 一個函數返回一個可選類型
func getdog() -> String? {
    return "wangcai"
}

//假定咱們經過getdog方法返回的值必定不爲空
var dog: String? = getdog()
let cat: String = dog!          // 使用前須要經過!強制取值

使用dog的時候都須要取值咱們以爲太麻煩了,能夠聲明成隱式可選類型,使用的時候自動取值

var dog: String! = getdog()     // 實際上dog仍是可選類型,只是使用的時候回自動取值
let cat: String = dog           // 在使用dog的時候會自動進行取值,不須要取值操做符

5. 可選類型自判斷連接

在使用可選類型以前,須要進行判斷其是否有值,才能使用,經過!操做符取值後使用(保證有值的狀況下),或經過if-let可選綁定的方式,swift提供了一種相似C#語言的語法糖可讓代碼更爲簡潔,能夠自動判斷值,若是有值,則操做,無值則不操做,並返回nil,在使用前加上?

class Person {
    var favDog: Dog?
}
class Dog {
    var name: String?
}

var p = Person()
var d = Dog()
// p.favDog = d
p.favDog?.name = "tobi"   // 若是p.favDog爲空,不設置name

if let name = p.favDog?.name {
    // p.favDog不爲空且p.favDog.name不爲空
} else {
    // p.favDog爲空或p.favDog.name爲空
}

自判斷連接還支持多鏈接如

let identifier = john.residence?.address?.buildingIdentifier

6. 可選關聯運算符

可選關聯運算符可對可選類型進行拆包,若是可選類型對象爲nil,返回第二個操做數,第二個操做數類型必須和第一個操做數同類型(可選或不可選)

let defaultColorName = "red"
var userDefinedColorName: String?   // defaults to nil

var colorNameToUse = userDefinedColorName ?? defaultColorName
  • defaultColorName和userDefinedColorName必須是同類型(String或String?)

  • 若是userDefinedColorName不爲空,返回其值,若是userDefinedColorName爲空,返回defaultColorName

  • 返回值colorNameToUse的類型同??的第二個操做數的類型,爲String

6、運算符

swift運算符在原有的基礎上作了一些改進,還添加了一下更高級的用法,還有新的運算符

  • =運算符不返回值

  • 符合運算符+=, -=等不返回值

    //下面語句會報錯
    let b = a *= 2
  • 比較運算符能夠用於元組的比較(逐個比較,若是遇到不等的元素,則返回,默認最多隻能比較7個元素的元組,超過則須要自定義)

    (1, "zebra") < (2, "apple")     // true,由於 1 小於 2
  • 字符串String,字符Character支持+運算符

  • 浮點數支持%求餘運算

    8 % 2.5 // 等於 0.5
  • ++/--運算在swift3被拋棄,用+=/-=代替

  • 支持溢出運算符(&+, &-, &*),能夠在溢出時進行(高位)截斷

  • 支持位運算符(>>, <<

  • 支持三目運算符(a ? b : c

  • 支持邏輯運算符(&&, ||, !

  • 與其餘高級語言相似,swift運算符支持重載,能夠爲類添加自定義的運算符邏輯,後面會講到

  • !=, ==, ===, !==(恆等於/不恆等於)

    `===`:這兩個操做符用於引用類型,用於判斷兩個對象是否指向同一地址
    `!===`:與`===`相反,表示兩個變量/常量指向的的地址不一樣
    `==`:表示兩個對象邏輯相等,能夠經過重載運算符實現相等的邏輯,兩個值相等的對象能夠是不一樣地址的對象
    `!=`:與`==`相反,表示兩個對象邏輯不等
  • 區間運算符
    可使用a...b表示一個範圍,有點相似於Python的range(a, b)

    for i in 1...5 {
        print(i)          // 1, 2, 3, 4, 5
    }

    a...b: 從a到b幷包含a和b
    a..<b: 包含a不包含b

    a..b表示半閉區間的用法已經被放棄

    範圍運算符也能夠做用於字符串

    let az = "a"..."z"      // 返回的是CloseInteval或HalfOpenInterval
    az.contains("e")        // True
  • 空合運算符??(與C#相似)
    對於可選類型取值,若是不爲空則返回該值,若是爲空則去第二個操做數

    let result = a ?? b

7、流程控制

swift使用三種語句控制流程:for-inforswitch-casewhilerepeat-while,且判斷條件的括號能夠省略

let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}

//若是不須要使用到迭代的值,使用下劃線`_`忽略該值
for _ in 1...10
    print("hello")

流程控制語句的條件返回值必須是Bool,下面會報錯

var dd: Bool? = true
if dd {
    print("fd")
}

條件判斷能夠與let結合使用,當值爲nil時,視爲false(即:可選綁定

var dd: Bool? = true
if let ee = dd {
    print("fd")
}

在Swift2.0之後,不支持do-while語句,使用repeat-while代替,用法與do-while同樣

repeat {  
    print("repeat while : \(j)")  
    j++  
} while j < 3

guard-else

翻譯爲保鏢模式,在執行操做前,進行檢查,若是不符合,則攔截,使用方式與if有些相似,若是與let結合使用,能夠對可選類型解包,先看看普通的if-else模式

func test(i: Int?) {
    if let i = i where i > 0 {
        // 符合條件的處理
        return
    }

    // 不符合條件的處理
}

上面的處理把條件放在了條件判斷內部,使用guard與之相反,把正確的狀況放在最外部,而異常狀況放在條件判斷內部

func test(i: Int?) {
    guard let i = i where i > 0 else {
        // 在這裏攔截,處理不符合條件的狀況
        return
    }

    // 符合條件的處理,這個時候已經對i進行了拆包,i是非可選類型,能夠直接使用
    print(i)
}

保鏢模式能夠避免代碼中過多的流程判斷代碼致使過多的代碼塊嵌套,加強可讀性

保鏢模式guard-else內的代碼塊必須包含break, return等跳出代碼塊的關鍵字

switch-case

  • switch語句支持更多數據類型(String,Int, Float, 元組, 枚舉),理論上switch支持任意類型的對象(須要實現~=方法或Equatable協議,詳情參見這裏

  • case能夠帶多個值,用逗號隔開

  • case能夠支持區間(a...b),支持元組,區間能夠嵌套在元組內使用

  • case多條語句不須要用大括號包起來

  • case語句不須要break,除了空語句,若是須要執行下面的case,可使用fallthrough

  • 若是case不能命中全部的狀況,必需要default,如Int,String類型,不然編譯會失敗

  • 能夠用fallthrough關鍵字聲明接着執行下一條case語句,注意,若是case語句有賦值語句(let),則fallthrough無效

// 定義一個枚舉
enum HttpStatus {
    case ServerError
    case NetworkError
    case Success
    case Redirect
}

var status = HttpStatus.Redirect
switch status {
// case能夠接收多個值
case HttpStatus.ServerError, HttpStatus.NetworkError:
    print("error")
    // case語句結束顯式寫break,除非是空語句

case .Redirect:             // 若是編譯器能夠識別出枚舉類型,能夠省略枚舉名
    print ("redirect")
    fallthrough             // 像C語言同樣,繼續執行下一條case
case HttpStatus.Success:
    print("success")
}

//元組,區間
let request = (0, "https://baidu.com")
switch request {
case (0, let a):                  // 支持綁定
    print(a)
case let (a, b) where a == 1:      // 綁定能夠卸載元組外面,支持where判斷
    print("cancel \(b)")
case (2...10, _):                 // 支持區間,支持忽略值
    print("error")
default:
    print("unknown")
}

// case能夠與where進行進一步判斷
let request2 = (0, 10)
switch request2 {
case (0, let y) where y < 5:
"success"   //被輸出
case (0, let y) where y >= 5:
"error"   //被輸出
default:
    "unknown"
}

case除了和swift一塊兒使用外,還支持與if語句結合使用,用法與switch同樣

let bb = (12, "bomo")
if case (1...20, let cc) = bb where cc == "bomo" {
    print(cc)
} else {
    print("nil")
}

帶標籤的語句

若是有多層嵌套的狀況下,有時候咱們須要在某處直接退出多層循環,在objc下並無比較好的方式實現,須要添加退出標識,而後一層一層退出,而在swift能夠很方便的退出多層循環,首先須要使用標籤標識不通的循環體,形式以下

labelName : while condition { statements }

看下面例子

outerLoop1 : for i in 1...10 {
    outerLoop2 : for j in 1...10 {
        outerLoop3 : for k in 1...10 {
            if j > 5 {
                // 1. 跳出一層循環(默認)繼續outerLoop2的循環
                break

                // 2. 跳出兩層循環,繼續outerLoop1的循環
                // break outerLoop2

                // 3. 跳出三層循環,退出整個循環,繼續後面的語句
                // break outerLoop1
            }
        }
    }
}

8、函數

1. 基本形式

//有返回值
func 函數名(參數名1:參數類型1, 參數名2:參數類型2) -> 返回值類型 {
    // 函數體
}

//多個返回值(元組)
func getPoint() -> (x: Int, y: Int) {
    return (1, 3)
}
var p = getPoint()
p.x

//無參數無返回值
func sayHello() {
    // 函數體
}

//egg
func add(a: Int, b: Int) -> Int {
    return a + b
}

// 調用
add(12, b: 232)

函數調用除了第一個參數,後面全部的參數必須帶上參數名(符合Objc的函數命名規則)若是是調用構造器,第一個參數也須要顯示聲明

class A {
    var name: String
    init(name: String) {
        self.name = name
    }

    func sayHello(msg: String, count: Int) {
        for _ in 1...count {
            print (msg)
        }
    }
}

let a = A(name: "bomo")               // 構造器全部參數都必須顯示聲明參數名
a.sayHello("hello", count: 2)         // 函數參數除了第一個其餘都須要顯示聲明參數名

在swift中,若是一個方法有返回值,可是調用的時候沒有使用該返回值,則會報警告,能夠在方法定義出使用@discardableResult聲明以消除改警告,以下

@discardableResult
func doWithResult(result: String) -> Bool {
    print(result)
    return true
}

2. 可變參數

可變參數只能做爲最後一個參數,一個方法最多隻有一個可變參數

func sum(numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}

3. 外部參數名

默認狀況下,若是不指定外部參數名,swift編譯器會自動爲函數參數聲明與內部參數名同名的外部參數名(格式爲:外部參數名 內部參數名: 類型名

//默認狀況下,外部參數名與內部參數名同樣
func add(first a: Int, second b: Int) -> Int {
    return a + b
}

// 調用
add(first: 10, second: 20)

若是函數在第一個參數定義外部參數名,必須顯示指定,固然咱們還能夠經過下劃線_讓函數忽略參數名

func add(a: Int, _ b: Int) -> Int {
    return a + b
}
add(1, 2)

4. 函數默認值

函數還支持聲明默認值,(格式爲:外部參數名 內部參數名: 類型名 = 默認值

func log(msg: String, isDebug: Bool = true) {
    if isDebug {
        print(msg)
    }
}
log("fail")
log("success", isDebug: false)

若是使用默認值而且默認值不是出如今最後,那調用的時候必須寫全全部參數

建議把默認參數放到最後面,這樣能夠確保非默認參數的賦值順序,減小參數混亂的狀況

5. 閉包

  • 函數做爲變量

  • 函數做爲函數參數

  • 函數做爲函數返回值

  • 閉包函數聲明

func add(a: Int, b: Int) -> Int {
    return a + b
}

//函數做爲變量,函數hello賦給somefunc,並調用
let somefunc: (Int, Int) -> Int = add
somefunc(10, 20)      // 30

//函數做爲參數
func logAdd(a:Int, b:Int, function: (Int, Int) -> Int) {
    // 函數內容
    print("begin")
    function(a, b)
    print("end")
}
logAdd(12, b: 23, function: add)

//函數做爲返回值(包裝一個函數,在執行先後輸出信息),函數做爲參數又做爲返回值
func addWrapper(addFunc: (Int, Int) -> Int) -> ((Int, Int) -> Int) {
    // 函數內容
    func wrapper(a: Int, b: Int) -> Int {
        print("begin")
        let res = addFunc(a, b)
        print("end")
        return res
    }
    return wrapper
}
var newAdd = addWrapper(add)
newAdd(12, 32)

閉包函數聲明形式

{ (parameters) -> returnType in
    statements      // 能夠有多行
}

閉包函數

//定義一個函數變量
var addfunc: (Int, Int) -> Int

//閉包的寫法
// 1. 完整寫法
addfunc = {(a: Int, b: Int) -> (Int) in
    //var c = a + 1       //函數體能夠有多條語句,若是在同一行,須要用分號隔開,函數體不須要大括號
    return a + b
}
// 2. 前面的addfunc變量能夠推斷出後面函數的參數類型和返回值類型,故能夠省略
addfunc = {(a, b) in return a + b}

// 3. 參數列表括號能夠省去,函數只有一條語句時,return能夠省略
addfunc = {a, b in a + b}

// 4. 參數和in能夠省去,經過$和索引取得參數
addfunc = {$0 + $1}

// 操做符須要的參數與函數參數一致,能夠省去參數,並使用括號括起來,做爲參數時,可不用括號
addfunc = (+)

6. Trailing(尾行)閉包

若是函數做爲另外一個函數的參數,而且是最後一個參數時,能夠經過Trainling閉包來加強函數的可讀性

func someFunctionThatTakesAClosure(a: Int, closure: () -> ()) {
    // 函數體部分
}

// 1. 通常形式
someFunctionThatTakesAClosure(10, closure: {
    // 閉包主體部分
})

// 2. Trainling閉包的方式
someFunctionThatTakesAClosure(10) {
    // 閉包主體部分
}

// 3. 若是沒有其餘參數時,能夠省略括號
someFunctionThatTakesAClosure {
    // 閉包主體部分
}

7. Escaping(逃逸)閉包

若是一個閉包/函數做爲參數傳給另一個函數,但這個閉包在傳入函數返回以後纔會執行,就稱該閉包在函數中"逃逸",須要在函數參數添加@escaping聲明,來聲明該閉包/函數容許從函數中"逃逸",以下

var completionHandlers: [() -> Void] = []

// 傳入的閉包/函數並無在函數內執行,須要在函數類型錢添加@escaping聲明
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

逃逸閉包只是一個聲明,以加強函數的意圖

8. 自動閉包

對於沒有參數的閉包,swift提供了一種簡寫的方式,直接寫函數體,不須要函數形式(返回值和參數列表),以下

// 聲明一個自動閉包(無參數,能夠有返回值,返回值類型swift能夠自動識別)
let sayHello = { print("hello world") }

//調用閉包函數
sayHello()

自動閉包只是閉包的一種簡寫方式

若是一個函數接受一個不帶參數的閉包

func logIfTrue(predicate: () -> Bool) {
    if predicate() {
        print("True")
    }
}

調用的時候可使用自動閉包

logIfTrue(predicate: { return 1 < 2 })

// 能夠簡化return
logIfTrue(predicate: { 1 < 2 })

上面代碼看起來可讀性不是很好,swift引入了一個關鍵字@autoclosure,簡化自動閉包的大括號,在閉包類型前面添加該關鍵字聲明

func logIfTrue(predicate: @autoclosure () -> Bool) {
    if predicate() {
        print("True")
    }
}

// 調用
logIfTrue(predicate:1 < 2)

@autoclosure 關鍵字是爲了簡化閉包的寫法,加強可讀性,這裏的例子比較簡單,能夠參考:@AUTOCLOSURE 和 ??

9. 常量參數和變量參數

默認狀況下全部函數參數都是常量,意味着參數是不可變的,咱們能夠顯式的聲明參數爲變量

func log(msg: String) {
    msg = "begin " + msg + " end"       // 會報錯,由於msg爲常量
    print(msg)
}
func log(var msg: String) {
    msg = "begin " + msg + " end"       // 變量參數正常運行
    print(msg)
}

注:變量參數在swift3被拋棄

10. 輸入輸出參數

在c語言裏有指針,能夠經過傳址直接修改外部變量的值,在swift經過inout關鍵字聲明函數內部可直接修改外部變量,外部經過&操做符取得變量地址

func swap(inout a: Int, inout b: Int) {
    let temp = a
    a = b
    b = temp
}
var a = 19, b = 3
swap(&a, &b)

11. 嵌套函數

swift的函數還支持嵌套,默認狀況下,嵌套函數對外部不可見,只能在函數內部使用

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    //定義兩個內部函數
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }

    return backward ? stepBackward : stepForward
}

嵌套函數至關於objc函數內的block

12. defer

在swift2.0以後添加了defer關鍵字,能夠定義代碼塊在函數執行完成以前的完成一些操做,而且在函數拋出錯誤的時候也能夠執行

func test() {
    print("begin1")
    defer {             // 入棧
        print("end1")
    }

    print("begin2")
    defer {             // 入棧
        print("end2")
    }

    if true {
        print("begin4")
        defer {
            print("end4")
        }

        print("begin5")
        defer {
            print("end5")
        }
    }
    print("do balabala")
    return
}

上面輸出結果爲

begin1
begin2
begin4
begin5
end5
end4
do balabala
end2
end1

一般能夠用在須要成對操做的邏輯中(如:open/close

9、枚舉

swift的枚舉比C語言的枚舉更爲強大,支持更多特性,swift的枚舉更像類和結構體,支持類和結構體的一些特性,與ObjC不一樣,若是不聲明枚舉的值,編譯器不會給枚舉設置默認值

枚舉與結構體同樣,是值類型

1. 聲明和使用

// 1. 定義枚舉
enum CompassPoint {
    case North
    case South
    case East
    case West
}

// 2. 能夠把枚舉值定義在一行,用逗號隔開
enum CompassPoint2 {
    case North, South, East, West
}

// 3. 像對象同樣使用枚舉,代碼結構更爲清晰,枚舉更爲簡短
let direction = CompassPoint.East

// 4. 若是編譯器能夠識別出枚舉的類型,能夠省略枚舉名
let direction2: CompassPoint
direction2 = .East

// 5. 若是編譯器能肯定case命中全部的狀況,能夠不須要default
switch direction {
case .East:
    print("east")
case .West:
    print("west")
case .South:
    print("south")
case .North:
    print("north")
    //全部值都被枚舉,則不須要default
}

2. 嵌套枚舉

swift的枚舉定義支持嵌套,在使用的時候一層一層引用

enum Character {
    enum Weapon {
        case Bow
        case Sword
        case Lance
        case Dagger
    }
    enum Helmet {
        case Wooden
        case Iron
        case Diamond
    }
    case Thief
    case Warrior
    case Knight
}

let character = Character.Thief
let weapon = Character.Weapon.Bow
let helmet = Character.Helmet.Iron

3. 遞歸枚舉

枚舉的關聯值的類型能夠設爲枚舉自身,這樣的枚舉稱爲遞歸枚舉

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

帶遞歸類型的枚舉須要在case前面添加關鍵字聲明indirect,也能夠在enum前面加上聲明,表示全部的成員是能夠遞歸的

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

使用遞歸枚舉取值的時候可使用遞歸函數

func evaluate(_ expression: ArithmeticExpression) -> Int {
   switch expression {
   case let .number(value):
       return value
   case let .addition(left, right):
       return evaluate(left) + evaluate(right)
   case let .multiplication(left, right):
       return evaluate(left) * evaluate(right)
   }
}

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)

// (5 + 4) * 2
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
print(evaluate(product))

其實感受這種嵌套多層的用法可讀性並非特別好,並且在取值的時候還須要遞歸,一般來講,嵌套一層就夠了

4. 原始值

與C語言同樣,能夠爲每一個枚舉指定值,而且能夠支持更多類型(Int, Float, Character, String

// 定義枚舉,並初始化原始值
enum ASCIIControlCharacter: Character {
    case Tab = "\t"
    case LineFeed = "\n"
    case CarriageReturn = "\r"
}

// 2. 經過兩個屬性得到原始值
var ch = ASCIIControlCharacter.Tab
ch.hashValue    // 獲取是否有原始值
ch.rawValue     // 得到原始值

// 3. 經過原始值構造枚舉,若是不存在,則返回nil
var tab = ASCIIControlCharacter.init(rawValue: "\t")

// 4. 若是是原始值是整形值,後面的值默認自增1,若是不指定,則默認爲空,而不是從0開始
enum Planet: Int {
    case Mercury = 1, Venus         // Venus = 2
    case Neptune                    // Neptune = 3
}

// 5. 若是沒有指定枚舉原始值的類型,則默認爲空,而不是整型
enum CompassPoint {
    case North
    case South
    case East
    case West
}
//swift 不會爲North, South, East, West設置爲0,1,2,3,而且CompassPoint沒有原始值(rawValue)

// 6. 有原始值的枚舉能夠經過原始值構造(構造器返回可選類型)
let lineFeed = ASCIIControlCharacter(rawValue: "\n")

5. 關聯值

上面咱們說到,枚舉與類和結構體相似,swift的枚舉能夠給不一樣的枚舉值綁定關聯值,以下

enum Barcode {
    case UPCA(Int, Int, Int)        //條形碼,關聯一個元組
    case QRCode(String)             //二維碼,關聯一個字符串
}

var productBarcode = Barcode.UPCA(8, 85909_51226, 3)
// var productBarcode = .QRCode("http://www.baidu.com")

switch productBarcode {
case .UPCA(let a, let b, let c):        //在枚舉的時候能夠取得關聯值
    print("barcode: \(a)\(b)\(c)")
case let .QRCode(value):
    print("qrcode: \(value)")
}

如上面這種輕量的數據,在OC上通常咱們可能須要定義兩個類實現,而swift的枚舉能夠輕鬆的處理這種輕量數據,而減小項目中類的定義和維護

10、類與結構體

先來看看結構體和類的一些差別

  • 類是引用類型,結構體爲值類型

  • 類使用引用計數管理內存,結構體分配在棧上,有系統管理內存,變量傳遞的時候,結構體整個拷貝,而類默認只傳遞引用地址(有些類會進行一些額外的拷貝,詳見[深拷貝和淺拷貝]())

  • 結構體不支持繼承,類支持繼承

  • 與ObjC不一樣,swift的結構體能夠定義方法

  • 類支持運行時類型檢查,而結構體不支持

  • 類有構造器和析構器,結構體只有構造器

  • 常量結構體的成員的值不能改變

實際上,在 Swift 中,全部的基本類型:整數(Integer)、浮 點數(floating-point)、布爾值(Boolean)、字符串(string)、數組(array)和字典(dictionary),都是 值類型,而且在底層都是以結構體的形式所實現。

1. 結構體,類定義

struct Point {
    let x: Int
    let y: Int

    func printPoint() {
        print("x=\(x), y=\(y)")
    }
}

class Person {
    var someObj = NSObject()          // 定義屬性,並初始化
    var name: String                  // 定義屬性,並指定類型

    init(name: String) {              // 構造函數
        self.name = name
    }

    func hello() {
        print("hello \(self.name)")
    }

    //析構函數
    deinit {
        print("dealloc")
    }
}

swift中,許多基本類型如String, ArrayDictionary都是用結構體實現的,意味着在傳遞的時候都會進行值拷貝,固然swift也對這些類型進行了優化,只有在須要的時候進行拷貝

2. 靜態屬性,靜態方法

swift中有兩個staticclass聲明靜態變量或方法,其中class只能用在類的方法和計算屬性上,其餘的都使用static,因爲類支持繼承,因此使用class聲明的靜態方法能夠被繼承,而static聲明的靜態方法不能被繼承

class Person {
    static var instanceCount: Int = 0       // 聲明一個類屬性
    init () {
        Person.instanceCount += 1           // 經過類名引用類屬性,子類能夠訪問基類的類屬性
    }

    // 使用class聲明的靜態方法能夠被繼承
    class func overrideableComputedTypeProperty() {
        print("\(Person.instanceCount)")
    }

    // 使用static聲明的靜態方法不能被繼承
    static func printInstanceCount() {      // 聲明一個靜態方法
        print("\(Person.instanceCount)")
    }
}

類和結構體的聲明和用法與類相似,使用static

注意:class只能用來聲明計算屬性和方法,不能用來聲明普通屬性

3. 構造器和析構器

swift的構造器規則和限制比較多,關於構造器能夠參見:這裏

析構器至關於objc裏面的dealloc方法,作一些須要手動釋放資源的操做,析構器與構造器不一樣,沒有參數,定義的時候不須要括號,類在釋放的以前會自動調用父類的析構器,不須要主動調用

class Person {
    deinit {
        print("釋放額外的資源,如通知")
    }
}

4. 類型判斷

在objc中,咱們一般使用isKindOfClass, isMemberOfClass, isSubclassOfClass等方法進行類型判斷,swift使用isas判斷類型

class Parent {
}
class Son: Parent {
}

var s = Son()
// isKindOfClass
son is Son                // true
son is Parent             // true

// isMemberOfClass
son.dynamicType == Son.self         // true
son.dynamicType == Parent.self      // false

// isSubclassOfClass 暫時沒找到相關的API

//TODO: swift動態性,反射

5. 弱引用

ObjC同樣,swift的內存管理也使用引用計數管理,也使用weak聲明弱引用變量

class Person {
    weak var person: Person? = nil
}

6. 訪問級別

在swift中,framework和bundle都被處理成模塊

  • public:公開,在模塊內能夠訪問或繼承或override,在模塊外能夠訪問,但不能被繼承或override

  • internal:內部,在模塊(framework)內部使用,模塊外訪問不到

  • private:真正意義上的私有,只有當前做用於才能訪問

  • fileprivate: 只能在當前源文件中使用,同文件能夠互相訪問

  • open: open的訪問級別比public更高,在模塊外部能夠被訪問同時能夠被繼承或override

swift默認的訪問級別爲Internal,使用的時候只須要在類/變量/函數前面加上訪問級別便可

public class Person {
    class public var peopleCount: Int = 0    // 類變量,經過class聲明,類變量使用時使用類名引用
    internal var age: Int                    // 實例變量
    var name: String                         // 不聲明,則爲internal

    init() {
        self.age = 0
        self.name = ""
        Person.peopleCount++              // 使用靜態變量
    }

    private func sayHello() {
        print("hello")
    }
}

外層訪問級別的必須是比成員更高,下面會報警告

class Person {                      // 默認爲internal
    public var age: Int = 0         // 爲public,比類訪問級別高,會有警告
    private var gender: Int = 10
    private func sayHello() {
          print("hello")
    }
}

函數的訪問級別要比參數(或泛型類型)的訪問級別低,不然會報警告

private class PrivatePerson {
    private var age: Int = 0
    var gender: Int = 10          // 報警告

    private func sayHello() {

    }
}

public class Test {
    public func test(person:PrivatePerson) {    //報編譯錯誤:這裏參數訪問級別爲private,因此函數訪問級別不能高於private,則只能爲private
    }
}

枚舉類型的成員訪問級別跟隨枚舉類型,嵌套類型默認最高訪問級別爲internal(外層爲public,內層默認爲internal)

public enum CompassPoint {
    case North            // 四個枚舉成員訪問級別都爲public
    case South
    case East
    case West
}

子類訪問級別不能高於父類(包括泛型類型),協議繼承也同理,子協議訪問級別不能高於父協議

class Parent {

}

public class Son: Parent {       // 報編譯錯誤:Son訪問級別必須低於Parent,應該爲internal或private

}

元組的訪問級別爲元組內全部類型訪問級別中最低級的

class Parent {
}

private class Son: Parent {
}

public class SomeClass {
    internal let sometuple = (Son(), Parent())  // 報編譯錯誤:sometuple的訪問級別不能高於成員類型的訪問級別,因爲Son爲private,故sometuple必須爲private
}

變量的訪問級別不能高於類型

private class PrivateClass {

}

public class SomeClass {
      public var value: PrivateClass        // 報編譯錯誤:變量value的訪問級別不能高於其類型,故value必須聲明爲private
}

屬性的 Setter 訪問級別不能高於 Getter訪問級別

public class SomeClass {
    private(set) var num = 1_000_000      // 聲明屬性num,getter訪問級別沒有聲明,默認爲Internal,setter訪問級別爲private

    private internal(set) var name = "bomo"   // 報編譯錯誤:屬性name的setter訪問級別爲internal,高於getter訪問級別private
}

協議與類的訪問級別關係

  • 協議中全部必須實現的成員的訪問級別和協議自己的訪問級別相同

  • 其子協議的訪問級別不高於父協議(與類相同)

  • 若是類實現了協議,那類的訪問級別必須低於或等於協議的訪問級別

類型別名訪問級別與類型的關係

  • 類型別名的訪問級別不能高於原類型的訪問級別;

函數構造函數默認訪問級別爲internal,若是須要給其餘模塊使用,需顯式聲明爲public

注意:swift的訪問級別是做用於文件(private)和模塊的(internal)的,而不僅是類,因此只要在同一個文件內,private訪問級別在不一樣類也能夠直接訪問,例如咱們能夠經過子類包裝父類的方法以改變訪問級別

public class A {
    private func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {   // 在同一個文件,改變someMethod的訪問級別
        super.someMethod()
    }
}

7. 屬性

  • 使用關鍵字lazy聲明一個懶加載 變量 屬性,當屬性被使用的時候(get),纔會進行初始化

  • set方法的訪問級別必須必get方法低

  • 聲明屬性的時候可使用private(set)internal(set)改變set方法默認的訪問級別

  • 每一個實例都有一個self屬性,指向實例自身,一般在屬性與函數參數有衝突的時候使用

  • 對於常量屬性,不準在定義它的類的構造器中賦值,不能再子類賦值

class DataImporter {
}

class DataManager {
    // 1. 只有第一次調用importer的get方法的時候纔會初始化
    lazy var importer = DataImporter()
    var data = [String]()
}

class Rectangle {
    var width: Double = 0.0
    var height: Double = 0.0

    // 2. 聲明get方法和set方法的訪問級別
    private private(set) var weight: Double = 0

    // 3. 自定義get/set方法
    var square: Double {
        get {
            return (self.width + self.height)/2;
        }
        //set {                 //若是不指定名稱,默認經過newValue使用新值
        set(newValue) {
            self.width = newValue/2.0;
            self.height = newValue/2.0
        }
    }

    // 4. 只讀屬性,能夠省略get,直接使用一個花括號
    var perimeter: Double {
        return (self.width + self.height) * 2
    }

    // 5. 屬性監視器,在初始化的時候不會觸發
    var someInt: Int = 0 {
        willSet {       //用法與set同樣若是不指定名稱,默認經過newValue使用舊值
            print("set方法以前觸發")
        }
        didSet {        //用法與set同樣若是不指定名稱,默認經過oldValue使用舊值
            print("set方法完成後觸發,能夠在這裏設置obj的值覆蓋set方法設置的值")
            self.someInt = 0      // someInt的值永遠爲0,在監視器修改屬性的值不會致使觀察器被再次調用
        }
    }
}

使用lazy聲明的屬性不是線程安全的,在多線程狀況下可能產生多份,須要本身控制

對於結構體,與OC不一樣,swift的結構體容許直接對屬性的子屬性直接修改,而不須要取出從新賦值

someVideoMode.resolution.width = 1280

在oc上須要這樣作

var resolution = someVideoMode.resolution
resolution.width = 1024
someVideoMode.resolution = resolution

8. 繼承

咱們都知道,在oc裏全部的類都繼承自NSObject/NSProxy,而在swift中的類並非從一個通用的基類繼承的,全部沒有繼承其餘父類的類都稱爲基類

class Parent {
    final var gender = "unknown"
    init(gender: String) {
        self.gender = gender
    }
    private func hello() {
        print("parent hello")
    }
}
class Son: Parent {
    // 重寫能夠改變父類方法的訪問級別
    internal override func hello() {                  // 重寫父類方法必須加上override,不然會報編譯錯誤
        //super.hello()                               // 能夠經過super訪問父類成員,包括附屬腳本
        print("son hello")
    }
}

重寫屬性的時候,若是屬性提供了setter方法,則必須爲提供getter方法
若是重寫了屬性的setter方法,則不能重寫willSet和didSet方法
若是重寫了willSet和didSet方法,則不能重寫get和set方法

父類的屬性,方法,類方法,附屬腳本,包括類自己均可以被子類繼承和重寫,能夠經過final約束限制子類的重寫(final class, final var, final func, final class func, 以及 final subscript

class Parent {
    final var gender = "unknown"        // 不容許被子類重寫
    var name: String                    // 能夠被子類重寫
    init(gender: String) {
        self.gender = gender
        self.name = ""
    }
    final func hello() {                // 不容許被重寫
        print("parent hello")
    }
}

swift編譯器在識別數組類型的時候,若是數組元素有相同的基類,會被自動識別出來

class Person {
}
class Teacher: Person {
}
class Student: Person {
}

let t1 = Teacher()
let t2 = Teacher()
let s1 = Student()
let s2 = Student()

let people = [t1, t2, s1, s2]      // people會被識別爲[Person]類型

向下類型轉換as!, as?as!返回非可選類型,若是類型不匹配會報錯,as?返回可選類型,若是類型不匹配返回nil

for person in people {
    if let teacher = person as? Teacher {
        println("teacher")
    } else if let student = person as? Student {
        println("student")
    }
}

9. 附屬腳本subscript

附屬腳本可讓類、結構體、枚舉對象快捷訪問集合或序列,而不須要調用使用對象內的實例變量引用,看下面實例

class DailyMeal {
    enum MealTime {
        case Breakfast
        case Lunch
        case Dinner
    }

    var meals: [MealTime : String] = [:]
}

// 若是須要使用DailyMeal的meals對象的,須要這麼用
var dailyMeal = DailyMeal()
dailyMeal.meals[MealTime.Breakfast] = "Toast"

使用附屬腳本能夠直接經過類對象索引訪問meals的值

class DailyMeal {
    enum MealTime {
        case Breakfast
        case Lunch
        case Dinner
    }

    var meals: [MealTime : String] = [:]

    // 定義附加腳本,相似屬性
    subscript(realMealTime: MealTime) -> String {
        get {
            if let value = meals[realMealTime] {
                return value
            } else {
                return "unknown"
            }
        }
        set(newValue) {
            meals[realMealTime] = newValue
        }
    }
}


var dailyMeal = DailyMeal()
dailyMeal[.Breakfast] = "sala"
print(dailyMeal[.Breakfast])

附加腳本還支持多個參數

struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(count: rows * columns, repeatedValue: 0.0)
    }
    func indexIsValidForRow(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValidForRow(row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

附加腳本相似屬性,擁有get/set方法,支持只讀和讀寫兩種方式,附加腳本也支持多個參數,附屬腳本能夠屏蔽外部對內部對象的直接訪問,隱藏對象內部的細節,提升封裝度,使得代碼更加健壯和簡潔

10. 類型嵌套

與枚舉同樣,結構體和類都支持類型嵌套,能夠在類裏面再定義類/結構體/枚舉

class SomeClass {
    // 類裏面嵌套定義枚舉
    enum Suit: Character {
        case Spades = "♠", Hearts = "♡", Diamonds = "♢", Clubs = "♣"

        // 枚舉裏面嵌套定義結構體
        struct Values {
            let first: Int, second: Int
        }
    }

    // 類裏面嵌套定義結構體
    struct Point {
        let x: Int
        let y: Int
    }

    // 類裏面嵌套定義類
    class InnerClass {
        var name: String = ""
        var id: Int = 0
    }
}

// 使用的時候像屬性同樣引用
let values = SomeClass.Suit.Values(first: 1, second: 2)

11. 類型別名

swift類型別名與c語言中取別名有點像,經過關鍵字typealias聲明別名

public typealias MyInt = Int

func add(a: MyInt, b: MyInt) -> MyInt {
    return a + b
}

一般在容易出現命名衝突的狀況下會考慮使用類型別名

11、擴展Extension

與oc同樣,擴展就是對已有的類添加新的功能,與oc的category相似,swift的擴展能夠:

  • 提供新的構造器(須要符合構造器的基本規則)

  • 添加實例計算型屬性和類計算性屬性

  • 添加實例方法和類方法

  • 添加附加腳本

  • 添加新的嵌套類型

  • 使一個已有類型符合某個接口

swift擴展不能夠:

  • 不能夠添加存儲屬性

  • 不能夠向已有屬性添加屬性觀測器(willSet, didSet)

class Person {
    func hello() {
        print("hello")
    }
}

// 定義擴展
extension Person {
    func fly() {
        print("fly")
    }
}

let p = Person()
p.fly()

擴展也能夠做用在結構體和枚舉上

struct Rectangle {
    let width: Double
    let height: Double
}

extension Rectangle {
    var perimeter: Double {
        return 2 * (self.width + self.height)
    }
}

let rect = Rectangle(width: 100, height: 200)
print(rect.perimeter)

擴展內的成員定義與類相似,這裏再也不說明

擴展屬性

因爲swift不能擴展新的屬性,有時候咱們但願給類添加屬性,在oc裏能夠用關聯屬性新增存儲屬性,在swift也能夠,須要引入ObjectiveC模塊

import ObjectiveC

class Point {
    var x: Int = 0
    var y: Int = 1
}

private var xoTag: UInt = 0
extension Point {
    var z: Int {
        get {
            return objc_getAssociatedObject(self, &xoTag) as! Int
        } set(newValue) {
            objc_setAssociatedObject(self, &xoTag, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN)
        }
    }
}

12、協議Protocal

swift的協議在oc的基礎上加了更多的支持,能夠支持屬性,方法,附加腳本,操做符等,協議的屬性必須爲變量var

protocol SomeProtocol {
    // 屬性要求
    var mustBeSettable: Int { get set }
    // 只讀屬性
    var doesNotNeedToBeSettable: Int { get }
    // 只讀靜態屬性
    static var staticProperty: Int { get }
    // 靜態方法
    static func hello()
}

1. mutating

在結構體/枚舉中的值類型變量,默認狀況下不能對其進行修改,編譯不經過,若是須要修改值類型的屬性,須要在方法聲明前加上mutating

struct Point {
    var x: Int
    var y: Int

    func moveToPoint(point: Point) {
        self.x = point.x        // 報錯:不能對值類型的屬性進行修改
        self.y = point.y        // 報錯:不能對值類型的屬性進行修改
    }

    mutating func moveToPoint2(point: Point) {
        self.x = point.x        // 編譯經過
        self.y = point.y        // 編譯經過
    }

    //可變方法還能夠對self進行修改,這個方法和moveToPoint2效果相同
    mutating func moveToPoint3(x deltaX: Int, y deltaY: Int) {
        self = Point(x:deltaX, y:deltaY)
    }
}

可變方法還能夠修改枚舉值自身的值

enum TriStateSwitch {
    case Off, Low, High
    mutating func next() {
        switch self {
            case .Off:
                self = .Low
            case .Low:
                self = .High
            case .High:
                self = .Off
        }
    }
}

特別是在定義Protocal的時候,須要考慮到協議可能做用於枚舉或結構體,在定義協議的時候須要在方法前加上mutating

protocol SomeProtocol {
    mutating func moveToPoint(point: Point)
}

2. 協議類型

協議雖然沒有任何實現,但能夠當作類型來用,與oc的protocal相似,用協議類型表示實現了該協議的對象,與oc的id<SomeProtocol>同樣

3. 協議組合

有時候咱們須要表示一個對象實現多個協議,可使用協議組合來表示,以下

protocol SwimProtocal {
    func fly()
}
protocol WalkProtocal {
    func walk()
}

func through(animal: protocol<WalkProtocal, SwimProtocal>) {
    animal.walk()
    animal.fly()
}

4. 自身類型

有時候咱們須要表示實現協議的類型,可使用Self代替,以下

protocol CompareProtocal {
    // Self表示實現協議本身的類型自己
    func compare(other: Self) -> Bool
}

class Product: CompareProtocal {
    var id: Int = 0
    func compare(other: Product) -> Bool {
        return self.id == other.id
    }
}

5. @objc協議

swift聲明的協議是不能直接被oc的代碼橋接調用的,若是須要,須要在聲明前加上@objc,使用@objc聲明的協議不能被用於結構體和枚舉

import Foundation
@objc protocol HasArea {            // 協議能夠被橋接到oc中使用
    var area: Double { get }
}

6. Optional要求

在oc中的protocal能夠定義可選方法,在swift默認不支持可選方法,swift只有在添加了@objc聲明的協議才能定義可選方法,在定義前添加optional聲明

import Foundation
@objc protocol HasArea {
    optional var area: Double { get }     // 定義可選屬性
}

十3、錯誤

與其餘高級語言異常處理有點相似,swift引入了錯誤的機制,能夠在出現異常的地方拋出錯誤,錯誤對象繼承自Error,拋出的錯誤函數會當即返回,並將錯誤丟給調用函數的函數處理,若是一個函數可能拋出錯誤,那麼必須在函數定義的時候進行聲明,以下

//定義錯誤類型
enum OperationError: Error {
    case DivideByZero
    case Other
}

//定義可能拋出異常的函數,在函數聲明的返回值前面加上throws
func divide(a: Int, b: Int) throws -> Float {
    if b == 0 {
        throw OperationError.DivideByZero
    }
    return Float(a) / Float(b)
}

//調用可能出錯的函數(調用出必須加上try)
do {
    let result = try divide(a: 10, b: 0)
    print(result)
} catch OperationError.DivideByZero {
    print(error)
} catch {
    //其餘錯誤
}

若是錯誤是一個對象,而不是枚舉,能夠用let綁定到變量上

do {
    try divide(a: 10, b: 0)
} catch let err as SomeErrorType {
    print(err.message)
} catch {
    print("other error")
}

若是不處理錯誤的話可使用try?,使用try?關鍵字的方法會被包裝到一個可選類型中,若是發生錯誤,則會返回nil,以下面序列化的例子

func serialize(obj: AnyObject) -> String {
    guard let jsonString = try? someSerializeFuncMayThrowError(obj) else {
        print(jsonString)
    }
    print("fail")
}

try?配合guard let一塊兒使用效果更好

十4、斷言

斷言可讓咱們在調試時候更好的發現問題,排查錯誤,幾乎全部的高級語言都支持斷言,swift也如此,斷言的代碼在release的時候回被忽略,不會影響發佈程序的性能,只會在調試的時候生效

// 若是age小於0,程序會中止,並輸出錯誤信息
assert(age >= 0, "A person's age cannot be less than zero")

十5、泛型

關於泛型的介紹,這裏不進行說明,swift的泛型是我認爲最酷的特性之一,固然其餘語言也有,可讓類或函數更大程度的重用,swift的泛型與其餘語言的泛型有點相似

1. 定義

在類或函數聲明的時候,指定一個泛型類型參數(一般爲T)而後使用的時候直接把T當成類型使用

//泛型函數定義
func swapTwoValues<T>(inout a: T, inout b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

//泛型類定義
class Result<T> {
    var code: Int = 0
    var errorMessage: String?
    var data: T?
}

//多個泛型類型參數
class Result<T, TK> {
    var code: Int = 0
    var errorMessage: String?
    var data: T?
    var subData: TK?
}

2. 泛型約束

咱們還能夠對泛型進行約束,泛型類型參數只能是某些類型的子類,或實現了某些協議

func findIndex<T>(array: [T], valueToFind: T) -> Int? {
    for (index, value) in array.enumerate() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

上面函數會報編譯錯誤,由於在swift裏,並非全部的類都能用==操做符比較,只有實現了Equatable協議的類才能用==操做符,修改成

func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
    for (index, value) in array.enumerate() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

3. 多泛型類型參數

有時候咱們須要用多個協議進行約束,可使用下面方式(類與函數的使用方式相似)

func someFunc<T : protocol<StudyProtocal, RunProtocal>>(arg: T) {
    // do stuff
}

若是約束既有類又有協議的話可使用where添加限制條件

func someFunc<T, TK where T:Student, T: StudyProtocal>(t: T, tk: TK) {
    // do stuff
}

4. 泛型是不可變的

var dog1 = SomeClass<Parent>()
var dog2 = SomeClass<Son>()

dog1 = dog2       // 報錯

關於可變,不可變,逆變,協變參考這裏:http://swift.gg/2015/12/24/friday-qa-2015-11-20-covariance-and-contravariance/

5. 泛型協議

swift的協議不支持泛型,不能像類同樣定義泛型,而是經過類型參數定義泛型

protocol GenericProtocol {
    associatedtype T1
    associatedtype T2
    func someFunc(t2: T2) -> T1
}

class SomeClass<T> : GenericProtocol {
    // 設置泛型類型
    typealias T1 = String
    typealias T2 = T

    func someFunc(t2: T2) -> T1 {
        return ""
    }
}

十6、運算符重載

與其餘高級語言的同樣,swift也提供了運算符重載的功能,咱們能夠自定義運算符的實現,運算符一般分爲三種類型

  • 單目運算符:<運算符><操做數><操做數><運算符>,如!a

  • 雙目運算符:<操做數><運算符><操做數>,如:1 + 1

  • 三元運算符:<操做數><運算符><操做數><運算符><操做數>,如:a ? b : c

swift的運算符重載

  • 支持自定義運算符/, =, -, +, *, %, <, >, !, &, |, ^, ., ~的任意組合。能夠腦洞大開創造顏文字。

  • 不能對默認的賦值運算符=進行重載。組合賦值運算符能夠被重載,如==!==!

  • 沒法對三元運算符a ? b : c進行重載

  • 運算符聲明和定義只能定義在全局做用域,不能定義在類/結構體/枚舉內
    *

1. 前綴,中綴,後綴運算符

  • 前綴prefix:默認的有-,!,~等

  • 中綴infix:默認的有+,*,==等

  • 後綴postfix:默認的有:++,--等

1.1 聲明運算符

若是實現不存在的運算符須要添加運算符聲明(系統的提供的,能夠不須要聲明),聲明必須放在全局做用域

// 前綴運算符
prefix operator +++ {}

// 中綴運算符(二元運算符)
infix operator +++ {}

// 後綴運算符
postfix operator +++ {}

1.2 實現上面三個運算符

// 定義Point結構體
struct Point {
    var x: Int
    var y: Int
}

// 重載操做符要放在全局做用域
func +++ (left: Point, right: Point) -> Point {
    return Point(x: left.x + right.x, y: left.y + right.y)
}

// 若是須要修改操做數,須要添加inout關鍵字
prefix func +++ (inout left: Point) {
    left.x += 1
    left.y += 1
}

postfix func --- (right: Point) -> Point {
    return Point(x: right.x - 1, y: right.y - 1)
}

1.3 使用

var p1 = Point(x: 12, y: 21)
var p2 = Point(x: 12, y: 2)

let p3 = p1+++p2            // p3.x = 24, p3.y = 23
+++p1                       // p1.x = 13, p1.y = 3
p1---                       // p1.x = 12, p1.y = 2

2. 優先級

這個很好理解,就是優先級高的運算符先執行,聲明運算符的時候能夠指明優先級

infix operator ^ {
    associativity left        // 結合性,後面說
    precedence 140            // 指定運算符優先級
}

這裏能夠查看默認運算符的優先級

3. 結合性

運算符還能夠定義結合性,對於雙目運算符,當優先級同樣的時候,能夠定義運算符優先進行左結合仍是右結合,運算符的結合性有下面三種

  • left:左結合

  • right:右結合

  • none:無

結合性設置爲left

// 定義一個雙目操做符
infix operator ^ {
    associativity left         // 結合性
    precedence 140             // 指定運算符優先級
}

func ^ (left: Int, right: Int) -> Int {
    return Int(pow(Double(left), Double(right)))
}

let a = 2 ^ 2 ^ 2 ^ 2           // 執行結果爲256
// 至關於
let aa = ((2 ^ 2) ^ 2) ^ 2

若是咱們設置結合性爲right

// 定義一個雙目操做符
infix operator ^ {
    associativity right         // 結合性
    precedence 140              // 指定運算符優先級
}

func ^ (left: Int, right: Int) -> Int {
    return Int(pow(Double(left), Double(right)))
}

let a = 2 ^ 2 ^ 2 ^ 2           // 執行結果爲65536
// 至關於
let aa = 2 ^ (2 ^ (2 ^ 2))

若是結合性設置爲none,則會報錯,沒法判斷

十7、參考連接

二10、總結

總的來講,swift仍是比較裝逼的,整出不少新名詞,新概念,例如,指定構造器,便利構造器,構造器代理,但其實這些東西在別的語言基本上有,沒那麼複雜,另外swift的關鍵字太多了,有些無關緊要,是否是蘋果看到什麼好的就想往swift裏面塞仍是怎麼着,我的感受編程語言應該是輕便,簡單

最後安利一下本身的博客:http://zhengbomo.github.com

相關文章
相關標籤/搜索