Swift語言入門之旅 (翻譯自《The Swift Programming Language》電子書)


關於Swift

  Swift是為IOS和OSX應用制定的新編程語言,吸取C和Objective-C語言的精粹,但不損失與C語言的兼容性。Swift采用安全編程模型、加入了各種現代編程語言特性,使得該語言更易被掌握、更具擴展性,用起來更有趣。Swift語言的奠基石是已經成熟的、並為大家所喜愛的Cocoa和Cocoa Touch框架,新語言使大家可以盡情暢想新軟件開發的機遇。

  Swift沉積了多年的研發成果,蘋果公司為提供高效Swift語言編譯器、調試器和基礎架構打下了堅實基礎。我們使用Automatic Reference Counting(ARC)技術簡化內存管理。我們的框架設計,建立在Cocoa穩固的基礎框架上。已經徹底完成了標准化和現代化的改進。Objective-C的改進支持了塊定義,集合語法、模塊,使語言框架合理集成了現代語言技術。要感謝前人的基礎工作,使我們現在能夠向大家介紹蘋果公司未來的軟件開發語言。

  對Objective-C開發者來說,Swift是比較容易上手的,它采用和Objective-C類似的參數命名規則和強大的動態對象模型。它提供無縫的使用Cocoa框架和與Objective-C代碼混合即用的互操作特性。構建在這些基礎上,Swift語言帶來了許多新的特性、也重新集成了過程式語言和面向對象語言功能。

  對新學習編程的人來說,Swift語言是門友好的語言。一門作為工業前沿的系統編程語言,Swift語言能夠象腳本語言一樣易於表述和理解。它支持“playgrounds”技術,作為一項革新功能,開發者可以使用它進行Swift的即時運行和直觀地看到結果,而不用從頭重新編譯運行程序。

  Swift集成了現代計算機語言的智慧,匯入了蘋果公司軟件研發的精粹。Swift編譯器針對性能進行了優化,而語言本身也針對現有的開發環境進行了優化,功能和性能得到平衡和發展。它能夠設計小到“hello world”程序、大到操作系統級別的程序。所有這一切使swift語言對開發者和蘋果公司來說都將更有吸引力。

  使用Swift語言編寫iOS和OS X應用將非常愉悅,它將繼續發展新的語言特性、包含更多功能。我們對Swift的發展雄心勃勃,也迫不及待地將這個工具送給大家去使用、去創造。

Swift入門之旅

  學習一門新的計算機語言,傳統來說都是從編寫一個在屏幕上打印“Hello world”的程序開始的。那在Swift,我們使用一句話來實現它:

println("Hello, world")

  如果你編寫過C或者Objective-C語言,Swift中的這個語法看起來便很熟悉,這句話卻是一個完整的程序。你無須為了得到輸入/輸出或字符串處理去導入一個獨立的功能庫。編寫全局代碼通常用於程序的進入點。你不需要寫一個main函數,你也不需要為每個語句寫個逗號作為結尾。

  這個入門之旅將給你足夠的信息去開始編寫Swift代碼,你可以實現許多的編程任務。這個過程中如果你有些內容不理解也不要擔心,我們將在本書的其他章節細節處為您解釋。 

批注:

  為了更好的體驗開發,請在Xcode中使用playground編輯這個章節的代碼。Playgrounds允許你在一邊編輯代碼的同時看到代碼的即時運行結果。

簡單賦值:

用於你把一個常量和變量作為成變量。常量的值不必每次都傳給編譯器,但你必須提前賦個數值給它。就是說常量可以賦值一次而多處引用。

var myVariable = 42

myVariable = 50

let myConstant = 42

相同的類型才能進行常量和變量的賦值,但是,你不用每次都顯式地定義類型,編譯器可以自動判斷。創建常量或變量的時候順便賦值可以讓編譯器知道它屬於什么類型。上面的例子,編譯器指定myVariable變量屬於整數類型,因為它用一個整數進行初始化。

如果初始值不能表達足夠的類型信息(或沒有初始值),則在變量后面加一個冒號和指定的類型。

let implicitInteger = 70

let implicitDouble = 70.0

let explicitDouble: Double = 70

 

練習:

顯式創建一個值為4的浮點類型的常量

 賦值不會自動隱式地變成另外一種類型,如果你需要把一個賦值變更到另外一種類型,你需要顯式地創建目標類型的實例。

 

let label = "The width is "

let width = 94

let widthLabel = label + String(width)

 

練習: 

試着把最后一行的字符串轉換聲明去掉,看看會出什么錯誤?

 

這里有更簡便的方法把數值包含在字符串里面:把數值寫在括號里面,把斜杠寫在括號前面,示例如下:

let apples = 3

let oranges = 5

let appleSummary = "I have \(apples) apples."

let fruitSummary = "I have \(apples + oranges) pieces of fruit."

 

試驗:

在字符串中使用 \()包含一個浮點計算結果,同時也包含對某個人的名字和祝詞。

 

使用方括號([])創建數組和字典,然后在方括號中使用索引下標存取各元素的值。

var

shoppingList = ["catfish", "water", "tulips", "blue paint"]

shoppingList[1] = "bottle of water"

 

var occupations = [

    "Malcolm": "Captain",

    "Kaylee": "Mechanic",

]

occupations["Jayne"] = "Public Relations"

 

我們使用初始化語法來創建空數組和字典

 

let emptyArray = String[]()

let emptyDictionary = Dictionary<String, Float>()

 

如果能推斷指定類型,你可以寫一個空數組[]和空字典[:]- 例如: 當你設置一個新給變量或傳值給函數的參數的時候。

 

shoppingList = []   // 購物和放置各種東西.

 

控制流:

使用if和switch來創建一個條件選擇邏輯,使用 for-in , for,while,和do-while來創建循環,大括號里面放置在條件或循環變量是可選的內容,而方括號在聲明主體中則是必備項。

let individualScores = [75, 43, 103, 87, 12]

var teamScore = 0

for score in individualScores {

    if score > 50 {

        teamScore += 3

    } else {

        teamScore += 1

    }

}

teamScore

 

在一個if的語句中,條件值必須是布爾表達式,所以象這樣的代碼是會出錯的:if score{...}, score不會隱式地返回零值。

 

你可以一起使用if和let語句賦值,但有可能賦值是空的,這些值代表它是可選的。一個可選值可以是包含實際值或是一個代表數值是空值的nil值。在類型聲明后面加上一個問號代表這個定義的數值是可空的。

var optionalString: String? = "Hello"

optionalString == nil

 

var optionalName: String? = "John Appleseed"

var greeting = "Hello!"

if let name = optionalName {

    greeting = "Hello, \(name)"

}

 

試驗:

將optionalName設置為nil. 請看那個輸出祝福語句的結果有什么變化?判斷optionName是空值時加上其他子句設置輸出不同的祝語。

如果可選值是nil,條件判斷為假,大括號中的代碼也將不會被執行。如果可選項不是nil的話,數值將被賦值給let后面指定的變量,同時,在后面的大括號中的代碼中,該變量將是可以訪問得到的。

 

開關語句(switch)支持所有的的類型和豐富多樣的各式比較,他們並不是只限定於整數或只是測試它是否相等。

 

let vegetable = "red pepper"

switch vegetable {

case "celery":

    let vegetableComment = "Add some raisins and make ants on a log."

case "cucumber", "watercress":

    let vegetableComment = "That would make a good tea sandwich."

case let x where x.hasSuffix("pepper"):

    let vegetableComment = "Is it a spicy \(x)?"

default:

    let vegetableComment = "Everything tastes good in soup."

}

 

試驗:

請嘗試去掉這個最后這個默認的default case選項,看看會得到什么錯誤。

 

當執行完對應的case分支代碼后,程序將退出switch語句,不再接着遍歷下一個case,所以也不用在每個case分支的結尾聲明跳轉出switch。你可以使用for-in來遍歷所有的字典里面的分項。

 

l

largest

 

 

試驗:

和程序里面檢查最大數字的變量相類似,在程序中加入另外一個變量跟蹤哪類的數字是最大的。

 

 

我們使用while語句以重復一個代碼塊的執行,直到條件變化而退出。循環的條件可以放置在語句塊的最后面,只要語句執行到最后就可以擊發判斷。

 

var n = 2

while n < 100 {

    n = n * 2

}

n

 

var m = 2

do {

    m = m * 2

} while m < 100

m

 

你可以在循環中使用一個循環指標,使用兩個點 (“..”) 來隔開起始值和終值表述它范圍或者明確定義它的起始值,終止條件或步進值,下面是兩個相同功能的循環語句。

 

var firstForLoop = 0

for i in 0..3 {

    firstForLoop += i

}

firstForLoop

 

var secondForLoop = 0

for var i = 0; i < 3; ++i {

    secondForLoop += 1

}

secondForLoop

 

使用兩個點號表示循環指標不包含終點值,使用三個點號表示循環指標包含終點值。

 

 

函數和閉包:

 

使用func來定義一個函數。給出函數名字和在括號中指出參數后我們就可以訪問一個函數,同時我們在定義函數的輸入參數的名字類型和輸出的類型之間使用“->”來隔開。

 

func greet(name: String, day: String) -> String {

    return "Hello \(name), today is \(day)."

}

greet("Bob", "Tuesday")

 

試驗:

移除名稱為day 的輸入參數,加入一個參數,使函數返回今天的特殊祝福語句。

 

使用元組來表達函數返回多個值

func getGasPrices() -> (Double, Double, Double) {

    return (3.59, 3.69, 3.79)

}

getGasPrices()

 

函數同樣也可以輸入可變數量的參數組

 func sumOf(numbers: Int...) -> Int {

    var sum = 0

    for number in numbers {

        sum += number

    }

    return sum

}

sumOf()

sumOf(42, 597, 12)

 

試驗:

編寫一個函數用於計算它的輸入函數的平均值。

 

函數可以嵌套。嵌套函數可以使用調用它的上級函數的變量。 如果代碼太長或太復雜,您可以使用嵌套函數來優化代碼。

 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, 19, 7, 12]

hasAnyMatches(numbers, lessThanTen)

 

函數是一個特例的閉包,你可以使用大括號包含一段沒有名字的代碼閉包。輸入參數和輸出參數之間應使用in語法隔開函數的主體。

numbers.map({

    (number: Int) -> Int in

    let result = 3 * number

    return result

    })

 

試驗:

請重寫上面的代碼段判斷輸入值為奇數則返回零值。

 

你有幾個更簡練的方法編寫代碼閉包,當閉包的類型是確定的,比如是一個回調或代理代碼,你可以忽略輸入參數的類型或返回的類型。單個語句的代碼閉包隱含地返回語句的值(不須return)。

numbers.map({ number in 3 * number })

 

你可以使用位置編號引用參數值而不是使用它的名字,這樣寫有利於我們編寫非常短的代碼閉包。 一個代碼閉包可以在函數圓括號后面傳給函數作為最后一個輸入參數。

sort([1, 5, 3, 12, 2]) { $0 > $1 }

 

 

對象和類:

 

類名

我們使用class語法定義類,類的屬性和定義和普通定義一個變量或常量一樣沒有區別,但屬性要定義在類的定義塊里面,類的方法和函數亦然。

 

class Shape {

    var numberOfSides = 0

    func simpleDescription() -> String {

        return "A shape with \(numberOfSides) sides."

    }

}

 

實驗:

在上面的類定義中使用let語法增加一個常量屬性,增加另外一個帶有一個參數的方法。

 

創建類的實例就是在類名后面加上一對圓括號。 使用點語法存儲這個類實例的屬性和程序方法。

var shape = Shape()

shape.numberOfSides = 7

var shapeDescription = shape.simpleDescription()

 

這個版本的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屬性和構造函數的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,使用半徑值和名字作為構造函數的參數。和上面的代碼類似,實現Circle類的area和describe方法。

 

類屬性除了存儲,我們還可以定義的getter和setter,即寫入的方法和讀取的方法。

class EquilateralTriangle: NamedShape {

    var sideLength: Double = 0.0

    

    init(sideLength: Double, name: String) {

        self.sideLength = sideLength

        super.init(name: name)

        numberOfSides = 3

    }

    

    var perimeter: Double {

    get {

        return 3.0 * sideLength

    }

    set {

        sideLength = newValue / 3.0

    }

 

   }

    

    override func simpleDescription() -> String {

        return "An equilateral triagle with sides of length \(sideLength)."

    }

}

var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")

triangle.perimeter

triangle.perimeter = 9.9

triangle.sideLength

 

在周長的setter設置方法上,用於更新的數值默認為newValue,你可以提供顯式的名字在set符號后面的圓括號中。 

請注意 EquilateralTriangle類的構造函數有以下三個步驟的內容:

1、初始化繼承后的各子類屬性。

2、調用父類的構造函數。

3、更新父類定義的屬性值、完成所有其他初始化工作,比如調用其他方法,其他getter和setter讀取與設置方法都可以在這里實現。

如果你不需要計算屬性的值,而是要在代碼運行之前或設置新數值之后執行其他代碼,那就要使用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")

triangleAndSquare.square.sideLength

triangleAndSquare.triangle.sideLength

triangleAndSquare.square = Square(sideLength: 50, name: "larger square")

triangleAndSquare.triangle.sideLength

 

類的方法有一個與函數不同的地方。函數的參數只能在函數內使用,而方法可以調用你本地的變量(除了方法的第一個參數名)。方法的參數名字在方法里面是不變的,你可以定義它的別名,在方法體內代替默認的參數名使用。

 

 

class Counter {

    var count: Int = 0

    func incrementBy(amount: Int, numberOfTimes times: Int) {

        count += amount * times

    }

}

var counter = Counter()

counter.incrementBy(2, numberOfTimes: 7)

 

當你需要使用可選值的時候,你也可將問號附加在方法,屬性名字之前。如果問號前的值是nil空值,所有的在問號之后的都被忽略,整個表達式都將置空(nil)。不為空值的話,該值將解包,所有的在問號之后的值將生效和附值。對於整個表達式來說,以上兩種情況使整個表達式的值變成可選值。

 

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

let sideLength = 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.toRaw())

        }

    }

}

let ace = Rank.Ace

let aceRawValue = ace.toRaw()

 

實驗:

寫一個通過對比枚舉的裸數值來對比它的序列值的函數。

 

在上文的示例程序中,枚舉的裸值類型是整數,所以你只要指定好第一個枚舉的裸值,其他剩余的數值將按序存放,當然你也可以使用字符串和浮點數值作為枚舉的裸值類型。

你可以使用toRaw和fromRaw函數轉換枚舉的裸值和它的序列值。

 if let convertedRank = Rank.fromRaw(3) {

    let threeDescription = convertedRank.simpleDescription()

}

 枚舉的成員值是一個可調用的實際值,不是裸數據的別名。除非裸數據沒有可讀性,你無需提供名字的轉換。

 

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

 

試驗:

添加一個color方法到suit枚舉中,對應spades和clubs返回”black”,對應hearts和diamonds返回”red”

 

注意這里的兩種hearts枚舉成員引用的方式: 當對hearts常量附值的時候,枚舉成員suit.hearts是使用枚舉全名,因為常量沒有顯式指定類型(所以無法判斷是否來自枚舉)。而在switch里面,枚舉使用簡寫的.Hearts就可以訪問主要是因數它的self已指向suit類型。所以你只要確定所屬的類型就可以對成員進行簡寫式的訪問。

使用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()

 

試驗:

Add a method to Card that creates a full deck of cards, with one card of each combination of rank and suit.

 

枚舉成員的實例可以有對應的數值,名字相同的枚舉成員可以有不同值。你可以在創建它們的時候再給予不同的值。枚舉的別名數值和裸值是不同的:裸值對所有的枚舉實例來說都是相同值,你可以在定義枚舉的時候就提供裸值。

例如,我們寫一個服務器程序提供獲取太陽的升起和日落的時間計算功能,程序返回計算結果或返回計算錯誤信息,程序如下

 

enum ServerResponse {

    case Result(String, String)

    case Error(String)

}

 

let success = ServerResponse.Result("6:00 am", "8:09 pm")

let failure = ServerResponse.Error("Out of cheese.")

 

switch success {

case let .Result(sunrise, sunset):

    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."

case let .Error(error):

    let serverResponse = "Failure...  \(error)"

}

 

試驗:

為ServerResponse和 switch添加第三個case。

 

請注意代碼中經過switch判斷,從ServerResponse返回的surise和sunset times的過程機制。

 

協議和擴展: 

我們使用protocol關鍵字定義協議

protocol ExampleProtocol {

    var simpleDescription: String { get }

    mutating func adjust()

類、枚舉和結構可以采用和聲明協議(protocols)。

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

 

 

試驗:

編寫一個遵守這個protocol的枚舉類型。

 

注意上文程序中使用mutating關鍵字在SimpleStructure結構中對方法進行修改(結構是靜態的)。而 SimpleClass類不需要使用 mutating關鍵字修飾 adjust 主要是因為類中就可以進行類的修改(類是動態的)。

使用extension對現有的類型添加新功能,比如新的方法或計算屬性。你可以使用extension對定義在其他地方的類型添加協議,甚至是你從其他的框架和庫導入的類。

 

extension Int: ExampleProtocol {

    var simpleDescription: String {

    return "The number \(self)"

    }

    mutating func adjust() {

        self += 42

    }

}

7.simpleDescription

 

試驗:

為Double類型添加了個absoluteValue屬性作為對該類型的擴展。

 

你可以和使用其他命名類型一樣使用協議,比如你創建一個不同類的對象實例集合,但又實現自相同的協議。

當你繼承協議,你可以存儲它的屬性數值,但直接調用它的方法將會報錯(協議方法需要在繼承中另外實現)。

 

let protocolValue: ExampleProtocol = a

protocolValue.simpleDescription

// protocolValue.anotherProperty  // Uncomment to see the error

雖然protocolValue是 ExampleProtocol的實例,而 ExampleProtocol有一個繼承類叫SimpleClass,但編譯把它當然是獨立的解釋ExampleProtocol的類型,並不是對協議的擴展,你不能在獨立實例化ExampleProtocol的ProtocolValue中調用它的方法和屬性。

 

泛型

在一對尖括號之間的命名代表泛化的類型或函數

func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] {

    var result = ItemType[]()

    for i in 0..times {

        result += item

    }

    return result

}

repeat("knock", 4)

 

你可以編寫泛化后的函數和方法,也可以編寫泛化類、泛化枚舉、結構。

 

// Reimplement the Swift standard library's optional type

enum OptionalValue<T> {

    case None

    case Some(T)

}

var possibleInteger: OptionalValue<Int> = .None

 

possibleInteger = .Some(100)

 

在類名后面使用where命令來指定類型必須滿足的條件,比如下面的代碼是要實現一個協議接口,需要滿足where規定的兩個類型約束條件。

 

func  anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool {

    for lhsItem in lhs {

        for rhsItem in rhs {

            if lhsItem == rhsItem {

                return true

            }

        }

    }

    return false

}

anyCommonElements([1, 2, 3], [3])

 

試驗:

修改anyCommonElements函數,實現把兩個序列的共同元素組成數組並返回。

在簡單的情況下,你可以忽略where命令簡單把protocol或類名寫在冒號后面。編寫 <T: Equatable>是和 <T where T: Equatable>.相等同的。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM