ทัวร์ที่รวดเร็ว

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

ประเพณีแนะนำว่าโปรแกรมแรกในภาษาใหม่ควรพิมพ์คำว่า "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
print(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
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.

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

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"
print(occupations)
["Jayne": "Public Relations", "Kaylee": "Mechanic", "Malcolm": "Captain"]

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

shoppingList.append("blue paint")
print(shoppingList)
["catfish", "bottle of water", "tulips", "blue paint", "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
    }
}
print(teamScore)
11

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

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

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

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

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

let nickName: String? = nil
let fullName: String = "John Appleseed"
print("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 case ที่ตรงกันแล้ว โปรแกรมจะออกจากคำสั่ง 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
        }
    }
}
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.

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

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

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

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

print(total)
6

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

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

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

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.

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

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

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

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
print(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]
print(hasAnyMatches(list: numbers, condition: lessThanTen))
true

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

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.

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

let mappedNumbers2 = numbers.map({ number in 3 * number })
print(mappedNumbers2)
[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.

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

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 เพื่อสร้าง deinitializer หากคุณต้องการทำการล้างข้อมูลก่อนที่วัตถุจะถูกจัดสรรคืน

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

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

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

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

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

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

  2. การเรียกตัวเริ่มต้นของซูเปอร์คลาส

  3. การเปลี่ยนค่าของคุณสมบัติที่กำหนดโดยซูเปอร์คลาส งานการตั้งค่าเพิ่มเติมใดๆ ที่ใช้วิธีการ 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")
print(optionalSquare?.sideLength)
Optional(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()
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?

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

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

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.

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

let protocolValue: ExampleProtocol = a
print(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? เพื่อแปลงผลลัพธ์เป็นทางเลือก หากฟังก์ชันเกิดข้อผิดพลาด ข้อผิดพลาดเฉพาะจะถูกยกเลิก และผลลัพธ์จะเป็น 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
}
print(fridgeContains("banana"))
print(fridgeIsOpen)
false
false

ยาสามัญ

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

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"]

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

// 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
}
print(anyCommonElements([1, 2, 3], [3]))
true

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

,

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

ประเพณีแนะนำว่าโปรแกรมแรกในภาษาใหม่ควรพิมพ์คำว่า "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
print(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
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.

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

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"
print(occupations)
["Jayne": "Public Relations", "Kaylee": "Mechanic", "Malcolm": "Captain"]

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

shoppingList.append("blue paint")
print(shoppingList)
["catfish", "bottle of water", "tulips", "blue paint", "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
    }
}
print(teamScore)
11

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

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

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

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

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

let nickName: String? = nil
let fullName: String = "John Appleseed"
print("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 case ที่ตรงกันแล้ว โปรแกรมจะออกจากคำสั่ง 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
        }
    }
}
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.

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

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

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

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

print(total)
6

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

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

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

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.

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

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

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

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
print(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]
print(hasAnyMatches(list: numbers, condition: lessThanTen))
true

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

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.

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

let mappedNumbers2 = numbers.map({ number in 3 * number })
print(mappedNumbers2)
[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.

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

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 เพื่อสร้าง deinitializer หากคุณต้องการทำการล้างข้อมูลก่อนที่วัตถุจะถูกจัดสรรคืน

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

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

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

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

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

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

  2. การเรียกตัวเริ่มต้นของซูเปอร์คลาส

  3. การเปลี่ยนค่าของคุณสมบัติที่กำหนดโดยซูเปอร์คลาส งานการตั้งค่าเพิ่มเติมใดๆ ที่ใช้วิธีการ 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")
print(optionalSquare?.sideLength)
Optional(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()
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?

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

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

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.

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

let protocolValue: ExampleProtocol = a
print(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? เพื่อแปลงผลลัพธ์เป็นทางเลือก หากฟังก์ชันเกิดข้อผิดพลาด ข้อผิดพลาดเฉพาะจะถูกยกเลิก และผลลัพธ์จะเป็น 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
}
print(fridgeContains("banana"))
print(fridgeIsOpen)
false
false

ยาสามัญ

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

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"]

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

// 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
}
print(anyCommonElements([1, 2, 3], [3]))
true

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