หน้านี้ได้รับการแปลโดย Cloud Translation API
Switch to English

ทัวร์อย่างรวดเร็ว

ดัดแปลงมาจาก A Swift Tour ดั้งเดิมบน Swift.org พร้อมการปรับเปลี่ยน เนื้อหาต้นฉบับเขียนโดย Apple Inc. ได้รับอนุญาตภายใต้ใบอนุญาต Creative Commons Attribution 4.0 International (CC BY 4.0)
ดูใน TensorFlow.org เรียกใช้ใน Google Colab ดูแหล่งที่มาบน GitHub

Tradition แนะนำว่าโปรแกรมแรกในภาษาใหม่ควรพิมพ์คำว่า "Hello, world!" บนหน้าจอ. ใน Swift สามารถทำได้ในบรรทัดเดียว:

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

หากคุณเขียนโค้ดใน C หรือ Objective-C ไวยากรณ์นี้จะดูคุ้นเคยสำหรับคุณใน Swift โค้ดบรรทัดนี้เป็นโปรแกรมที่สมบูรณ์ คุณไม่จำเป็นต้องนำเข้าไลบรารีแยกต่างหากสำหรับการทำงานเช่นอินพุต / เอาต์พุตหรือการจัดการสตริง โค้ดที่เขียนในขอบเขตส่วนกลางถูกใช้เป็นจุดเริ่มต้นของโปรแกรมดังนั้นคุณไม่จำเป็นต้องมีฟังก์ชัน main() คุณไม่จำเป็นต้องเขียนอัฒภาคท้ายทุกคำสั่ง

ทัวร์นี้ให้ข้อมูลเพียงพอแก่คุณในการเริ่มเขียนโค้ดใน Swift โดยแสดงวิธีการทำงานการเขียนโปรแกรมให้สำเร็จ ไม่ต้องกังวลหากคุณไม่เข้าใจบางสิ่งทุกอย่างที่แนะนำในทัวร์นี้มีรายละเอียดอธิบายไว้ในส่วนที่เหลือของหนังสือเล่มนี้

ค่าง่ายๆ

ใช้ let เพื่อสร้างค่าคงที่และ var เพื่อสร้างตัวแปร ไม่จำเป็นต้องทราบค่าของค่าคงที่ในเวลาคอมไพล์ แต่คุณต้องกำหนดค่าหนึ่งครั้ง ซึ่งหมายความว่าคุณสามารถใช้ค่าคงที่เพื่อตั้งชื่อค่าที่คุณกำหนดเพียงครั้งเดียว แต่ใช้ได้หลายที่

var myVariable = 42
myVariable = 50
let myConstant = 42

ค่าคงที่หรือตัวแปรต้องมีประเภทเดียวกันกับค่าที่คุณต้องการกำหนดให้ อย่างไรก็ตามคุณไม่จำเป็นต้องเขียนประเภทอย่างชัดเจนเสมอไป การระบุค่าเมื่อคุณสร้างค่าคงที่หรือตัวแปรช่วยให้คอมไพลเลอร์สามารถสรุปประเภทได้ ในตัวอย่างด้านบนคอมไพลเลอร์สรุปว่า myVariable เป็นจำนวนเต็มเนื่องจากค่าเริ่มต้นเป็นจำนวนเต็ม

หากค่าเริ่มต้นให้ข้อมูลไม่เพียงพอ (หรือไม่มีค่าเริ่มต้น) ให้ระบุประเภทโดยเขียนไว้หลังตัวแปรโดยคั่นด้วยเครื่องหมายทวิภาค หมายเหตุ: การใช้ Double แทน Float สำหรับตัวเลขทศนิยมจะช่วยให้คุณมีความแม่นยำมากขึ้นและเป็นประเภทจุดลอยตัวเริ่มต้นใน 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.

ค่าจะไม่ถูกแปลงเป็นประเภทอื่นโดยปริยาย หากคุณต้องการแปลงค่าเป็นประเภทอื่นให้สร้างอินสแตนซ์ของประเภทที่ต้องการอย่างชัดเจน

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?

มีวิธีที่ง่ายกว่านั้นในการรวมค่าในสตริง: เขียนค่าในวงเล็บและเขียนแบ็กสแลช (``) ก่อนวงเล็บ ตัวอย่างเช่น:

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.

ใช้เครื่องหมายคำพูดคู่สามตัว ( """ ) สำหรับสตริงที่ใช้หลายบรรทัดการเยื้องที่จุดเริ่มต้นของแต่ละบรรทัดที่ยกมาจะถูกลบออกตราบใดที่ตรงกับการเยื้องของเครื่องหมายคำพูดปิดตัวอย่างเช่น:

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.

สร้างอาร์เรย์และพจนานุกรมโดยใช้วงเล็บ ( [] ) และเข้าถึงองค์ประกอบโดยการเขียนดัชนีหรือคีย์ในวงเล็บ อนุญาตให้ใช้จุลภาคหลังองค์ประกอบสุดท้าย

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"

อาร์เรย์จะเติบโตโดยอัตโนมัติเมื่อคุณเพิ่มองค์ประกอบ

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

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

ในการสร้างอาร์เรย์หรือพจนานุกรมว่างให้ใช้ไวยากรณ์ตัวเริ่มต้น

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

หากอนุมานข้อมูลประเภทได้คุณสามารถเขียนอาร์เรย์ว่างเป็น [] และพจนานุกรมว่างเป็น [:] - ตัวอย่างเช่นเมื่อคุณตั้งค่าใหม่สำหรับตัวแปรหรือส่งผ่านอาร์กิวเมนต์ไปยังฟังก์ชัน

shoppingList = []
occupations = [:]

ควบคุมกระแส

ใช้ if และ switch เพื่อสร้างเงื่อนไขและใช้ for - in , for , 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
    }
}
teamScore
11

ในคำสั่ง if เงื่อนไขต้องเป็นนิพจน์บูลีนซึ่งหมายความว่าโค้ดเช่น if score { ... } เป็นข้อผิดพลาดไม่ใช่การเปรียบเทียบโดยปริยายกับศูนย์

คุณสามารถใช้ if and let together เพื่อทำงานกับค่าที่อาจขาดหายไป ค่าเหล่านี้แสดงเป็นตัวเลือก ค่าที่เป็นทางเลือกอาจมีค่าหรือมี nil เพื่อระบุว่าไม่มีค่า เขียนเครื่องหมายคำถาม ( ? ) หลังประเภทของค่าเพื่อทำเครื่องหมายค่าเป็นทางเลือก

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`.

หากค่าทางเลือกเป็น nil เงื่อนไขจะเป็น false และรหัสในวงเล็บปีกกาจะข้ามไป มิฉะนั้นค่าที่เป็นทางเลือกจะถูกคลายออกและกำหนดให้กับค่าคงที่หลังจาก let ซึ่งทำให้ค่าที่ไม่ได้ปิดกั้นมีอยู่ภายในบล็อกของโค้ด

อีกวิธีในการจัดการกับค่าที่เป็นทางเลือกคือการระบุค่าเริ่มต้นโดยใช้ ?? ตัวดำเนินการ หากไม่มีค่าทางเลือกระบบจะใช้ค่าเริ่มต้นแทน

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

สวิตช์รองรับข้อมูลทุกประเภทและการดำเนินการเปรียบเทียบที่หลากหลายไม่ จำกัด เฉพาะจำนวนเต็มและการทดสอบความเท่าเทียมกัน

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?

สังเกตว่าสามารถใช้ let ในรูปแบบเพื่อกำหนดค่าที่ตรงกับส่วนนั้นของรูปแบบเป็นค่าคงที่ได้อย่างไร

หลังจากรันโค้ดภายในเคสสวิตช์ที่ตรงกันโปรแกรมจะออกจากคำสั่ง 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 (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.

ใช้ while เพื่อทำบล็อกโค้ดซ้ำจนกว่าเงื่อนไขจะเปลี่ยนไป เงื่อนไขของลูปสามารถอยู่ที่ส่วนท้ายแทนเพื่อให้แน่ใจว่าลูปทำงานอย่างน้อยหนึ่งครั้ง

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

n
128

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

m
128

คุณสามารถเก็บดัชนีไว้ในลูป - โดยใช้ ..< เพื่อสร้างช่วงของดัชนีหรือโดยการเขียนการเริ่มต้นเงื่อนไขและการเพิ่มที่ชัดเจน ลูปทั้งสองนี้ทำสิ่งเดียวกัน:

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

total
6

ใช้ ..< เพื่อสร้างช่วงที่ละเว้นค่าบนและใช้ ... เพื่อสร้างช่วงที่มีทั้งสองค่า

ฟังก์ชั่นและการปิด

ใช้ func เพื่อประกาศฟังก์ชัน เรียกใช้ฟังก์ชันโดยตั้งชื่อตามด้วยรายการอาร์กิวเมนต์ในวงเล็บ ใช้ -> เพื่อแยกชื่อพารามิเตอร์และประเภทออกจากประเภทการส่งคืนของฟังก์ชัน

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.

โดยค่าเริ่มต้นฟังก์ชันจะใช้ชื่อพารามิเตอร์เป็นเลเบลสำหรับอาร์กิวเมนต์ เขียนป้ายชื่ออาร์กิวเมนต์ที่กำหนดเองก่อนชื่อพารามิเตอร์หรือเขียน _ เพื่อไม่ใช้ป้ายกำกับอาร์กิวเมนต์

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
"Hello John, today is 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)
120
120

ฟังก์ชันสามารถซ้อนกันได้ ฟังก์ชันที่ซ้อนกันสามารถเข้าถึงตัวแปรที่ประกาศไว้ในฟังก์ชันภายนอก คุณสามารถใช้ฟังก์ชันที่ซ้อนกันเพื่อจัดระเบียบโค้ดในฟังก์ชันที่ยาวหรือซับซ้อนได้

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

ฟังก์ชันเป็นประเภทชั้นหนึ่ง ซึ่งหมายความว่าฟังก์ชันสามารถคืนค่าฟังก์ชันอื่นเป็นค่าได้

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

ฟังก์ชันสามารถใช้ฟังก์ชันอื่นเป็นหนึ่งในอาร์กิวเมนต์ได้

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

ฟังก์ชั่นเป็นกรณีพิเศษของการปิด: บล็อกของโค้ดที่สามารถเรียกได้ในภายหลัง รหัสในการปิดสามารถเข้าถึงสิ่งต่างๆเช่นตัวแปรและฟังก์ชันที่มีอยู่ในขอบเขตที่สร้างการปิดแม้ว่าการปิดจะอยู่ในขอบเขตที่แตกต่างกันเมื่อดำเนินการคุณเห็นตัวอย่างของสิ่งนี้ที่มีฟังก์ชันซ้อนกันอยู่แล้ว คุณสามารถเขียนปิดโดยไม่มีชื่อโดยใช้รหัสล้อมรอบด้วยเครื่องหมายวงเล็บปีกกา ( {} ) ใช้ in เพื่อแยกอาร์กิวเมนต์และประเภทการส่งคืนออกจากเนื้อหา

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.

คุณมีหลายทางเลือกในการเขียนคำปิดให้รัดกุมยิ่งขึ้น เมื่อทราบประเภทของการปิดอยู่แล้วเช่นการเรียกกลับสำหรับผู้รับมอบสิทธิ์คุณสามารถละเว้นประเภทของพารามิเตอร์ประเภทการส่งคืนหรือทั้งสองอย่างได้ การปิดคำสั่งเดี่ยวจะส่งคืนค่าของคำสั่งเดียวโดยปริยาย

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

คุณสามารถอ้างถึงพารามิเตอร์ตามหมายเลขแทนการใช้ชื่อ - วิธีนี้มีประโยชน์อย่างยิ่งในการปิดที่สั้นมาก การปิดผ่านเป็นอาร์กิวเมนต์สุดท้ายของฟังก์ชันสามารถปรากฏขึ้นหลังวงเล็บ เมื่อการปิดเป็นอาร์กิวเมนต์เดียวของฟังก์ชันคุณสามารถละเว้นวงเล็บได้ทั้งหมด

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

วัตถุและคลาส

ใช้ class ตามด้วยชื่อคลาสเพื่อสร้างคลาส การประกาศคุณสมบัติในคลาสถูกเขียนในลักษณะเดียวกับการประกาศค่าคงที่หรือตัวแปรยกเว้นว่าจะอยู่ในบริบทของคลาส ในทำนองเดียวกันการประกาศวิธีการและฟังก์ชันจะเขียนในลักษณะเดียวกัน

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.

สร้างอินสแตนซ์ของคลาสโดยใส่วงเล็บหลังชื่อคลาส ใช้ไวยากรณ์ dot เพื่อเข้าถึงคุณสมบัติและวิธีการของอินสแตนซ์

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 ไปยัง initializer อย่างไร อาร์กิวเมนต์ไปยัง initializer จะถูกส่งผ่านเช่นการเรียกใช้ฟังก์ชันเมื่อคุณสร้างอินสแตนซ์ของคลาส ทุกคุณสมบัติต้องการค่าที่กำหนด - ในการประกาศ (เช่นเดียวกับ numberOfSides ) หรือในตัวเริ่มต้น (เช่นเดียวกับ name )

ใช้ deinit เพื่อสร้าง deinitializer หากคุณจำเป็นต้องทำการล้างข้อมูลก่อนที่วัตถุจะถูกจัดสรร

คลาสย่อยจะรวมชื่อคลาสพิเศษไว้หลังชื่อคลาสโดยคั่นด้วยเครื่องหมายทวิภาค ไม่มีข้อกำหนดสำหรับคลาสย่อยคลาสรูทมาตรฐานใด ๆ ดังนั้นคุณสามารถรวมหรือละเว้นซูเปอร์คลาสได้ตามต้องการ

วิธีการในคลาสย่อยที่แทนที่การนำไปใช้งานของ superclass ถูกทำเครื่องหมายด้วยการ 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()
"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.

นอกจากคุณสมบัติง่ายๆที่เก็บไว้แล้วคุณสมบัติยังมี 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 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

ใน setter สำหรับ perimeter ค่าใหม่มีชื่อโดยนัย newValue คุณสามารถระบุชื่อที่ชัดเจนในวงเล็บหลัง set ได้

สังเกตว่าตัวเริ่มต้นสำหรับคลาส EquilateralTriangle มีสามขั้นตอนที่แตกต่างกัน:

  1. การตั้งค่าคุณสมบัติที่คลาสย่อยประกาศ

  2. การเรียกใช้ initializer ของ superclass

  3. การเปลี่ยนค่าของคุณสมบัติที่กำหนดโดย superclass งานการตั้งค่าเพิ่มเติมใด ๆ ที่ใช้เมธอด getters หรือ setters สามารถทำได้ในจุดนี้

หากคุณไม่จำเป็นต้องคำนวณคุณสมบัติ แต่ยังคงต้องระบุโค้ดที่รันก่อนและหลังการตั้งค่าใหม่ให้ใช้ 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)
10.0
10.0
50.0

เมื่อทำงานกับค่าทางเลือกคุณสามารถเขียน ? ก่อนการดำเนินการเช่นวิธีการคุณสมบัติและการสมัครสมาชิก ถ้าค่าก่อนหน้า ? เป็น nil ทุกอย่างหลังจาก ? จะถูกละเว้นและค่าของนิพจน์ทั้งหมดเป็น nil มิฉะนั้นค่าที่เป็นทางเลือกจะถูกคลายออกและทุกอย่างหลังจาก ? ดำเนินการกับมูลค่าที่ยังไม่ได้ห่อ ในทั้งสองกรณีค่าของนิพจน์ทั้งหมดเป็นค่าทางเลือก

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

  - some : 2.5

การแจงนับและโครงสร้าง

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

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

ตามค่าเริ่มต้น Swift จะกำหนดค่าดิบโดยเริ่มต้นที่ศูนย์และเพิ่มทีละครั้งในแต่ละครั้ง แต่คุณสามารถเปลี่ยนลักษณะการทำงานนี้ได้โดยระบุค่าอย่างชัดเจน ในตัวอย่างด้านบน Ace ได้รับค่าดิบเป็น 1 อย่างชัดเจนและค่าดิบที่เหลือจะถูกกำหนดตามลำดับ คุณยังสามารถใช้สตริงหรือตัวเลขทศนิยมเป็นประเภทดิบของการแจงนับ ใช้คุณสมบัติ rawValue เพื่อเข้าถึงค่าดิบของกรณีการแจงนับ

ใช้ตัว init?(rawValue:) เพื่อสร้างอินสแตนซ์ของการแจงนับจากค่าดิบ จะส่งคืนกรณีการแจงนับที่ตรงกับค่าดิบหรือ nil หากไม่มี Rank ตรงกัน

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

สังเกตสองวิธีที่อ้างถึงกรณี Hearts ของการแจงนับ: เมื่อกำหนดค่าให้กับค่าคงที่ของ hearts กรณีการแจงนับ Suit.Hearts จะอ้างถึงโดยชื่อเต็มเนื่องจากค่าคงที่ไม่มีการระบุประเภทที่ชัดเจน ภายในสวิตช์กรณีการแจงนับถูกอ้างถึงโดยรูปแบบย่อ .Hearts เพราะคุณค่าของ 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)")
}
Sunrise is at 6:00 am and sunset is at 8:09 pm.

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

สังเกตว่าเวลาพระอาทิตย์ขึ้นและพระอาทิตย์ตกถูกดึงออกจากค่า ServerResponse เป็นส่วนหนึ่งของการจับคู่ค่ากับกรณีสวิตช์

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

โปรโตคอลและส่วนขยาย

ใช้ 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()
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?

สังเกตการใช้คีย์เวิร์ดที่ mutating ในการประกาศ SimpleStructure เพื่อทำเครื่องหมายวิธีการที่ปรับเปลี่ยนโครงสร้าง การประกาศ SimpleClass ไม่จำเป็นต้องมีเมธอดใด ๆ ที่ทำเครื่องหมายว่ากลายพันธุ์เนื่องจากเมธอดในคลาสสามารถแก้ไขคลาสได้เสมอ

ใช้ extension เพื่อเพิ่มฟังก์ชันการทำงานให้กับประเภทที่มีอยู่เช่นเมธอดใหม่และคุณสมบัติที่คำนวณ คุณสามารถใช้ส่วนขยายเพื่อเพิ่มความสอดคล้องของโปรโตคอลให้กับประเภทที่ประกาศไว้ที่อื่นหรือแม้แต่กับประเภทที่คุณนำเข้าจากไลบรารีหรือเฟรมเวิร์ก

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.

คุณสามารถใช้ชื่อโปรโตคอลเช่นเดียวกับประเภทชื่ออื่น ๆ ตัวอย่างเช่นเพื่อสร้างคอลเล็กชันของออบเจ็กต์ที่มีประเภทต่างกัน แต่ทั้งหมดเป็นไปตามโปรโตคอลเดียว เมื่อคุณทำงานกับค่าที่มี type เป็นประเภทโปรโตคอลจะไม่มีวิธีการภายนอกนิยามโปรโตคอล

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

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

แม้ว่าตัวแปร protocolValue จะมีประเภทรันไทม์เป็น SimpleClass แต่คอมไพลเลอร์จะถือว่าเป็นชนิดของ ExampleProtocol กำหนด ซึ่งหมายความว่าคุณไม่สามารถเข้าถึงเมธอดหรือคุณสมบัติที่คลาสนำไปใช้โดยไม่ได้ตั้งใจนอกเหนือจากความสอดคล้องของโปรโตคอล

การจัดการข้อผิดพลาด

คุณแสดงข้อผิดพลาดโดยใช้ประเภทใด ๆ ที่ใช้โปรโตคอล Error

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

ใช้ throw เพื่อโยนข้อผิดพลาดและ throws เพื่อทำเครื่องหมายฟังก์ชันที่สามารถส่งข้อผิดพลาดได้ หากคุณใส่ข้อผิดพลาดในฟังก์ชันฟังก์ชันจะส่งกลับทันทีและรหัสที่เรียกใช้ฟังก์ชันจะจัดการกับข้อผิดพลาด

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

มีหลายวิธีในการจัดการข้อผิดพลาด วิธีหนึ่งคือใช้ do-catch ภายในบล็อก do คุณทำเครื่องหมายรหัสที่สามารถทำให้เกิดข้อผิดพลาดได้โดยการเขียน try ไว้ข้างหน้า ภายในบล็อก catch ข้อผิดพลาดจะได้รับ error ชื่อโดยอัตโนมัติเว้นแต่คุณจะตั้งชื่ออื่น

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.

คุณสามารถจัดเตรียมบล็อกการ catch หลายรายการที่จัดการข้อผิดพลาดที่เฉพาะเจาะจง คุณเขียนรูปแบบหลังจาก catch เช่นเดียวกับที่คุณทำหลังจาก 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 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?

อีกวิธีในการจัดการข้อผิดพลาดคือ try? ใช้ try? เพื่อแปลงผลลัพธ์เป็นทางเลือก หากฟังก์ชันแสดงข้อผิดพลาดข้อผิดพลาดเฉพาะจะถูกละทิ้งและผลลัพธ์จะเป็น 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")
fridgeIsOpen
false

Generics

เขียนชื่อภายในวงเล็บเหลี่ยมเพื่อสร้างฟังก์ชันหรือประเภททั่วไป

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"

คุณสามารถสร้างรูปแบบทั่วไปของฟังก์ชันและวิธีการเช่นเดียวกับคลาสการแจงนับและโครงสร้าง

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

ใช้ where หลังชื่อประเภทเพื่อระบุรายการข้อกำหนด - ตัวอย่างเช่นต้องการให้ประเภทใช้โปรโตคอลเพื่อกำหนดให้สองประเภทเหมือนกันหรือกำหนดให้คลาสมีซูเปอร์คลาสเฉพาะ

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

การเขียน <T: Equatable> เหมือนกับการเขียน <T> ... where T: Equatable