Chuyến tham quan nhanh

Được chuyển thể từ A Swift Tour gốc trên Swift.org với một số sửa đổi. Nội dung gốc được viết bởi Apple Inc. Được cấp phép theo Giấy phép Creative Commons Ghi công 4.0 Quốc tế (CC BY 4.0) .
Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub

Truyền thống gợi ý rằng chương trình đầu tiên bằng ngôn ngữ mới nên in dòng chữ "Xin chào, thế giới!" trên màn hình. Trong Swift, điều này có thể được thực hiện trong một dòng duy nhất:

print("Hello, world!")
Hello, world!

Nếu bạn đã viết mã bằng C hoặc Objective-C, cú pháp này có vẻ quen thuộc với bạn—trong Swift, dòng mã này là một chương trình hoàn chỉnh. Bạn không cần phải nhập một thư viện riêng cho chức năng như xử lý đầu vào/đầu ra hoặc chuỗi. Mã được viết ở phạm vi toàn cục được sử dụng làm điểm vào của chương trình, do đó bạn không cần hàm main() . Bạn cũng không cần phải viết dấu chấm phẩy ở cuối mỗi câu lệnh.

Chuyến tham quan này cung cấp cho bạn đủ thông tin để bắt đầu viết mã trong Swift bằng cách chỉ cho bạn cách hoàn thành nhiều tác vụ lập trình khác nhau. Đừng lo lắng nếu bạn không hiểu điều gì đó—mọi điều được giới thiệu trong chuyến tham quan này sẽ được giải thích chi tiết trong phần còn lại của cuốn sách này.

Giá trị đơn giản

Sử dụng let để tạo một hằng số và var để tạo một biến. Giá trị của hằng số không cần phải biết tại thời điểm biên dịch, nhưng bạn phải gán cho nó một giá trị chính xác một lần. Điều này có nghĩa là bạn có thể sử dụng hằng số để đặt tên cho một giá trị mà bạn xác định một lần nhưng sử dụng ở nhiều nơi.

var myVariable = 42
myVariable = 50
let myConstant = 42

Hằng hoặc biến phải có cùng loại với giá trị bạn muốn gán cho nó. Tuy nhiên, không phải lúc nào bạn cũng phải viết kiểu rõ ràng. Việc cung cấp một giá trị khi bạn tạo một hằng hoặc một biến sẽ cho phép trình biên dịch suy ra kiểu của nó. Trong ví dụ trên, trình biên dịch suy ra rằng myVariable là một số nguyên vì giá trị ban đầu của nó là một số nguyên.

Nếu giá trị ban đầu không cung cấp đủ thông tin (hoặc nếu không có giá trị ban đầu), hãy chỉ định loại bằng cách viết nó sau biến, phân tách bằng dấu hai chấm. Lưu ý: sử dụng Double thay vì Float cho số dấu phẩy động mang lại cho bạn độ chính xác cao hơn và là loại dấu phẩy động mặc định trong Swift.

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
// Experiment:
// Create a constant with an explicit type of `Float` and a value of 4.

Các giá trị không bao giờ được chuyển đổi hoàn toàn sang loại khác. Nếu bạn cần chuyển đổi một giá trị sang một loại khác, hãy tạo một thể hiện của loại mong muốn một cách rõ ràng.

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

// Experiment:
// Try removing the conversion to `String` from the last line. What error do you get?

Có một cách thậm chí còn đơn giản hơn để đưa các giá trị vào chuỗi: Viết giá trị trong ngoặc đơn và viết dấu gạch chéo ngược (``) trước dấu ngoặc đơn. Ví dụ:

let apples = 3
print("I have \(apples) apples.")
I have 3 apples.

let oranges = 5
print("I have \(apples + oranges) pieces of fruit.")
I have 8 pieces of fruit.

// Experiment:
// Use `\()` to include a floating-point calculation in a string and to include someone's name in a
// greeting.

Sử dụng ba dấu ngoặc kép ( """ ) cho các chuỗi chiếm nhiều dòng. Thụt lề ở đầu mỗi dòng trích dẫn sẽ bị xóa, miễn là nó khớp với thụt lề của dấu ngoặc kép đóng. Ví dụ:

let quotation = """
    Even though there's whitespace to the left,
    the actual lines aren't indented.
        Except for this line.
    Double quotes (") can appear without being escaped.

    I still have \(apples + oranges) pieces of fruit.
    """
print(quotation)
Even though there's whitespace to the left,
the actual lines aren't indented.
    Except for this line.
Double quotes (") can appear without being escaped.

I still have 8 pieces of fruit.

Tạo mảng và từ điển bằng dấu ngoặc ( [] ) và truy cập các phần tử của chúng bằng cách viết chỉ mục hoặc khóa trong ngoặc. Dấu phẩy được cho phép sau phần tử cuối cùng.

var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
print(occupations)
["Jayne": "Public Relations", "Kaylee": "Mechanic", "Malcolm": "Captain"]

Mảng tự động phát triển khi bạn thêm các phần tử.

shoppingList.append("blue paint")
print(shoppingList)
["catfish", "bottle of water", "tulips", "blue paint", "blue paint"]

Để tạo một mảng hoặc từ điển trống, hãy sử dụng cú pháp khởi tạo.

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

Nếu thông tin loại có thể được suy ra, bạn có thể viết một mảng trống dưới dạng [] và một từ điển trống dưới dạng [:] — ví dụ: khi bạn đặt một giá trị mới cho một biến hoặc truyền một đối số cho một hàm.

shoppingList = []
occupations = [:]

Kiểm soát dòng chảy

Sử dụng ifswitch để tạo các điều kiện và sử dụng for - in , for , whilerepeat - while để tạo vòng lặp. Dấu ngoặc đơn xung quanh điều kiện hoặc biến vòng lặp là tùy chọn. Cần phải có nẹp quanh cơ thể.

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

Trong câu lệnh if , điều kiện phải là biểu thức Boolean—điều này có nghĩa là mã chẳng hạn như if score { ... } là lỗi chứ không phải là so sánh ngầm với 0.

Bạn có thể sử dụng iflet cùng nhau để xử lý các giá trị có thể bị thiếu. Các giá trị này được thể hiện dưới dạng tùy chọn. Giá trị tùy chọn chứa một giá trị hoặc chứa nil để biểu thị rằng một giá trị bị thiếu. Viết dấu chấm hỏi ( ? ) sau loại giá trị để đánh dấu giá trị đó là tùy chọn.

var optionalString: String? = "Hello"
print(optionalString == nil)
false

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
print(greeting)
Hello, John Appleseed

// Experiment:
// Change `optionalName` to `nil`. What greeting do you get?
// Add an `else` clause that sets a different greeting if `optionalName` is `nil`.

Nếu giá trị tùy chọn là nil thì điều kiện là false và mã trong dấu ngoặc nhọn sẽ bị bỏ qua. Mặt khác, giá trị tùy chọn sẽ được mở và gán cho hằng số sau let , điều này làm cho giá trị chưa được gói có sẵn bên trong khối mã.

Một cách khác để xử lý các giá trị tùy chọn là cung cấp giá trị mặc định bằng cách sử dụng ?? nhà điều hành. Nếu thiếu giá trị tùy chọn thì giá trị mặc định sẽ được sử dụng thay thế.

let nickName: String? = nil
let fullName: String = "John Appleseed"
print("Hi \(nickName ?? fullName)")
Hi John Appleseed

Bộ chuyển mạch hỗ trợ bất kỳ loại dữ liệu nào và nhiều hoạt động so sánh khác nhau—chúng không bị giới hạn ở số nguyên và phép kiểm tra sự bằng nhau.

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

// Experiment:
// Try removing the default case. What error do you get?

Lưu ý cách let có thể được sử dụng trong một mẫu để gán giá trị khớp với phần đó của mẫu cho một hằng số.

Sau khi thực thi mã bên trong trường hợp switch khớp, chương trình sẽ thoát khỏi câu lệnh switch. Việc thực thi không tiếp tục đến trường hợp tiếp theo, do đó không cần phải thoát ra khỏi switch một cách rõ ràng ở cuối mã của mỗi trường hợp.

Bạn sử dụng for - in để lặp lại các mục trong từ điển bằng cách cung cấp một cặp tên để sử dụng cho mỗi cặp khóa-giá trị. Từ điển là một bộ sưu tập không có thứ tự, vì vậy các khóa và giá trị của chúng được lặp lại theo một thứ tự tùy ý.

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 (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)
25

// Experiment:
// Add another variable to keep track of which kind of number was the largest, as well as what that
// largest number was.

Sử dụng while để lặp lại một khối mã cho đến khi điều kiện thay đổi. Thay vào đó, điều kiện của vòng lặp có thể ở cuối, đảm bảo rằng vòng lặp được chạy ít nhất một lần.

var n = 2
while n < 100 {
    n = n * 2
}

print(n)
128

var m = 2
repeat {
    m = m * 2
} while m < 100

print(m)
128

Bạn có thể giữ một chỉ mục trong một vòng lặp—bằng cách sử dụng ..< để tạo một phạm vi chỉ mục hoặc bằng cách viết một phần khởi tạo, điều kiện và mức tăng rõ ràng. Hai vòng lặp này làm điều tương tự:

var total = 0
for i in 0..<4 {
    total += i
}

print(total)
6

Sử dụng ..< để tạo một phạm vi bỏ qua giá trị trên của nó và sử dụng ... để tạo một phạm vi bao gồm cả hai giá trị.

Chức năng và đóng cửa

Sử dụng func để khai báo một hàm. Gọi một hàm bằng cách theo sau tên của nó kèm theo danh sách các đối số trong ngoặc đơn. Sử dụng -> để tách tên và kiểu tham số khỏi kiểu trả về của hàm.

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

// Experiment:
// Remove the `day` parameter. Add a parameter to include today’s lunch special in the greeting.

Theo mặc định, các hàm sử dụng tên tham số làm nhãn cho các đối số của chúng. Viết nhãn đối số tùy chỉnh trước tên tham số hoặc viết _ để không sử dụng nhãn đối số.

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

Sử dụng bộ dữ liệu để tạo một giá trị phức hợp—ví dụ: để trả về nhiều giá trị từ một hàm. Các phần tử của một bộ có thể được gọi bằng tên hoặc bằng số.

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

Các hàm có thể được lồng nhau. Các hàm lồng nhau có quyền truy cập vào các biến được khai báo ở hàm ngoài. Bạn có thể sử dụng các hàm lồng nhau để sắp xếp mã trong một hàm dài hoặc phức tạp.

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

Chức năng là loại hạng nhất. Điều này có nghĩa là một hàm có thể trả về một hàm khác làm giá trị của nó.

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

Một hàm có thể lấy một hàm khác làm đối số của nó.

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]
print(hasAnyMatches(list: numbers, condition: lessThanTen))
true

Các hàm thực chất là một trường hợp đặc biệt của các bao đóng: các khối mã có thể được gọi sau. Mã trong một bao đóng có quyền truy cập vào những thứ như biến và hàm có sẵn trong phạm vi nơi bao đóng được tạo, ngay cả khi bao đóng đó ở một phạm vi khác khi nó được thực thi—bạn đã thấy một ví dụ về điều này với các hàm lồng nhau. Bạn có thể viết một bao đóng mà không cần tên bằng cách bao quanh mã bằng dấu ngoặc nhọn ( {} ). Sử dụng in để tách các đối số và kiểu trả về khỏi phần nội dung.

let mappedNumbers = numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})
print(mappedNumbers)
[60, 57, 21, 36]

// Experiment:
// Rewrite the closure to return zero for all odd numbers.

Bạn có một số tùy chọn để viết các bao đóng chính xác hơn. Khi đã biết loại của một bao đóng, chẳng hạn như lệnh gọi lại cho một đại biểu, bạn có thể bỏ qua loại tham số, loại trả về của nó hoặc cả hai. Việc đóng câu lệnh đơn sẽ ngầm trả về giá trị của câu lệnh duy nhất của chúng.

let mappedNumbers2 = numbers.map({ number in 3 * number })
print(mappedNumbers2)
[60, 57, 21, 36]

Bạn có thể tham chiếu các tham số theo số thay vì theo tên—cách tiếp cận này đặc biệt hữu ích trong các phần đóng rất ngắn. Một bao đóng được truyền dưới dạng đối số cuối cùng của hàm có thể xuất hiện ngay sau dấu ngoặc đơn. Khi bao đóng là đối số duy nhất của hàm, bạn có thể bỏ qua hoàn toàn dấu ngoặc đơn.

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
[20, 19, 12, 7]

Đối tượng và lớp học

Sử dụng class theo sau là tên lớp để tạo lớp. Khai báo thuộc tính trong một lớp được viết giống như khai báo hằng hoặc biến, ngoại trừ việc nó nằm trong ngữ cảnh của một lớp. Tương tự như vậy, các khai báo phương thức và hàm được viết theo cùng một cách.

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}
// Experiment:
// Add a constant property with `let`, and add another method that takes an argument.

Tạo một thể hiện của một lớp bằng cách đặt dấu ngoặc đơn sau tên lớp. Sử dụng cú pháp dấu chấm để truy cập các thuộc tính và phương thức của phiên bản.

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

Phiên bản này của lớp Shape thiếu một thứ quan trọng: trình khởi tạo để thiết lập lớp khi một phiên bản được tạo. Sử dụng init để tạo một cái.

class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
        self.name = name
    }

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

Lưu ý cách self được sử dụng để phân biệt thuộc tính name với đối số name với bộ khởi tạo. Các đối số cho trình khởi tạo được truyền giống như lệnh gọi hàm khi bạn tạo một phiên bản của lớp. Mọi thuộc tính đều cần một giá trị được gán—trong phần khai báo của nó (như với numberOfSides ) hoặc trong bộ khởi tạo (như với name ).

Sử dụng deinit để tạo bộ khử khởi tạo nếu bạn cần thực hiện một số thao tác dọn dẹp trước khi đối tượng bị hủy phân bổ.

Các lớp con bao gồm tên siêu lớp sau tên lớp, cách nhau bằng dấu hai chấm. Không có yêu cầu các lớp phải phân lớp con bất kỳ lớp gốc tiêu chuẩn nào, vì vậy bạn có thể thêm hoặc bỏ qua lớp cha nếu cần.

Các phương thức trên một lớp con ghi đè việc triển khai của lớp cha được đánh dấu bằng override — việc ghi đè một phương thức một cách tình cờ mà không override , bị trình biên dịch phát hiện là một lỗi. Trình biên dịch cũng phát hiện các phương thức override mà không thực sự ghi đè bất kỳ phương thức nào trong siêu lớp.

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")
print(test.area())
print(test.simpleDescription())
27.040000000000003
A square with sides of length 5.2.

// Experiment:
// - Make another subclass of `NamedShape` called `Circle` that takes a radius and a name as
//   arguments to its initializer.
// - Implement an `area()` and a `simpleDescription()` method on the `Circle` class.

Ngoài các thuộc tính đơn giản được lưu trữ, các thuộc tính có thể có getter và 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 triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
9.3
3.3000000000000003

Trong bộ thiết lập perimeter , giá trị mới có tên ngầm định newValue . Bạn có thể cung cấp tên rõ ràng trong ngoặc đơn sau set .

Lưu ý rằng trình khởi tạo cho lớp EquilateralTriangle có ba bước khác nhau:

  1. Đặt giá trị của các thuộc tính mà lớp con khai báo.

  2. Gọi trình khởi tạo của siêu lớp.

  3. Thay đổi giá trị của các thuộc tính được xác định bởi siêu lớp. Bất kỳ công việc thiết lập bổ sung nào sử dụng các phương thức, getters hoặc setters cũng có thể được thực hiện tại thời điểm này.

Nếu bạn không cần tính toán thuộc tính nhưng vẫn cần cung cấp mã chạy trước và sau khi đặt giá trị mới, hãy sử dụng willSetdidSet . Mã bạn cung cấp sẽ được chạy bất cứ khi nào giá trị thay đổi bên ngoài bộ khởi tạo. Ví dụ, lớp bên dưới đảm bảo rằng độ dài cạnh của tam giác luôn bằng độ dài cạnh của hình vuông.

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)
10.0
10.0
50.0

Khi làm việc với các giá trị tùy chọn, bạn có thể viết ? trước các hoạt động như phương thức, thuộc tính và đăng ký. Nếu giá trị trước ?nil , mọi thứ sau ? bị bỏ qua và giá trị của toàn bộ biểu thức là nil . Nếu không, giá trị tùy chọn sẽ được mở ra và mọi thứ sau ? hành động trên giá trị chưa được bao bọc. Trong cả hai trường hợp, giá trị của toàn bộ biểu thức là giá trị tùy chọn.

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

Bảng liệt kê và cấu trúc

Sử dụng enum để tạo một bảng liệt kê. Giống như các lớp và tất cả các kiểu được đặt tên khác, bảng liệt kê có thể có các phương thức liên quan đến chúng.

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
print(ace)
let aceRawValue = ace.rawValue
print(aceRawValue)
ace
1

// Experiment:
// Write a function that compares two `Rank` values by comparing their raw values.

Theo mặc định, Swift chỉ định các giá trị thô bắt đầu từ 0 và tăng dần từng giá trị một, nhưng bạn có thể thay đổi hành vi này bằng cách chỉ định rõ ràng các giá trị. Trong ví dụ trên, Ace được cấp rõ ràng giá trị thô là 1 và các giá trị thô còn lại được gán theo thứ tự. Bạn cũng có thể sử dụng chuỗi hoặc số dấu phẩy động làm kiểu liệt kê thô. Sử dụng thuộc tính rawValue để truy cập giá trị thô của trường hợp liệt kê.

Sử dụng trình khởi tạo init?(rawValue:) để tạo một thể hiện của bảng liệt kê từ một giá trị thô. Nó trả về trường hợp liệt kê khớp với giá trị thô hoặc nil nếu không có Rank phù hợp.

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

Các giá trị kiểu của một bảng liệt kê là các giá trị thực tế, không chỉ là một cách viết giá trị thô khác của chúng. Trên thực tế, trong trường hợp không có giá trị thô có ý nghĩa, bạn không cần phải cung cấp giá trị đó.

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()
// Experiment:
// Add a `color()` method to `Suit` that returns "black" for spades and clubs, and returns "red" for
// hearts and diamonds.

Lưu ý hai cách mà trường hợp liệt kê Hearts được đề cập ở trên: Khi gán giá trị cho hằng số hearts , trường hợp liệt kê Suit.Hearts được tham chiếu bằng tên đầy đủ vì hằng số không có loại rõ ràng được chỉ định. Bên trong switch, trường hợp liệt kê được gọi bằng dạng viết tắt .Hearts vì giá trị của self đã được biết là một suit. Bạn có thể sử dụng biểu mẫu viết tắt bất cứ lúc nào loại giá trị đã được biết.

Nếu một bảng liệt kê có các giá trị thô thì các giá trị đó được xác định như một phần của khai báo, nghĩa là mọi phiên bản của một trường hợp liệt kê cụ thể luôn có cùng một giá trị thô. Một lựa chọn khác cho trường hợp liệt kê là có các giá trị liên quan đến trường hợp—các giá trị này được xác định khi bạn tạo phiên bản và chúng có thể khác nhau đối với từng phiên bản của trường hợp liệt kê. Bạn có thể coi các giá trị liên quan hoạt động giống như các thuộc tính được lưu trữ của phiên bản trường hợp liệt kê.

Ví dụ: hãy xem xét trường hợp yêu cầu thời gian mặt trời mọc và mặt trời lặn từ máy chủ. Máy chủ sẽ phản hồi với thông tin được yêu cầu hoặc phản hồi bằng mô tả về sự cố.

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)")
}
Sunrise is at 6:00 am and sunset is at 8:09 pm.

// Experiment:
// Add a third case to `ServerResponse` and to the switch.

Lưu ý cách trích xuất thời gian mặt trời mọc và mặt trời lặn từ giá trị ServerResponse như một phần của việc khớp giá trị với các trường hợp chuyển đổi.

Sử dụng struct để tạo cấu trúc. Cấu trúc hỗ trợ nhiều hành vi giống như các lớp, bao gồm các phương thức và bộ khởi tạo. Một trong những khác biệt quan trọng nhất giữa cấu trúc và lớp là cấu trúc luôn được sao chép khi chúng được truyền trong mã của bạn, nhưng các lớp được truyền bằng tham chiếu.

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()
// Experiment:
// Write a function that returns an array containing a full deck of cards, with one card of each
// combination of rank and suit.

Giao thức và tiện ích mở rộng

Sử dụng protocol để khai báo một giao thức.

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

Các lớp, bảng liệt kê và cấu trúc đều có thể áp dụng các giao thức.

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()
print(b.adjust())
print(b.simpleDescription)
()
A simple structure (adjusted)

// Experiment:
// Add another requirement to `ExampleProtocol`.
// What changes do you need to make to `SimpleClass` and `SimpleStructure` so that they still
// conform to the protocol?

Lưu ý việc sử dụng từ khóa mutating trong khai báo SimpleStructure để đánh dấu một phương thức sửa đổi cấu trúc. Việc khai báo SimpleClass không cần bất kỳ phương thức nào của nó được đánh dấu là đột biến vì các phương thức trên một lớp luôn có thể sửa đổi lớp đó.

Sử dụng tiện ích extension để thêm chức năng cho loại hiện có, chẳng hạn như phương pháp mới và thuộc tính được tính toán. Bạn có thể sử dụng tiện ích mở rộng để thêm tính tuân thủ giao thức vào một loại được khai báo ở nơi khác hoặc thậm chí vào loại mà bạn đã nhập từ thư viện hoặc khung.

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

// Experiment:
// Write an extension for the `Double` type that adds an `absoluteValue` property.

Bạn có thể sử dụng tên giao thức giống như bất kỳ loại được đặt tên nào khác—ví dụ: để tạo một tập hợp các đối tượng có các loại khác nhau nhưng tất cả đều tuân theo một giao thức duy nhất. Khi bạn làm việc với các giá trị có loại là loại giao thức, các phương thức bên ngoài định nghĩa giao thức sẽ không khả dụng.

let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
A very simple class.  Now 100% adjusted.

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

Mặc dù biến protocolValue có kiểu thời gian chạy là SimpleClass , nhưng trình biên dịch sẽ xử lý nó như kiểu đã cho của ExampleProtocol . Điều này có nghĩa là bạn không thể vô tình truy cập vào các phương thức hoặc thuộc tính mà lớp đó triển khai ngoài việc tuân thủ giao thức của nó.

Xử lý lỗi

Bạn biểu thị lỗi bằng cách sử dụng bất kỳ loại nào sử dụng giao thức Error .

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

Sử dụng throw để ném lỗi và throws để đánh dấu một hàm có thể đưa ra lỗi. Nếu bạn đưa ra lỗi trong một hàm, hàm đó sẽ trả về ngay lập tức và mã gọi hàm đó sẽ xử lý lỗi đó.

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

Có một số cách để xử lý lỗi. Một cách là sử dụng do-catch . Bên trong khối do , bạn đánh dấu mã có thể gây ra lỗi bằng cách viết try phía trước nó. Bên trong khối catch , lỗi sẽ tự động được đặt tên error trừ khi bạn đặt cho nó một tên khác.

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

// Experiment:
// Change the printer name to `"Never Has Toner"`, so that the `send(job:toPrinter:)` function
// throws an error.

Bạn có thể cung cấp nhiều khối catch để xử lý các lỗi cụ thể. Bạn viết một mẫu sau khi catch giống như bạn làm sau case trong một switch.

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 printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
Job sent

// Experiment:
// Add code to throw an error inside the `do` block.
// What kind of error do you need to throw so that the error is handled by the first `catch` block?
// What about the second and third blocks?

Một cách khác để xử lý lỗi là sử dụng try? để chuyển đổi kết quả thành một tùy chọn. Nếu hàm đưa ra lỗi, lỗi cụ thể sẽ bị loại bỏ và kết quả là nil . Ngược lại, kết quả là tùy chọn chứa giá trị mà hàm trả về.

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

Sử dụng defer để viết một khối mã được thực thi sau tất cả các mã khác trong hàm, ngay trước khi hàm trả về. Mã được thực thi bất kể hàm có đưa ra lỗi hay không. Bạn có thể sử dụng defer để viết mã thiết lập và mã dọn dẹp cạnh nhau, mặc dù chúng cần được thực thi vào những thời điểm khác nhau.

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
}
print(fridgeContains("banana"))
print(fridgeIsOpen)
false
false

Thuốc gốc

Viết tên bên trong dấu ngoặc nhọn để tạo thành một hàm hoặc kiểu chung.

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

Bạn có thể tạo các dạng chung của hàm và phương thức, cũng như các lớp, kiểu liệt kê và cấu trúc.

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

Sử dụng where sau tên loại để chỉ định danh sách các yêu cầu - ví dụ: để yêu cầu loại thực hiện một giao thức, yêu cầu hai loại giống nhau hoặc yêu cầu một lớp phải có một siêu lớp cụ thể.

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

Viết <T: Equatable> cũng giống như viết <T> ... where T: Equatable .