Intermediate Swift


จากที่เขียนเรื่อง Basic ไปเมื่อคราวที่แล้ว (http://www.macbaszii.com/2014/06/introduction-to-swift.html) คราวนี้มาเรื่องที่ยากขึ้นหน่อยแล้วกันเนอะ โดยบาง Feature ของภาษา Swift บางอย่างก็จะมีอยู่แล้วใน Objective-C แต่ทำให้ดูน่ารัก น่าใช้ขึ้นอีกหน่อย

Optionals

เคยไหม ? ฟังก์ชั่นสักตัวที่ให้ค่ากลับมาเป็นผลลัพธ์ หรือบางทีก็ไม่ (nothing value) เรามักจะแยกประเภทเวลาที่ฟังก์ชั่นส่งค่าที่สื่อถึงการไม่มีค่ากลับมา เช่นถ้าเป็น NSRange ก็จะส่ง NSNotFound กลับมา อะไรที่เกี่ยวกับ Index เช่นการหาข้อมูลบางอย่างใน Array ก็จะส่ง -1 หรือไม่ท่าพื้นฐานก็ส่ง nil กลับมาเลย ทำให้ต้องไปดู Protocol ของฟังก์ชั่นที่ใช้ แล้วเช็คเงื่อนไขให้ถูกต้องนั่นแหละ

แต่ใน Swift มีแนวคิดของการใช้ Optionals ตอนนี้เรามี "nothing value หรือปล่าว ?" ให้ใช้กันแล้ว เพียงแค่เติม ? ต่อท้ายไปเท่านั้นเอง มาดูตัวอย่างโค้ดนี้กัน


จากตัวอย่าง Property Car ใน Person Class เป็น Optional นั่นหมายความ "คนอาจจะมี หรือไม่มีรถก็ได้" โดยเราจะไม่สามารถเข้าถึง Property ที่เป็น Optional ได้โดยตรงจะต้องทำสิ่งที่เรียกว่า unwrap ก่อนด้วย 2 วิธีคือ
  • Optional Binding โดยใช้ `if let` operator (`if let` ถือเป็น operator ห้ามใส่วงเล็บหลัง if)
  • Unwrap โดยใช้ ? หรือ ! ต่อท้าย Optional Property
โดยถ้าเราไม่ประกาศ Car Property เป็น Optional จะต้องถูกกำหนดเสมอไม่งั้นจะโดน complier ด่าว่า "Property self.car not initialized" ตรงนี้ก็ต้องคิดเรื่องความมีอยู่ของ Property ของแต่ละ Class ว่าควรจะเขียนยังไง จะต้องกำหนดเลย หรือกำหนดทีหลัง

Optionals Unwrapping Operator

อย่างที่บอกไปด้านบนว่าการ Unwrap Optional Property ในข้อที่สอง แบ่งออกเป็นอีก 2 วิธีคือการใช้ ? หรือ ! แล้วมันต่างกันยังไงล่ะ เราอยากให้คิดว่า Optional คือ Struct ตัวหนึ่ง (ซึ่งจริงๆ มันก็คือ Struct นั่นแหละ) โดยที่มันจะสร้างชั้น (layer) เพื่อป้องกันการเข้าถึงตัวแปรนั้นๆ (wrap) จำได้ไหมว่า Optional คือการ "มีค่า" หรือ "ไม่มีค่า" ของตัวแปรใดๆ ดังนั้นเมื่อ Unwrap แล้วเราก็จะมีโอกาสได้ค่า 2 แบบคือ "ค่าที่เราต้องการ" หรือ "nil" นั่นเอง

โดยที่การ Unwrap ด้วย ! จะเหมือนการบังคับให้ Unwrap โดยเราหวังว่ามันจะต้องมีค่าแน่ๆ ซึ่งจะมีการ execute สิ่งที่อยู่ตามหลังมา เช่น Mark.car!.accelerate() แบบนี้ถ้า Mark.car เกิดเป็น nil ขึ้นมา nil.accelerate() จะถูก execute ด้วยและจะทำให้แอปเราแครชทันที (runtime error) ในขณะที่ถ้าเรา Unwrap ด้วย ? นั้น ถ้า Mark.car เป็น nil จะทำให้ accelerate() ไม่ถูก execute แต่อย่างใด

Implicitly Unwrapping Optional


คล้ายกับการประกาศ Optional ปกติ เพียงแต่จะใช้เครื่องหมาย ! (Exclamation Mark) แทน ? (Question Mark) โดยการประกาศแบบนี้เหมือนกับมันจะทำการ Unwrap ให้เราอัตโนมัติ ดังนั้นก็จะใช้ในกรณีที่เรามั่นใจแน่ๆ ตัว Property หรือ Method ที่เรากำลังเรียกอยู่มี "ค่า" อย่างแน่นอน จะเห็นว่าเราไม่ต้องใส่ ! ตอนที่เรียกใช้เพราะมัน unwrapped อยู่แล้ว

Delegate Pattern


น่ารักขึ้นเยอะเลยใช่ไหม ? ไม่ต้องเขียน respondToSelector อีกต่อไป และไม่ต้องเช็คว่า delegate มีค่าหรือไม่แบบเดิม เพียงแค่ใช้ Optional Chaining ท่านี้น่ารักนะ ผมชอบ :)

ที่ต้องใส่ @objc หน้า protocol เนื่องจาก protocol บน Swift จะไม่มี @optional ให้ใช้นั่นเอง ส่วนวิธีการ implement delegate method ใน Class ที่ประกาศ protocol นี้ก็ไม่ต่างจากเดิมเลยครับ


Struct และ Enum

ใน Swift นั้น 2 Data Structure นี้จะมีความพิเศษมากกว่าภาษาอื่นๆ เยอะเลยทีเดียว เรื่องแรกคือ Struct และ Enum สามารถมี method ได้ด้วย


จากตัวอย่าง จะเห็น Swift จะสร้างฟังก์ชั่นกำหนดค่าเริ่มต้นให้อัตโนมัติ (สามารถที่จะเขียนเองก็ได้)

ส่วน Enum ก็จะใช้ `case` เป็น keyword ในการประกาศ element อีกอย่างคือ เราจะเก็บอะไรใน Enum ก็ได้ ไม่ได้จำกัดแค่ตัวเลขจำนวนเต็มแบบภาษาอื่นๆ


มาลองเขียน Enum ให้ซับซ้อนขึ้นหน่อยดีไหม ?



จากโค้ดด้านบน เรามี Nested Type ชื่อว่า Vitamin จากนั้นสร้าง Property ของ Enum แต่ละตัวที่ชื่อว่า mainVitamin แล้วยัง Override Getter ด้วย :)

Mutable และ Immutable

ใน Objective-C จะมี Class Type ชัดเจนระหว่าง Mutable Type และ Immutable Type เช่น NSArray และ NSMutableArray หรือ NSDictionary และ NSMutableDictionary แต่ใน Swift กลับต่างออกไป

โดยใน Swift เมื่อมี var และ let แล้ว (มันคืออะไรกลับไปอ่านตอนที่แล้วนะ) ก็จะสามารถใช้ได้เลย ในความเข้าใจที่ว่า `constant (ค่าคงที่) would not be change`

Blocks vs Closures

ใครๆ ก็ชอบ Blocks ตั้งแต่ Apple ประกาศ ก็มีคนเริ่มใช้มากขึ้น จนตอนนี้ใช้กันทั่วบ้านทั่วเมือง เพราะทำให้โค้ดสะอาดขึ้น เช่นการทำ Delegation ต่างๆ ใน Swift เอง อะไรที่เหมือนกับ Blocks เราเรียกมันว่า Closure ในตัวอย่างของ Apple Swift Documentation นั้นเป็นแบบนี้


และสามารถ Refactor เป็น


จะเห็นอย่างแรกคือเรามีหลากหลายวิธีในการเขียน Closure เพิ่มมากขึ้น ซึ่งใช้พวก Feature อย่าง Type Inference เข้ามาช่วย หรือจะเป็นสิ่งที่เรียกว่า Shorthand Argument ($0 $1) หรือความเท่สุดคือ Direct Operator Function ( > จากโค้ด) ซึ่งสามารถอ่านรายละเอียดเต็มๆ ของ Closure ได้ที่นี่

The Swift Programming Language: Closures

ใน Entry นี้เราจะไม่พูดถึงว่าเราจะเขียน Block อย่างไร ให้ลองไปอ่านกันเองนะ :) เราจะมาพูดเรื่องที่ไม่เหมือนกับ Blocks ใน Objective-C เสียทีเดียว นั่นคือการ capture variable ของ Closure ซึ่งใน Objective-C นั้น ถ้าเราต้องการเปลี่ยนแปลงค่าที่อยู่นอก scope ของ Closure จะต้องใส่ __block identifier แต่ใน Swift นั้น เราไม่จำเป็นต้องทำแบบนั้นอีกต่อไป นั่นคือ นอกจากเราจะสามารถเปลี่ยนแปลงค่าใน Scope ได้แล้ว Closures ใน Swift ยังฉลาดพอจะ capture ตัวแปรที่อยู่นอก scope ได้ด้วยตัวเอง และตัวแปรที่ถูก capture นั้นจะเป็น reference เมื่อมีการเปลี่ยนแปลงใน scope นั้นๆ แต่ถ้าแค่อ้างอิงเฉยๆ จะเป็น copy แทน (ลองไปหาความหมายของ reference type และ copy type ดูเองนะ)

คราวนี้มาว่ากันเรื่องปัญหาสุดคลาสสิคของการใช้ Blocks หรือ Closures กัน นั่นคือ Strong Reference Cycle มาดูตัวอย่างโค้ดกันก่อน


จะเห็นได้ว่าใน Closure ที่ชื่อว่า agePotion มีการใช้ self นั่นหมายความว่ามีการอ้างอิงถึง instance ที่ถูกสร้างขึ้นอยู่ และในขณะเดียวกัน instance แต่ละตัวที่ถูกสร้างขึ้นก็มี reference ไปหา Closure เช่นกัน บูมมมมมมมม เกิดสิ่งที่เรียกว่า Strong Reference Cycle

แล้วเราจะแก้ปัญหานี้อย่างไรดีล่ะ ? ใน Swift เราจะใช้ Capture List (ใน Objective-C เราจะใช้ weak self pattern เพื่อไม่ให้มีการเพิ่ม reference count) โดย Capture List ที่ว่าจะเป็นการกำหนด weak หรือ unowned reference ไปยัง instance ที่ถูกสร้างและเรียกใช้ภายใน Closure วิธีการใช้งานก็คือ เขียน [unowned/weak self] ข้างหน้าการประกาศ Closure เท่านี้เอง ดังรูป


Unowned และ Weak Reference

เรารู้กันอยู่แล้วว่า weak ใน Objective-C ทำงานอย่างไร ใน Swift ก็ไม่มีอะไรต่างกันนะ แต่สิ่งที่เพิ่มเข้ามาคือ unowned แล้วมันคืออะไรล่ะ ? มาดูตัวอย่างโค้ดกัน จากโค้ดด้านล่าง ความสัมพันธ์ของ Class Person และ BankAccount เป็นดังนี้
  • Person อาจจะมีหรือไม่มี BankAccount
  • BankAccount ควรจะมีเจ้าของผู้ถือบัญชี

จากโค้ดจะทำให้เกิด Reference Cycle ระหว่าง 2 Class นี้ วิธีการแก้วิธีแรกคือใส่ weak reference ให้ owner property ของ BankAccount ซะ แต่อย่างไรก็ตามในกรณีนี้การใส่ unowned reference จะหมายความว่า property ตัวนั้น `จะต้องมีค่า ห้ามเป็น nil เด็ดขาด` 

แค่นั้นเองครับ weak และ unowned reference ทำงานเหมือนกันเด๊ะ นั่นคือการไม่เพิ่ม reference count เวลามีการอ้างอิงถึง Object ใดๆ แต่ต่างกันที่ความหมายของการใช้งาน

Popular posts from this blog

12 วิธี การบริการและดูแลลูกค้าในร้าน Starbucks

[Android Dev] การติดตั้ง Eclipse+AndroidSDK เพื่อพัฒนาโปรแกรมบน Android

"อีสุกอีใส" ประสบการณ์เมื่อต้องมาเป็นตอนอายุ 22