Swift4.1第一章翻譯

歡迎使用Swift

關於Swift

用swift來寫代碼是一種很棒的方式,無論是手機、電腦客戶端服務端或者是其餘別的均可以用swift代碼來運行。她是一種安全快速 交互式的編程語言,結合了現代優秀編程語言的最佳思惟,從更加普遍的蘋果工程文化和開源社區中汲取更多的智慧。編譯器對性能作了很大的優化,而且她的語言也爲開發者作了優化,也就是說在性能和語言優化上,它沒有作出妥協。編程

swift是一門極其友好的新語言。她是一種具備工業品質的一門編程語言,和腳本語言同樣具富有表現力和愉悅感。用playground來寫swift語言能給你所見即所得的體驗,當你寫完一段代碼的時候,你不須要從新去編譯也不須要從新運行一個app就能夠看到代碼執行的結果。swift

Swift採用了現代的編程模式來定義大量的常見編程錯誤數組

例如:安全

1.變量在使用以前必需要先初始化閉包

2.數組在越界的時候發生錯誤app

3.整形類型須要檢查是否溢出框架

4.可選類型的變量要確保進行過判斷nil處理less

5.內存是系統本身管理的編程語言

6.對錯誤的處理容許從意外故障中恢復編輯器

爲了充分利用現代的硬件,swift代碼在不斷被編譯和優化。語法和標準庫的設計基於指導庫的原則,這很顯然使咱們的代碼表現的更好。速度和安全的結合使得swift成爲從hello world 到整個操做系統實現的最佳選擇。

Swift將強大的類型推斷和模式匹配與現代化的輕量級的語法相結合,使得咱們能夠用清晰的表達式來表達複雜的想法。所以,代碼不只僅是容易寫,並且易於閱讀和維護。

Swift的發展已經有一些年了,而且隨着新特性的發展在不斷的改進。咱們swift的目標是雄心勃勃的。咱們火燒眉毛的看看你用swfit能創造出來什麼。

版本兼容性

這本書主要講的是Swift 4.1,默認的版本的Swift是基於Xcode 9.2 版本。不論語言是Swift4 或者Swift3你均可以用Xcode9.2來進行編譯。

注意:

當你在Swift4 環境下使用swift3的代碼的時候,語言的標識應該是3.2.所以,你可使用像#if swift(>=3.2)這樣的條件編譯代碼塊來編寫與多個版本兼容的swift代碼。

當你使用Xcode9.2來編譯Swift3代碼的時候,大部分Swift4的功能是可用的。可是下面的一些特性只在Swift4下是可用的。

子串的操做返回值只能是子串的類型,不能是父串的類型
@objc 這些隱式的屬性被用在更少的地方。
同一個文件中某個類型的延展,能夠訪問該類型的私有成員變量。



一個用Swift4編寫的工程能夠依賴用Swift3寫的代碼,反之用Swift3寫的工程也能夠依賴Swift4的代碼。
若是你有一個大的工程是由多個framework組成的,那麼將你能夠一次性的將代碼從Swift3移植到Swift4框架中。

Swift之旅

大多數人在開始學習一門新的語言的時候,按照慣例都要在打印這句單詞Hello, world!在Swift中咱們一樣也能夠用一句單獨的話來表達。

print("Hello, world!")

若是你以前用C語言或者Objective-C語言寫過代碼的話,swift中的這個語法對你來講應該是很熟悉的,這一行代碼就是一個完整的程序。你不須要引入另一個框架來實現輸入和輸出一個字符串的操做。咱們在使用一個實體的時候都是在全局做用域中使用的,所以這裏咱們不須要main函數。固然你也沒必要在每個聲明以後添加分號來結束。
這個旅行的小冊子給你足夠的信息關於怎麼用swift來完成一系列的編程任務。若是你對一些不明白的話,也不要擔憂。在剩下的這本書裏將會爲你詳細的介紹關於swift的全部東西。

注意:
使用mac下載這個playground,雙擊文件在Xcode 中打開 下載地址: https://developer.apple.com/g...

值的使用

let來定義一個常量,用var來定義變量。常量不須要在編譯期就暴露出來,你必須一次性給它賦一個特別明確的值。這就意味着你可使用一個常量來表明一個值。這個值只被賦值一次可是能夠被不少地方使用。

var myVariable = 42
myVariable = 50
let myConstant = 42

一個常量或者變量要想對它進行賦值的時候,你必須得知道須要賦什麼類型的值給它們。然而,並非全部的變量都須要直接定義類型的。當你聲明一個變量或者常量的時候給他們一個類型的值,讓編譯器本身去判斷你所定義值的類型。在上面的例子中,編譯器會本身判斷myVariable 是一個integer類型的常量,由於它是被一個integer類型初始化的。
常量或者變量初始化的值並不必定能提供給咱們太多的信息(也可能這個常量或者變量沒有賦初始值),你能夠給變量或者常量進行類型的綁定設置,用一個冒號隔開。

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble:Double = 70

小測試:
建立一個值爲4 的浮點型常量

let floatValue:Float = 4

一個值不可以直接被轉化爲另一種類型(隱式轉換),若是真的須要把當前值轉化爲一個不一樣的類型,咱們須要經過顯式轉換來得到目標值的類型。

let label = "The width is"
let width = 94
let widthLabel = label + String(width)

小測試:
若是去掉最後一行將width 轉化爲String的代碼,將會出現什麼錯誤呢?
結果:Binary operator '+' cannot be applied to operands of type 'String' and 'Int'

還有一種簡單的方式能夠把值轉化爲字符串:將你所要轉化的值用圓括號括起來,而後放一個斜槓\到圓括號的前面,例如:

let apples = 3
let orange = 5
let appSummary = "I have \(apples) apples. "
let fruitSummary = "I have \(apples + orange) pieces of fruit. "

小測試:
在一句歡迎語中,將一個浮點型的值和一我的名用()拼接到一塊兒。

let floatValue = 70.0
let stringName = "Kevin"
let stringValue = "My name is " +stringName+ "The Tencent rock price is \(floatValue)"

用三個雙引號"""來定義一個多行的字符串,引號的縮進規則是相同的,例如:

let quotation = """
I said "I have \(apples) apples." And then I said "I have \(apples+orange) pieces of fruit."
"""

[]建立一個數組或者字典,用 []獲取數組的某一個元素或者字典的某一個key,最後一個元素能夠用隔開。

var shoppingList = ["catfish","water","tulips","bluePrint"]
shoppingList[1] = "bottle of water"
var occupations = [
"malcolm":"Caption",
"Kaylee":"Mechanic",
]
occupations["Jayne"] = "Public Relations"

用初始化方法建立一個空的數組或者字典

let emptyArray = [String]()
let emptyDictonary = [String:Float]()

假設給出值的類型可以被編譯器推測出來,你能夠像這樣寫一個空的數組[]或者是一個空的字典[:]-- 例如:你能夠像給一個變量賦值或者傳遞參數同樣。

shoppingList = []
occupations = [:]

控制流程

if或者switch來處理一些條件判斷,用for-in while 和repeat-while 作循環。判斷條件之間的圓括號能夠省略,可是語句之間的大括號是不能夠省略的。

let individualScores = [75,43,103,87,12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    }else {
        teamScore += 1
    }
}
print(teamScore)

在一個 if條件語句中,判斷的條件必須是一個bool類型的值,這就是說像 if score {...}這樣寫的代碼是錯誤的,由於編譯器不會隱式的與零值作比較。
你能夠會用iflet這個組合來處理賦值中變量值爲空的狀況,這些值表明是可選類型的值。一個可選類型的值可能包含一個值或者包含一個nil來表示這個值是空的。在定義值類型後面添加一個?表示這個值是可選類型。

小測試:

var optionalString: String? = "hello"
print(optionalString == nil)
var optionalName: String? = "john appleseed"
var greeting = "hello"
if let name = optionalName {
    greeting = "hello,\(name)"
}

小測試:
optionalName的值改成nil。那麼greeting 的值應該是什麼呢?將optionalName的值改成nil並在if語句以後添加一個else 此時輸出greeting 語句。

var optionalString1: String? = "hello"
print(optionalString1 == nil)
var optionalName1: String? = nil
var greeting1 = "hello"
if let name1 = optionalName1 {
    greeting1 = "hello,\(name1)"
}else {
    greeting1 = "this is an errorCode!"
}

輸出結果爲:"this is an errorCode!" 若是optionalName 爲空的時候if判斷中的代碼就不會執行,此時執行是else語句.

若是可選類型 值爲nil,由於判斷條件爲false不知足那麼代碼就會被跳過。反之若是可選類型的值就會被解包並複製給let 聲明的常量,解包的值就可以在代碼塊中(花括號中)使用了。

另一種處理可選類型的方式是用??操做符提供一個默認的值。若是可選類型的值爲空的時候,默認值就會用來替換可選類型的值。

let nickName: String? = nil
let fullName:String = "john Appleseed"
let informalGreeting = "hi \(nickName ?? fullName)"

Switches 語句支持任意類型數據的比較操做-他們不侷限於基本數據類型的比較,還能用於等式的比較。

let vegetable = "red papper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber","watrcress":
    print("that would make a good tea sandwich")
case let x where x.hasSuffix("papper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}

小測試:
試着移除switch的default case 。出現什麼錯誤了?

Switch must be exhaustive

經過上面的操做你能夠看到,let被用於接收可變值,而後對常量let進行等式判斷。
執行完switch中對應的case以後,程序會結束switch語句的執行,所以,不用像OC同樣寫完一個switch以後緊跟一個break來結束 switch語句。
你能夠用for - in這個語句遍歷字典中的鍵值對。因爲字典是無序的,所以他們鍵值對也是無序的。

let interestingNumbers = [
    "Prime":[2,3,5,7,11,13],
    "Fibonacci":[1, 1, 2, 3, 5, 8],
    "Square":[1, 4, 9, 16, 25],
]
var largest = 0
for (_,numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)

小測試:
添加另一個變量來找到number中的最大值,同時記錄下最大的number是哪一個?

var largestTest = 0
var largestNumber = ""
for (kindTest,numbersTest) in interestingNumbers {
    for numberTest in numbersTest {
        if numberTest > largestTest {
            largestTest = numberTest
            largestNumber = kindTest
        }
    }
}
print("the largest is \(largestTest),the lastNumber is \(largestNumber).")

用while來重複一段代碼,直到條件知足。若是把循環條件寫在結尾的話,至少要保證循環走一遍。

var n = 2
while n < 100 {
    n *= 2
}
print("the result of n is \(n)")
var m = 2
repeat {
    m *= 2
} while m < 100
print("the result of m is \(m)")

你可使用..<來完成一個在必定範圍內循環。

var total = 0
for i in0 ..<4 {
total += i
}
print(total)

..< 來進行循環處理的時候,不包括邊界的值。若是你想包括邊界的值 那麼用...來表達。

函數和閉包

func來聲明一個函數。函數調用的時候直接使用函數的名字,若是函數有參數的時候要帶上參數。用->來分割參數名和函數返回值類型。

func greet (person:String,day:String) ->String {
return "Hello \(person),today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

小測試:
去掉函數的參數day,而後添加另一個參數今天午餐到問候語中。

func greetPre (person:String,meal:String) ->String {
    return "Hello,\(person),what will you  have on  \(meal)?"
}
greetPre(person: "Kevin", meal: "Supper")

通常來講,函數用它們的形參做爲實參的標籤。實參能夠寫在形參前面的標籤作自定義,或者用_ 來表示沒有實參標籤。

func greetAdd(_ person:String, on day:String) ->String {
return "Hello \(person),today is \(day).
}
greetAdd("John", on:"Wednesday")

咱們能夠用一個元組來處理返回多個值的狀況(複合值),好比:一個函數中有多個返回值。元組中的每個元素能夠用它們的名字或者它們的編號來表示。

func calculateStatistics (scores:[Int]) -> (min: Int, max:Int, sum:Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0
    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }
    return (min, max, sum)

}
let statistics = calculateStatistics(scores: [5,3,100,3,9])
print(statistics.sum)
print(statistics.2)

函數是能夠進行嵌套的,嵌套函數能夠對聲明在外層函數的參數進行訪問。你能夠在一個既長又複雜的函數中用函數嵌套來進行組織代碼。

func returnFifteen() ->Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

函數是第一類對象,這意味着一個函數能夠用另一個函數的返回值做爲本身的返回值。

func makeIncrementer() -> ((Int) ->Int) {
    func addOne(number : Int) ->Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

函數也能夠用另一個函數做爲它的參數。

func hasAnyMatches(list:[Int], condition:(Int) ->Bool) ->Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) ->Bool {
    return number < 10
}
var numbers = [20, 9, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

函數是一種特殊的閉包:block內部的代碼會延時執行。當閉包建立的時候,閉包中的代碼能夠對閉包中的變量和函數進行訪問,就像以前的嵌套函數同樣,閉包也能夠在其餘不一樣的空間中執行。你能夠定義一個用{}包圍起來可是沒有名字的閉包,用in來分割函數名和函數的返回值。

numbers.map ({(number:Int) ->Int in
let result = 3 * number
return result
})

小測試:
重寫閉包,當值爲奇數時返回0

numbers.map ({(number:Int) ->Int in
if number % 2 == 0 {
return 0
}
return number
})

若是你想更簡單的寫一個閉包,那麼下面幾種方式。若是一個包的類型已經肯定,好比說一個代理的回調,你能夠省略參數的類型,它的返回類型或者二者都省略。單語句的閉包隱式返回執行的結果。

let mappedNumbers = numbers.map({number in 3 * number})
print(mappedNumbers)

當你使用參數的時候,你能夠用參數所在位置來替代參數的名字,這在短的閉包中特別有高效。當閉包做爲函數的最後個參數時,它能夠直接跟在圓括號以後。若是閉包是函數僅有的一個參數,你能夠直接省略掉圓括號。 

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)

類與對象

class加類名來建立一個類。一個類中屬性的聲明常量和變量方式是相同,只不過它被聲明在一個類的上下文中。一樣的,方法和函數的聲明寫法是同樣的。

class Shape {
    var numberOfSides = 0
    func simpleDescription() ->String {
        return "A shape with \(numberOfSides) sides."
    }
}

小測試:
let定義一個常量並定義一個帶有參數的方法。

let letConstrant = 5
func letConstant(parame:Int) ->String {
    return "B shape with\(letConstrant) sides."
}

經過在類名後面添加 一個()來建立一個類的實例。使用點語法訪問這個實例的屬性和方法。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
print(shape.simpleDescription(),"\n",shape.letConstant(parame:5))

上面這個版本的Shape類少了一些重要的東西:當一個實例建立的時候應該有一個初始化方法用init方法來建立一個。

class NamedShape {   
    var numberOfSides:Int = 0
    var name:String
    init(name:String) {
        self.name = name
    }
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }    
}

注意觀察在初始化方法中是怎麼用self來區分出調用的name是參數仍是屬性。當你建立一個了類的實例的時候,初始化參數就能像函數同樣被傳遞使用了。每個屬性就須要被分配一個值,無論它是聲明中(好比說上面的變量numberOfSides)或者是初始化的時候(像上面的name)。
若是你想在對象被銷燬前作一些處理工做,能夠用deinit來建立一個析構器。
在每一個子類的後面每每都跟着它所繼承的父類,用冒號隔開。一個類對於繼承任何標準的基類並非必須的,因此你能夠選擇引用或者省略掉父類。
子類重寫父類定義的方法的時候用關鍵詞override來標記。這是爲了防止意外被重寫。若是沒有override關鍵字,編譯器會報錯。編譯器也會檢測在子類override的方法是否是真的重寫了父類的某個方法。

class Square:NamedShape {
    
    var sideLength:Double
    init(sideLength:Double, name:String) {
        self.sideLength = sideLength
        super .init(name: name)
        numberOfSides = 4
    }
    func area() -> Double {
        return sideLength * sideLength
    }
    
    override func simpleDescription() -> String {
        return "A square with sides of length\(sideLength)."
    }
    
}
let test  = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

小測試:
再定義一個繼承自NamedShape的類Circle,在它的初始化方法中有一個半徑和name的兩個參數。在這個類中實現area和simpleDescription兩個方法。

class Circle:NamedShape {
    
    var radius:Double
    init(radius:Double,name:String) {
        self.radius = radius
        super.init(name: name)
        numberOfSides = 6
    }
    func area() -> Double {
        return Double.pi * radius * radius
    }
    override func simpleDescription() -> String {
        return "The radius of the circle is \(radius)."
    }
    
}
let testCircle = Circle(radius: 2, name: "the circle")
testCircle.area()
testCircle.simpleDescription()

屬性除了可以作簡單的存儲以外,還可以添加gettersetter方法。

class EquilateralTriangle:NamedShape {
    
    var sideLength:Double = 0.0
    init(sideLength:Double,name:String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    var perimter:Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }
    override func simpleDescription() -> String {
        return "An equilteral Triangle with sides of length\(sideLength)."
    }
}
var triangle = EquilateralTriangle (sideLength: 3.1, name: "a triangle")
print(triangle.perimter)
triangle.perimter = 9.9
print(triangle.sideLength)

perimtersetter方法中,新值被隱式的命名爲newValue,你能夠在setter方法的圓括號內爲新值從新命名。
咱們注意到對EquilateralTriangle進行初始化的時候有如下三個步驟,

  • 對父類聲明的屬性進行賦值
  • 初始化的時候調用父類的初始化方法
  • 改變在父類定義的屬性的值,一些額外的設置工做在調用方法以及 gettersetter方法的時候也會完成。

若是你並非真的要計算一個屬性的值,可是仍然須要添加相應的代碼,用willset和didset來操做新值。這段代碼會在初始化以外屬性值發生任意改變的時候執行。例如:下面的類保證了三角形的每一條邊的長度和正方形的每一條邊的長度都相等。

class TriangleAndSquare {
    var  triangle:EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var  square:Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size:Double,name:String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}

var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)

當值是可選的時候,你能夠在一些操做以前添加例如:使用方法、屬性、下標的時候。若是在?以前值已是nil,那麼在以後的全部操做均可以被忽視掉,整個表達式的結果就是nil。相反的,可選值將被展開(也就是說可選值是有值的),那麼在以後的代碼將會按照展開的值執行。綜合以上兩種狀況,表達式的值是可選值。

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideWidth = optionalSquare?.sideLength

枚舉和結構體

使用enum關鍵字來建立一個枚舉,建立方式就像咱們建立類和其餘數據類型的數據同樣,枚舉裏面可能會包含一些和枚舉值相關的方法。

enum Rank:Int {
    case ace = 1
    case two,three,four,five,six,seven,eight,nine,ten
    case jack,queen,king
    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let  ace = Rank.ace
let aceRawValue = ace.rawValue

小測試:
寫一個函數從枚舉Rank中取出兩個枚舉值比下他們的rawValue

func compareFromRang(_ a:Rank,_ b:Rank) -> String{
    if a.rawValue > b.rawValue {
        return "\(a.simpleDescription())'s rawValue is the max than \(b.simpleDescription())'s"
    }else {
        return "\(a.simpleDescription())'s rawValue is the max than \(b.simpleDescription())'s"
    }
}
compareFromRang(Rank.jack,Rank.king)

在swift中,枚舉的默認值是從0開始,枚舉中成員的值是自增的,可是你能夠給成員自定義值。在上面的例子中,Ace就被明確的賦值爲1,它以後的成員就是按照順序自增的。你可使用字符串或者浮點類型的值做爲枚舉的初始值。使用rawValue這個屬性來訪問枚舉中成員變量的值。
你可使用init?(rawValue:)這個初始化方法來獲取枚舉中某個成員的值。若是在Rank這個枚舉中可以找到的話,返回相對應的值。若是找不到那麼就返回nil

if  let convertedRank = Rank (rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
    print(threeDescription)
    
}

枚舉中的每一個case值都是實際的值,另一種寫法是給他們本身定義值。事實上,若是這個值沒有特殊的意義,你沒有必要從新爲他們賦值。

enum  Suit {
    case spades,hearts,diamonds,clubs
    func simpleDescription() ->String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
}

let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()

小練習:
爲上面的Suit枚舉添加一個color()方法,使得 spadesclubs 返回black heartsdiamonds返回red

func colorDescription()->UIColor {
        switch self {
        case .spades,.clubs:
            return UIColor.black
        case .hearts,.diamonds:
            return UIColor.red
        }
    }

在枚舉外面調用

let heartsColorDescription = hearts.colorDescription()

上面咱們能夠看出用兩種方式引用了hearts這個枚舉成員,當咱們hearts這個從常量賦值的時候,枚舉中的Suit.hearts須要用全名來引用(也就是說先找到枚舉所屬,而後使用它的成員變量),由於常量沒有顯式的指定類型。在switch中,枚舉成員使用它的縮寫.hearts來引用,因在suit這個枚舉中self是已知的類型。若是值的類型是已知的話可使用縮寫。
若是一個枚舉有本身的初始值,這些值在他們聲明的時候就已經肯定了。那就意味着:不一樣枚舉實例中相同成員變量都有一個相同的值。另一種爲枚舉成員變量的值進行關聯,那就是在建立枚舉成員變量的時候肯定的。這意味着枚舉成員的關聯值可能會不一樣。你能夠把關聯值理解爲枚舉成員的寄存屬性。例如:能夠考慮一下從服務端來獲取日出和日落時間狀況的請求。服務端會返回你正確的結果或者錯誤的描述信息。

enum ServerResponse {
    case result(String, String)
    case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
    print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
    print("Failure ... \(message)")
}

小練習:
爲上面的枚舉ServerResponse添加第三個成員變量,並添加相應的switch語句。
注意一下日出和日落的時間是怎麼從ServerResponse中獲取到而且和switch中的case 相匹配的。
struct建立一個結構體,結構體和類有不少類似之處,好比說方法和構造器。他們之間最大的區別就是結構體是用來傳值的,而類是傳的是引用。

struct Card {
    var rank:Rank
    var suit:Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
    
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

小練習:
Crad添加一個方法,建立一副完整的撲克牌並把ranksuit對應起來。

協議和擴展

protocol來聲明一個協議

protocol ExampleProtocol {
    var simpleDescription:String{get}
    mutating func adjust()
}

類、枚舉和結構體都可以遵循並實現協議。

class SimpleClass:ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty:Int = 69105
    func  adjust() {
        simpleDescription += " Now 100% adjusted."
    }
    
}
var a  = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure:ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += "(adjusted)"
    }
    
}
var b = SimpleStructure ()
b.adjust()
let bDescription = b.simpleDescription

小練習:寫一個枚舉遵照上面的這個協議。

enum SimpleEnum:Int,ExampleProtocol {
    case type,title
    var simpleDescription:String {
        switch self {
        case .type:
            return "A simple  enum  about  type"
        case .title:
            return "A simple  enum  about  title"
        }
    }
    func adjust() {
        print("this is enum")
    }
}
var c = SimpleEnum.type
c.simpleDescription
c.adjust()

注意在聲明SimpleStructure的時候使用關鍵字mutating來標記一個能改變結構體值的方法。SimpleClass的聲明不須要用關鍵字來標記修改它的方法,由於類裏的方法是能夠一直髮生改變的。
extension延展來爲現有的類添加功能,好比添加一些新的方法和能夠用來計算的屬性。你能夠用延展來添加一個協議給一個在其餘地方建立的類,甚至添加的類型多是引用的一個library或者框架。

extension Int:ExampleProtocol {
    
    var simpleDescription:String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
print(7.simpleDescription)

小練習:
Double類型的數據添加一個absoluteValue屬性

extension Double {
    var absoluteValue:String {
        return "The Double number is \(self)"
    }
}
print(2.0.absoluteValue)

你能夠像使用其餘命名類型同樣來使用協議名,例如:建立一個具備不一樣類型的數據的集合,這些不一樣類型的數據都遵循同一個協議。當你要處理的類型是協議的值的時候,協議以外定義的方法是不可用的。

let protocolValue:ExampleProtocol = a
print(protocolValue.simpleDescription)
// 打開註釋你就會發現報錯信息
//print(protocolValue.anotherProperty)
//Value of type 'ExampleProtocol' has no member 'anotherProperty'

儘管protocolValue這個變量在運行時的時候類型是SimpleClass,編輯器在編譯的時候仍然把它的類型當ExampleProtocol。這就意味着你不能訪問類在協議方法以外的屬性或者方法。

錯誤處理

你能夠用任何遵循Error協議類型來表示錯誤。

enum PrinterError:Error {
    case outOfPaper
    case noToner
    case onFire
}

throw這個關鍵字來拋出一個錯誤,而且用throw來標記一個能夠拋出錯誤的一個函數。若是你在一個方法裏拋出了一個錯誤,函數會當即返回而且會調用函數的代碼進行處理錯誤。
處理error的方式有不少種,一種方式是用do-catch。在do這個代碼塊裏面,在你須要標記的拋出錯誤的代碼以前添加try。在catch的代碼塊裏,錯誤會自動被命名error,固然除非你給錯誤指定一個新的名字。

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi sheng")
    print(printerResponse)
} catch  {
    print(error)
}

小練習:
改變打印者的名字爲「Never Has Toner」,讓send(job:Toner)拋出一個錯誤。

do {
    let printerResponse = try send(job: 1040, toPrinter: "Never Has Toner")
    print(printerResponse)
} catch  {
    print(error)
}

你能夠本身定義多個catch代碼塊來處理錯誤。你能夠在catch以後添加一種模式,用法能夠和switch中使用成員變量case同樣。

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here,with the rest of the fire")
} catch let printferError as PrinterError {
    print("Printer error:\(printferError)。")
} catch {
    print(error)
}

小練習:
do代碼塊中添加拋異常的代碼。你要拋出什麼樣的錯誤才能讓第一個catch接收並處理?第二個 第三個呢?
send(job:Int,toPrinter printerName:String)做補充就能夠獲得問題的答案了

func send(job:Int,toPrinter printerName:String) throws-> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    } else if printerName == "Out Of Paper"
    {
        throw PrinterError.outOfPaper
    }
    else if printerName == "On fire" {
        throw PrinterError.onFire
    }
    return "Job sent"
}

另一種處理錯誤的方式是使用try?關鍵字來轉換結果爲可選項。若是函數拋出了一個錯誤,錯誤將被忽略而且結果會被置爲nil。
不然,結果就是一個包含了函數返回值的可選項。(這就是說若是拋出異常那麼結果就是nil,若是不拋出異常那麼結果就是一個帶有一個返回值的可選項。)

let printerSuccess = try?send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try?send(job: 1885, toPrinter:"Never Has Toner")

使用defer代碼塊來表示在函數返回以前,函數執行的代碼。這段代碼會執行無論函數是否拋出錯誤。使用defer你能夠將設置和清除的代碼寫在裏面,儘管它們執行的時機可能徹底不一樣。

var fridgeIsOpen = false
let fridgeContent = ["milk","eggs","leftovers"]
func fridgeContains(_ food:String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }
    let result = fridgeContent.contains(food)
    return result
    
}
fridgeContains("banana")
print(fridgeIsOpen)

泛型

在尖括號裏寫一個名字來建立一個泛型函數或者泛型的類型。

func makeArray<Item>(repeating item:Item, numberOfTimes:Int) -> [Item] {
    var result = [Item]()
    for _ in 0 ..< numberOfTimes {
        result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes: 4)

你也能夠建立泛型的函數和方法,在類、枚舉和結構體裏均可以使用。

// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
    case none
    case some(Wrapped)
}
var possibleInteger:OptionalValue<Int> = .none
possibleInteger = .some(100)

在類型名字的後面使用where來指定對類型的一系列的需求,例如:限定一個類型來執行一個協議,限定兩個類型是相同的,指定一個類有一個特定的父類。

func anyCommonElements<T:Sequence,U:Sequence>(_ lhs:T,_ rhs:U)->Bool
    where T.Iterator.Element:Equatable,
    T.Iterator.Element ==
    U.Iterator.Element {
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    return true
                }
            }
    }
    return false
}
anyCommonElements([1,2,3], [3])

小練習:
修改anyCommonElements(:)函數來建立一個函數,返回一個數組。內容是兩個序列裏的公共元素。

func anyCommonArrayElements<T:Sequence,U:Sequence>(_ lhs:T,_ rhs:U)->Array<Any>
    where T.Iterator.Element:Equatable,
    T.Iterator.Element ==
    U.Iterator.Element {
        var  array = Array<Any>()
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                   array.append(lhsItem)
                }
            }
        }
        return array
}
anyCommonArrayElements([1,2,3], [2,3,4,5])

上面咱們所寫的<T:Equatable><T>...where T:Equatable 是等價的。

相關文章
相關標籤/搜索