Swift Lazy
updated 12 Sep 2018 for Swift 4.2
Lazy เป็นอีกหนึ่งตัวช่วยสำคัญในภาษา Swift ที่ทำให้การเขียน App ง่ายขึ้น ลดการใช้หน่วยความจำและทำงานได้เร็วขึ้นอีกด้วย
ทำความรู้จัก Lazy ในภาษา Swift กัน
Lazy พูดกันง่ายๆ คือการชะลอการทำงาน หรือการคำนวณบางอย่างออกไป ช่วยลดการหน่วงของแอพ (กระตุก) รวมทั้งการจัดการ Code ก็ง่ายขึ้นด้วย
โดย Lazy ใน Swift แบ่งออกเป็น 2 ส่วนใหญ่ๆ ดังนี้
1. Lazy Var
ตัวแปรแบบ Lazy จะพบได้บ่อยสุด ทั้งในตัวแปรระดับออปเจค ตัวแปรระดับคลาส ตัวแปรระบบ รวมทั้งยังใช้ในสตรัคได้อีกด้วย
lazy var ในคลาส หรือสตรัค
บรรทัด 15: คำนวณหาระยะทางโดยใช้ lazy โดยจะคำนวณในบรรทัด 22 ครั้งเดียว ส่วนบรรทัด 23 จะเรียกค่าที่คำนวณไว้แล้วแทน
ข้อดีของ Lazy var แบบนี้คือ จะไม่มีคำนวณระยะทางเลยหากไม่มีการเรียกใช้ และหากมีการขอข้อมูลระยะทางหลายครั้ง ก็จะคำนวณแค่ครั้งเดียวเท่านั้น
ข้อควรรู้ในการใช้ lazy
- ใช้กับคลาสต้องใส่ [unowned self] in ภายใน { … } ทุกครั้งหากมีการเรียกตัวแปรภายใน เพื่อป้องกัน Reference Cycle
- เหมาะที่จะใช้กับคลาสลูกของ UIViewController เพราะจะทำให้ตัวแปรต่างๆ ไม่ถูก Load ขณะ Init ส่งผลให้ ViewDidLoad ถูกเรียกได้เร็วขึ้น
- สามารถเรียกเมธอดต่างๆ ภายใน { … }() ได้
- สำหรับสตรัค ตัวแปรที่สร้างจากสตรัคต้องใช้ var เท่านั้น เช่น var line = Line(…) เพราะว่าค่าใน lazy เริ่มแรกจะไม่มีค่า จะมีก็ต่อเมื่อเรียกครั้งแรก ถือว่าเป็นการ mutating (อัพเดตค่าตัวแปร)
static let, varในคลาส สตรัค หรือระดับ App
บรรทัด 2: สร้างตัวแปร Global ภายในคลาส AppController ในกรณีนี้ไม่จำเป็นต้องใส่ lazy ลงไป ทุกตัวแปรที่ประกาศด้วย static จะเป็น lazy อัตโนมัติ
บรรทัด 5: คล้ายตัวอย่างก่อนหน้า โดยกรณีจะเป็นการสร้าง Array แบบ lazy
ประกาศตัวแปรระบบ สังเกตุว่าไม่ต้องระบุ static แล้ว เพราะหากประกาศในสโคปนอกคลาส หรือสตรัค ทุกอย่างจะถือเป็น lazy หมดโดยอัตโนมัติ
2. Collection Lazy
Lazy แบบที่สองจะมีคนรู้จักและใช้น้อยกว่าแบบแรก ซึ่งหากท่านใดได้อ่านมาถึงตรงนี้ ถือว่าคุณคือผู้โชคดีครับ ^^เพราะจะได้รู้ถึงเคล็ดลับในการเขียน Swift ให้เร็วขึ้น แถมยังลด Ram พีคจนแอพปิดตัวได้อีกด้วย โดยที่ไม่ต้องเพิ่มโค้ดสักบรรทัด
.lazy สามารถเรียกใช้ได้ทั้ง Array และ Diciontary โดยจะเรียกใช้ร่วมกับ map, flatMap, filter, forEach หรืออื่นๆ
ตัวอย่าง
บรรทัด 3–5: ถ้าไม่มี .lazy จะทำให้คำนวณเลขยกกำลังทั้ง Array ก่อนค่อยส่งไปแสดงผล
บรรทัด 3–5: พอใส่ .lazy โค้ดส่วน pow(x, 2) จะดีเลย์ออกไปคำนวณตอนแสดงผล ทำให้ลดการใช้หน่วยความจำเก็บผลลัพธ์ค่ายกกำลังทั้ง Array ลงไปได้
บรรทัด 7–12: เหมือนกับด้านบน ต่างกันที่ใช้ for .. in แทน forEach { .. }
บรรทัด 16: ผสมการใช้ map และ filter เพื่อหาเส้นรอบวง > 40
* บรรทัด 17: ลองเอา Array (…) ออก แล้วสังเกตผลลัพธ์ดูครับ
บรรทัด 20: ตัวอย่างการใช้ maxElement เพื่อหาค่าสูงสุดแบบ lazy
จากตัวอย่างจะเห็นว่า เราสามรถใช้ lazy ได้ในหลายสถาณการณ์ และไม่ได้เพิ่มบรรทัดของโค้ดเลย เพียงแค่เติม .lazy ลงไป และจะเห็นผลชัดเจนหาก Array หรือ Dictionary เราใหญ่มากๆ ก็ยิ่งลดการพีคของหน่วยความจำจน App ปิดตัวไปได้
จะมีบางสถาณการณ์ที่ลดการใช้ CPU ลงได้อย่างมาก เช่น
บรรทัด 5–19: สร้าง Array (count: 50,000) เก็บค่า JSON-Style
บรรทัด 24–28: กด hitMap(..) จะทำการหาชื่อ “Cat” แบบไม่ใช่ Lazy ใช้เวลาเฉลี่ย 0.129
บรรทัด 32–36: กด hitLazyMap(..) จะทำการหาชื่อ “Cat” แบบ Lazy ใช้เวลาเฉลี่ย 0.000168
จากข้อมูลทดสอบนี้ ปรากฎว่าแบบ lazy สามารถหาข้อมูลโดยใช้ indexOf(..) เร็วกว่าถึง ~700 เท่าเลยทีเดียว
Tips
ตัวที่ทำให้ช้าคือ .map{ $0[“name”] as! string } เพราะต้องแปลงข้อมูลปริมาณมากก่อนนำไปใช้ ให้ลองนึกถึงในการใช้งานจริง เราอาจต้องแปลงข้อมูลที่รับมาจาก Api ก่อนนำไปใช้ ซึ่ง .lazy ในตัวอย่างนี้ จะช่วยให้ไม่ต้องแปลงทั้งหมดก็หาผลลัพธ์ได้
วัดการใช้งาน CPU โดยใช้ Time Profile
วัดการใช้งาน Ram โดยใช้ Allocations (Allocation Density)
- เนื่องจากดาต้าเซตใหญ่ ถ้าเขียนแบบปกติจะเห็นช่วง spike ของหน่วยความจำ ส่วนแบบ lazy ไม่เห็นการพีคของหน่วยความจำเลย
สรุป
การใช้ lazy ช่วยให้โค้ดเรารันได้เร็วขึ้น และลดการพีคของหน่วยความจำลงได้ หากคนที่ใช้ map, flatMap, forEeah, filter, … เป็นประจำอยู่แล้ว ไม่ต้องปรับโครงสร้างโค้ดใดๆ ก็สามารถใช้ lazy ได้เลย แต่ถ้าใครยังไม่แม่น syntax หรือยัง ใช้ for .. in .. ในการแปลงค่า หรือหาผลลัพธ์ ลองศึกษาแนวทางนี้ดูครับ จะทำให้โค้ดสั้นลง และเร็วขึ้นอีกด้วย