ดัดแปลงมาจาก 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
มีสามขั้นตอนที่แตกต่างกัน:
การตั้งค่าคุณสมบัติที่คลาสย่อยประกาศ
การเรียกตัวเริ่มต้นของซูเปอร์คลาส
การเปลี่ยนค่าของคุณสมบัติที่กำหนดโดยซูเปอร์คลาส งานการตั้งค่าเพิ่มเติมใดๆ ที่ใช้วิธีการ 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