Trang này được dịch bởi Cloud Translation API.
Switch to English

Chuyến tham quan nhanh

Phỏng theo A Swift Tour gốc trên Swift.org có sửa đổi. Nội dung gốc được tác giả bởi Apple Inc. Được cấp phép theo Giấy phép Creative Commons Attribution 4.0 International (CC BY 4.0) .
Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub

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

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

Nếu bạn đã viết mã bằng C hoặc Objective-C, thì 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 các chức năng như nhập / xuất hoặc xử lý chuỗi. Mã được viết ở phạm vi toàn cục được sử dụng làm điểm đầu vào cho chương trình, vì vậy 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ã bằng Swift bằng cách chỉ cho bạn cách hoàn thành nhiều tác vụ lập trình. Đừng lo lắng nếu bạn không hiểu điều gì đó — mọi thứ được giới thiệu trong chuyến tham quan này đều đượ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 một hằng số không cần biết tại thời điểm biên dịch, nhưng bạn phải gán giá trị cho nó 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

Một hằng số hoặc biến phải có cùng kiểu 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 số hoặc một biến 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 cho 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 kiểu bằng cách viết nó sau biến, được 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à kiểu 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.

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

let label = "The width is "
let width = 94
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 đơn giản hơn nữa để bao gồm các giá trị trong chuỗi: Viết giá trị trong dấu 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
"I have \(apples) apples."
"I have 3 apples."

let oranges = 5
"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 được 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 cách sử dụ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 dấu ngoặc. Dấu phẩy được 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"
occupations
▿ 3 elements
  ▿ 0 : 2 elements

    - key : "Malcolm"
    - value : "Captain"
  ▿ 1 : 2 elements
    - key : "Kaylee"
    - value : "Mechanic"
  ▿ 2 : 2 elements
    - key : "Jayne"
    - value : "Public Relations"

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

shoppingList.append("blue paint")
shoppingList
▿ 5 elements

  - 0 : "catfish"
  - 1 : "bottle of water"
  - 2 : "tulips"
  - 3 : "blue paint"
  - 4 : "blue paint"

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

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

Nếu thông tin kiểu có thể được suy ra, bạn có thể viết một mảng trống là [] và một từ điển trống là [:] - 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 điều kiện và sử dụng for - in , for , whilerepeat - while để tạo các 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. Niềng răng xung quanh cơ thể là bắt buộc.

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

Trong một if tuyên bố, có điều kiện phải là một biểu hiện này Boolean phương tiện mã như if score { ... } là một lỗi, không phải là một sự so sánh ngầm không.

Bạn có thể sử dụng iflet cùng nhau để làm việc với các giá trị có thể bị thiếu. Các giá trị này được biểu diễn dưới dạng tùy chọn. Một giá trị tùy chọn hoặc chứa một giá trị hoặc chứa nil để chỉ ra 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"
optionalString == nil
false

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
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 bị bỏ qua. Nếu không, giá trị tùy chọn sẽ không được bao bọc và được gán cho hằng số sau let , điều này làm cho giá trị không được bao bọc 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 dấu ?? nhà điều hành. Nếu thiếu giá trị tùy chọn, giá trị mặc định sẽ được sử dụng thay thế.

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

Thiết 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 giới hạn ở các số nguyên và kiểm tra tính bình đẳng.

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 hiện mã bên trong trường hợp chuyển đổi phù hợp, chương trình thoát khỏi câu lệnh switch. Việc thực thi không tiếp tục cho trường hợp tiếp theo, vì vậy không cần phải thoát ra khỏi công tắc 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
        }
    }
}
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 một điều kiện thay đổi. Thay vào đó, điều kiện của một 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
}

n
128

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

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 khởi tạo, điều kiện và số gia rõ ràng. Hai vòng lặp này làm cùng một việc:

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

total
6

Sử dụng ..< để tạo một dải ô bỏ qua giá trị trên của nó và sử dụng ... để tạo một dải ô 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ó với danh sách các đối số trong dấu 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)."
}
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ố của chúng 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 thông 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)."
}
greet("John", on: "Wednesday")
"Hello John, today is Wednesday."

Sử dụng một bộ giá trị để tạo giá trị ghé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 theo 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 vào nhau. Các hàm lồng nhau có quyền truy cập vào các biến đã được khai báo trong hàm bên ngoài. Bạn có thể sử dụng các hàm lồng nhau để tổ chức 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
}
returnFifteen()
15

Các hàm là một 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()
increment(7)
8

Một hàm có thể nhận 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]
hasAnyMatches(list: numbers, condition: lessThanTen)
true

Các hàm thực sự 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 này. Mã trong một bao đóng có quyền truy cập vào những thứ như các biến và hàm có sẵn trong phạm vi mà 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 không có tên bằng mã xung quanh có 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 thân.

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

  - 0 : 60
  - 1 : 57
  - 2 : 21
  - 3 : 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 đóng góp ngắn gọn hơn. Khi đã biết kiểu của 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 kiểu tham số, kiểu trả về của nó hoặc cả hai. Các lệnh đơn đóng lại ngầm định trả về giá trị của câu lệnh duy nhất của chúng.

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

Bạn có thể tham khảo 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 trường hợp đóng rất ngắn. Một bao đóng được truyền làm đối số cuối cùng cho một hàm có thể xuất hiện ngay sau dấu ngoặc đơn. Khi một bao đóng là đối số duy nhất của một 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

Sử dụng class theo sau là tên của lớp để tạo một lớp. Một khai báo thuộc tính trong một lớp được viết giống như một 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 cá thể.

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: một trình khởi tạo để thiết lập lớp khi một thể hiện được tạo. Sử dụng init để tạo một.

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 từ đối số name với trình khởi tạo. Các đối số cho bộ khởi tạo được chuyển giống như một lời gọi hàm khi bạn tạo một thể hiện của lớp. Mọi thuộc tính 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 deinitializer 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 được phân bổ.

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

Các phương thức trên lớp con ghi đè việc triển khai của lớp cha được đánh dấu bằng override một phương thức một cách tình cờ, không override , được 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 có override không thực sự ghi đè bất kỳ phương thức nào trong lớp cha.

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()
"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ó một getter và một 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 setter cho perimeter , giá trị mới có tên ngầm định là newValue . Bạn có thể cung cấp một tên rõ ràng trong ngoặc đơn sau khi 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 lớp cha.

  3. Thay đổi giá trị của các thuộc tính được xác định bởi lớp cha. Bất kỳ công việc thiết lập bổ sung nào sử dụng các method, 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 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 được chạy bất kỳ lúc nào giá trị thay đổi bên ngoài trình khởi tạo. Ví dụ, lớp dưới đây đả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à chỉ số con. Nếu giá trị trước dấu ?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ẽ không được bao bọc và mọi thứ sau dấu ? 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à một giá trị tùy chọn.

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
optionalSquare?.sideLength
▿ Optional<Double>

  - some : 2.5

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, kiểu liệt kê có thể có các phương thức liên kết với 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 gán các giá trị thô bắt đầu từ 0 và tăng dần mỗi lần, 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 đưa ra một cách rõ ràng giá trị thô của 1 , và phần còn lại của các giá trị nguyên được giao 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 nguyên của một kiểu liệt kê. Sử dụng thuộc tính rawValue để truy cập giá trị thô của một trường hợp liệt kê.

Sử dụng bộ khởi tạo init?(rawValue:) để tạo một phiên bản liệt kê từ một giá trị thô. Nó trả về trường hợp liệt kê phù hợ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ị trường hợp của một kiểu liệt kê là các giá trị thực, không chỉ là một cách khác để viết các giá trị thô của chúng. Trên thực tế, trong những 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 Hearts của liệt kê đượ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 gọi bằng tên đầy đủ của nó vì hằng số không có kiểu rõ ràng được chỉ định. Bên trong công tắc, 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 đến là một bộ đồ. Bạn có thể sử dụng dạng viết tắt bất cứ lúc nào kiểu của giá trị đã được biết.

Nếu một kiểu liệt kê có các giá trị thô, các giá trị đó được xác định như một phần của khai báo, có nghĩa là mọi trường hợp 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 các trường hợp liệt kê là có các giá trị được liên kết với trường hợp — các giá trị này được xác định khi bạn tạo trường hợp, và chúng có thể khác nhau đối với mỗi trường hợp của một trường hợp liệt kê. Bạn có thể coi các giá trị được liên kết hoạt động giống như các thuộc tính được lưu trữ của 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à lặn từ một máy chủ. Máy chủ phản hồi với thông tin được yêu cầu hoặc nó phản hồi với mô tả về những gì đã xảy ra.

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 thời gian mặt trời mọc và mặt trời lặn được trích xuất từ ​​giá trị ServerResponse như một phần của việc đối sánh giá trị với các trường hợp chuyển đổi.

Sử dụng struct để tạo cấu trúc. Cá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à trình khởi tạo. Một trong những điểm khác biệt quan trọng nhất giữa cấu trúc và lớp là các cấu trúc luôn được sao chép khi chúng được truyền xung quanh trong mã của bạn, nhưng các lớp được chuyể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()
}

Tất cả 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()
b.adjust()
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à thay đổi 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 extension để thêm chức năng cho một loại hiện có, chẳng hạn như các 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 sự tuân thủ giao thức vào một kiểu được khai báo ở nơi khác, hoặc thậm chí cho kiểu mà bạn đã nhập từ thư viện hoặc khuôn khổ.

extension Int: ExampleProtocol {
    public var simpleDescription: String {
        return "The number \(self)"
    }
    public mutating func adjust() {
        self += 42
    }
}
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ỳ kiểu đượ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 kiểu 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ó kiểu là kiểu 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
protocolValue.simpleDescription
"A very simple class.  Now 100% adjusted."

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

Mặc dù protocolValue có thể SimpleClass kiểu thời gian chạy là SimpleClass , trình biên dịch coi nó như là kiểu 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 thực hiện ngoài sự tuân thủ giao thức của nó.

Xử lý lỗi

Bạn trình bày lỗi bằng bất kỳ loại nào thông qua 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 chức năng có thể ném lỗi. Nếu bạn gặp lỗi trong một hàm, hàm sẽ trả về ngay lập tức và mã được gọi là 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 do khối, bạn đánh dấu mã có thể ném ra một lỗi bằng cách viết thử ở 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ô hình sau khi catch cũng giống như bạn làm sau khi 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 tùy chọn. Nếu hàm ném ra một lỗi, lỗi cụ thể sẽ bị loại bỏ và kết quả là nil . Nếu không, kết quả là một 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ó phát 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 bên 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
}
fridgeContains("banana")
fridgeIsOpen
false

Generics

Viết tên bên trong dấu ngoặc nhọn để tạo 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
}
makeArray(repeating: "knock", numberOfTimes: 4)
▿ 4 elements

  - 0 : "knock"
  - 1 : "knock"
  - 2 : "knock"
  - 3 : "knock"

Bạn có thể tạo các dạng hàm và phương thức chung, 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 kiểu để chỉ định danh sách các yêu cầu - ví dụ: để yêu cầu kiểu thực hiện một giao thức, yêu cầu hai kiểu giống nhau hoặc yêu cầu một lớp phải có một lớp cha 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
}
anyCommonElements([1, 2, 3], [3])
true

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