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

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