ข้อผิดพลาดที่นักพัฒนา Swift ส่วนใหญ่ไม่รู้ว่าพวกเขากำลังทำอยู่

เผยแพร่แล้ว: 2022-03-11

มาจากพื้นหลัง Objective-C ในตอนแรก ฉันรู้สึกเหมือนกับว่า Swift รั้งฉันไว้ สวิฟต์ไม่อนุญาตให้ฉันก้าวหน้าเพราะมีลักษณะการพิมพ์ที่รุนแรง ซึ่งเคยทำให้โกรธเคืองในบางครั้ง

ต่างจาก Objective-C เพราะ Swift บังคับใช้ข้อกำหนดหลายอย่างในเวลาคอมไพล์ สิ่งที่ผ่อนคลายใน Objective-C เช่น ประเภท id และการแปลงโดยนัย ไม่ใช่สิ่งที่ Swift แม้ว่าคุณจะมี Int และ Double และคุณต้องการรวมเข้าด้วยกัน คุณจะต้องแปลงเป็นประเภทเดียวอย่างชัดเจน

นอกจากนี้ ตัวเลือกเป็นส่วนสำคัญของภาษา และถึงแม้จะเป็นแนวคิดที่เรียบง่าย แต่ก็ต้องใช้เวลาพอสมควรกว่าจะชินกับมัน

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

โปรแกรมเมอร์ Swift ส่วนใหญ่มีประสบการณ์กับ Objective-C มาก่อน ซึ่งอาจทำให้พวกเขาเขียนโค้ด Swift โดยใช้แนวทางปฏิบัติเดียวกันกับที่พวกเขาคุ้นเคยในภาษาอื่นๆ และนั่นอาจทำให้เกิดข้อผิดพลาดร้ายแรงได้

ในบทความนี้ เราสรุปข้อผิดพลาดที่พบบ่อยที่สุดที่นักพัฒนา Swift ทำและวิธีหลีกเลี่ยง

อย่าพลาด - แนวทางปฏิบัติที่ดีที่สุดของ Objective-C ไม่ใช่แนวทางปฏิบัติที่ดีที่สุดของ Swift
ทวีต

1. บังคับ-Unwrapping Optionals

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

วิธีหนึ่งคือการผูกที่เป็นทางเลือกโดยใช้ if let หรือ guard let นั่นคือ:

 var optionalString: String? //... if let s = optionalString { // if optionalString is not nil, the test evaluates to // true and s now contains the value of optionalString } else { // otherwise optionalString is nil and the if condition evaluates to false }

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

บางครั้งเรามีตัวแปรที่เราไม่สามารถ (หรือไม่ต้องการ) เริ่มต้นใน class/struct initializer ดังนั้นเราจึงต้องประกาศเป็นทางเลือก ในบางกรณี เราคิดว่าโค้ดเหล่านั้นจะไม่เป็น nil ในบางส่วนของโค้ดของเรา ดังนั้นเราจึงบังคับให้แกะโค้ดออกหรือประกาศให้เป็นทางเลือกที่ไม่ห่อหุ้มโดยปริยาย เนื่องจากง่ายกว่าการผูกตัวเลือกตลอดเวลา ควรทำด้วยความระมัดระวัง

ซึ่งคล้ายกับการทำงานกับ IBOutlet ซึ่งเป็นตัวแปรที่อ้างอิงวัตถุในปลายปากกาหรือกระดานเรื่องราว พวกเขาจะไม่ถูกเริ่มต้นเมื่อเริ่มต้นของวัตถุพาเรนต์ (โดยปกติคือตัวควบคุมการดูหรือ UIView แบบกำหนดเอง) แต่เราสามารถมั่นใจได้ว่าจะไม่เป็น nil เมื่อเรียก viewDidLoad (ในตัวควบคุมมุมมอง) หรือ awakeFromNib (ในมุมมอง) และเพื่อให้เราสามารถเข้าถึงได้อย่างปลอดภัย

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

2. ไม่ทราบข้อผิดพลาดของวงจรอ้างอิงที่แข็งแกร่ง

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

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

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

ในตัวอย่างต่อไปนี้ ซึ่งคุณสามารถเรียกใช้ใน Xcode Playground คลาส Container มีอาร์เรย์และการปิดทางเลือกที่เรียกใช้เมื่อใดก็ตามที่อาร์เรย์มีการเปลี่ยนแปลง (ใช้ผู้สังเกตการณ์คุณสมบัติในการทำเช่นนั้น) คลาส Whatever มีอินสแตนซ์ Container และในเครื่องมือเริ่มต้น จะกำหนดการปิดให้กับ arrayDidChange และการปิดนี้อ้างอิง self ดังนั้นจึงสร้างความสัมพันธ์ที่แน่นแฟ้นระหว่างอินสแตนซ์ Whatever และการปิด

 struct Container<T> { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container<String> init() { container = Container<String>() container.arrayDidChange = { array in self.f(array) } } deinit { print("deinit whatever") } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil

หากคุณเรียกใช้ตัวอย่างนี้ คุณจะสังเกตเห็นว่าไม่ deinit whatever ที่ไม่เคยพิมพ์ ซึ่งหมายความว่าอินสแตน w ของเราจะไม่ได้รับการจัดสรรคืนจากหน่วยความจำ ในการแก้ไขปัญหานี้ เราต้องใช้รายการจับภาพเพื่อไม่ให้จับภาพ self อย่างรุนแรง:

 struct Container<T> { var array: [T] = [] { didSet { arrayDidChange?(array: array) } } var arrayDidChange: ((array: [T]) -> Void)? } class Whatever { var container: Container<String> init() { container = Container<String>() container.arrayDidChange = { [unowned self] array in self.f(array) } } deinit { print("deinit whatever") } func f(s: [String]) { print(s) } } var w: Whatever! = Whatever() // ... w = nil

ในกรณีนี้ เราสามารถใช้ unowned ได้ เพราะ self จะไม่มีวันเป็น nil ตลอดช่วงชีวิตที่ปิดตัวลง

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

3. ใช้ self ได้ทุกที่

ต่างจากใน Objective-C ใน Swift เราไม่จำเป็นต้องใช้ self เพื่อเข้าถึงคุณสมบัติของคลาสหรือ struct ภายในเมธอด เราต้องปิดบังเท่านั้นเพราะต้องยึด self การใช้ self ในที่ที่ไม่จำเป็นไม่ใช่ความผิดพลาดทั้งหมด มันใช้ได้ดี และจะไม่มีข้อผิดพลาดและไม่มีคำเตือน อย่างไรก็ตามทำไมต้องเขียนโค้ดมากกว่าที่คุณต้อง? นอกจากนี้ การรักษาโค้ดของคุณให้สอดคล้องกันเป็นสิ่งสำคัญ

4. ไม่รู้ประเภทของคุณ

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

ในภาษาเชิงวัตถุส่วนใหญ่ เมื่อเราสร้างอินสแตนซ์ของคลาสและส่งผ่านไปยังอินสแตนซ์อื่นและเป็นอาร์กิวเมนต์ของเมธอด เราคาดว่าอินสแตนซ์นี้จะเหมือนกันทุกที่ นั่นหมายถึงการเปลี่ยนแปลงใดๆ ที่เกิดขึ้นจะสะท้อนให้เห็นทุกที่ เพราะอันที่จริง สิ่งที่เรามีเป็นเพียงข้อมูลอ้างอิงจำนวนหนึ่งไปยังข้อมูลเดียวกัน ออบเจ็กต์ที่แสดงพฤติกรรมนี้เป็นประเภทอ้างอิง และใน Swift ทุกประเภทที่ประกาศเป็น class เป็นประเภทอ้างอิง

ต่อไป เรามีประเภทค่าที่ประกาศโดยใช้ struct หรือ enum ประเภทของค่าจะถูกคัดลอกเมื่อถูกกำหนดให้กับตัวแปรหรือส่งผ่านเป็นอาร์กิวเมนต์ไปยังฟังก์ชันหรือเมธอด หากคุณเปลี่ยนแปลงบางอย่างในอินสแตนซ์ที่คัดลอก ต้นฉบับจะไม่ได้รับการแก้ไข ประเภทค่าจะ ไม่เปลี่ยนแปลง หากคุณกำหนดค่าใหม่ให้กับคุณสมบัติของอินสแตนซ์ประเภทค่า เช่น CGPoint หรือ CGSize อินสแตนซ์ใหม่จะถูกสร้างขึ้นพร้อมกับการเปลี่ยนแปลง นั่นเป็นเหตุผลที่เราสามารถใช้ผู้สังเกตการณ์คุณสมบัติในอาร์เรย์ (ดังในตัวอย่างข้างต้นในคลาส Container ) เพื่อแจ้งให้เราทราบถึงการเปลี่ยนแปลง สิ่งที่เกิดขึ้นจริงคืออาร์เรย์ใหม่ถูกสร้างขึ้นพร้อมกับการเปลี่ยนแปลง ถูกกำหนดให้กับคุณสมบัติ จากนั้น didSet จะถูกเรียกใช้

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

5. ไม่ใช้ศักยภาพทั้งหมดของ Enums

เมื่อเราพูดถึง enums เรามักจะนึกถึง C enum พื้นฐาน ซึ่งเป็นเพียงรายการของค่าคงที่ที่เกี่ยวข้องซึ่งเป็นจำนวนเต็มด้านล่าง ใน Swift enums นั้นทรงพลังกว่ามาก ตัวอย่างเช่น คุณสามารถแนบค่ากับแต่ละกรณีการแจงนับ Enum ยังมีเมธอดและคุณสมบัติอ่านอย่างเดียว/คำนวณที่สามารถใช้เพื่อเสริมให้แต่ละเคสมีข้อมูลและรายละเอียดเพิ่มเติม

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

6. ไม่ใช้คุณสมบัติการทำงาน

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

มาดูตัวอย่างกัน

สมมติว่าคุณต้องคำนวณความสูงของมุมมองตาราง เนื่องจากคุณมีคลาสย่อย UITableViewCell ดังต่อไปนี้:

 class CustomCell: UITableViewCell { // Sets up the cell with the given model object (to be used in tableView:cellForRowAtIndexPath:) func configureWithModel(model: Model) // Returns the height of a cell for the given model object (to be used in tableView:heightForRowAtIndexPath:) class func heightForModel(model: Model) -> CGFloat }

พิจารณา เรามีอาร์เรย์ของอินสแตนซ์ของโมเดล modelArray ; เราสามารถคำนวณความสูงของมุมมองตารางด้วยโค้ดหนึ่งบรรทัด:

 let tableHeight = modelArray.map { CustomCell.heightForModel($0) }.reduce(0, combine: +)

map จะส่งออกอาร์เรย์ของ CGFloat ซึ่งมีความสูงของแต่ละเซลล์ และการย่อจะ reduce ขึ้น

หากคุณต้องการลบองค์ประกอบออกจากอาร์เรย์ คุณอาจต้องดำเนินการดังต่อไปนี้:

 var supercars = ["Lamborghini", "Bugatti", "AMG", "Alfa Romeo", "Koenigsegg", "Porsche", "Ferrari", "McLaren", "Abarth", "Morgan", "Caterham", "Rolls Royce", "Audi"] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for s in supercars { if !isSupercar(s), let i = supercars.indexOf(s) { supercars.removeAtIndex(i) } }

ตัวอย่างนี้ดูไม่สวยงาม หรือไม่มีประสิทธิภาพมากนัก เนื่องจากเราเรียก indexOf สำหรับแต่ละรายการ พิจารณาตัวอย่างต่อไปนี้:

 var supercars = ["Lamborghini", "Bugatti", "AMG", "Alfa Romeo", "Koenigsegg", "Porsche", "Ferrari", "McLaren", "Abarth", "Morgan", "Caterham", "Rolls Royce", "Audi"] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } for (i, s) in supercars.enumerate().reverse() { // reverse to remove from end to beginning if !isSupercar(s) { supercars.removeAtIndex(i) } }

ตอนนี้ โค้ดมีประสิทธิภาพมากขึ้น แต่สามารถปรับปรุงเพิ่มเติมได้โดยใช้ filter :

 var supercars = ["Lamborghini", "Bugatti", "AMG", "Alfa Romeo", "Koenigsegg", "Porsche", "Ferrari", "McLaren", "Abarth", "Morgan", "Caterham", "Rolls Royce", "Audi"] func isSupercar(s: String) -> Bool { return s.characters.count > 7 } supercars = supercars.filter(isSupercar)

ตัวอย่างต่อไปจะแสดงให้เห็นวิธีที่คุณสามารถลบการดูย่อยทั้งหมดของ UIView ที่ตรงตามเกณฑ์บางอย่าง เช่น เฟรมที่ตัดกับสี่เหลี่ยมหนึ่งๆ คุณสามารถใช้บางสิ่งเช่น:

 for v in view.subviews { if CGRectIntersectsRect(v.frame, rect) { v.removeFromSuperview() } } ``` We can do that in one line using `filter` ``` view.subviews.filter { CGRectIntersectsRect($0.frame, rect) }.forEach { $0.removeFromSuperview() }

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

7. อยู่ใน Comfort-Zone และไม่ลองใช้ Protocol-Oriented Programming

Swift อ้างว่าเป็น ภาษาการเขียนโปรแกรมเชิงโปรโตคอล ตัวแรก ตามที่กล่าวไว้ในเซสชัน WWDC Protocol-Oriented Programming ใน Swift โดยพื้นฐานแล้ว หมายความว่าเราสามารถจำลองโปรแกรมของเราตามโปรโตคอล และเพิ่มพฤติกรรมให้กับประเภทได้ง่ายๆ โดยปฏิบัติตามโปรโตคอลและขยายส่วนขยายเหล่านั้น ตัวอย่างเช่น เนื่องจากเรามีโปรโตคอล Shape เราสามารถขยาย CollectionType (ซึ่งสอดคล้องกับประเภทต่างๆ เช่น Array , Set , Dictionary ) และเพิ่มวิธีการคำนวณพื้นที่รวมสำหรับทางแยก

 protocol Shape { var area: Float { get } func intersect(shape: Shape) -> Shape? } extension CollectionType where Generator.Element: Shape { func totalArea() -> Float { let area = self.reduce(0) { (a: Float, e: Shape) -> Float in return a + e.area } return area - intersectionArea() } func intersectionArea() -> Float { /*___*/ } }

คำสั่ง where Generator.Element: Shape เป็นข้อจำกัดที่ระบุว่าเมธอดในส่วนขยายจะใช้ได้เฉพาะในกรณีของประเภทที่สอดคล้องกับ CollectionType ซึ่งมีองค์ประกอบของประเภทที่สอดคล้องกับ Shape ตัวอย่างเช่น เมธอดเหล่านี้สามารถเรียกใช้บนอินสแตนซ์ของ Array<Shape> แต่ไม่สามารถเรียกใช้บนอินสแตนซ์ของ Array<String> ถ้าเรามีคลาส Polygon ที่สอดคล้องกับโปรโตคอล Shape วิธีการเหล่านั้นก็จะสามารถใช้ได้สำหรับอินสแตนซ์ของ Array<Polygon> เช่นกัน

ด้วยส่วนขยายโปรโตคอล คุณสามารถกำหนดการใช้งานเริ่มต้นให้กับวิธีการที่ประกาศไว้ในโปรโตคอล ซึ่งจะพร้อมใช้งานในทุกประเภทที่สอดคล้องกับโปรโตคอลนั้นโดยไม่ต้องทำการเปลี่ยนแปลงใดๆ กับประเภทเหล่านั้น (คลาส โครงสร้าง หรือ enums) ซึ่งดำเนินการอย่างกว้างขวางทั่วทั้งไลบรารีมาตรฐานของ Swift ตัวอย่างเช่น map และ reduce ถูกกำหนดในส่วนขยายของ CollectionType และการใช้งานแบบเดียวกันนี้จะถูกแชร์ตามประเภทต่างๆ เช่น Array และ Dictionary โดยไม่ต้องใช้โค้ดเพิ่มเติม

ลักษณะการทำงานนี้คล้ายกับ มิกซ์อิน จากภาษาอื่นๆ เช่น Ruby หรือ Python เพียงแค่ปฏิบัติตามโปรโตคอลที่มีการใช้งานวิธีการเริ่มต้น คุณจะเพิ่มฟังก์ชันการทำงานให้กับประเภทของคุณ

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

ตามที่เราเรียนรู้ Swift ไม่ใช่ภาษาของเล่น

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

เมื่อคุณคุ้นเคยกับ Swift และกลับไปที่ Objective-C คุณจะสังเกตเห็นความแตกต่าง คุณจะพลาดฟีเจอร์ดีๆ ที่ Swift มอบให้และจะต้องเขียนโค้ดที่น่าเบื่อใน Objective-C เพื่อให้ได้ผลลัพธ์แบบเดียวกัน บางครั้ง คุณอาจพบข้อผิดพลาดรันไทม์ที่ Swift ตรวจพบระหว่างการรวบรวม ถือเป็นการอัปเกรดที่ยอดเยี่ยมสำหรับโปรแกรมเมอร์ของ Apple และยังมีอะไรอีกมากที่จะตามมาเมื่อภาษาเติบโตขึ้น

ที่เกี่ยวข้อง: คู่มือนักพัฒนา iOS: จาก Objective-C ถึงการเรียนรู้ Swift