[iOS] Strong and Weak คืออะไร แล้วมันสำคัญอย่างไร?

Khemmachart Chutapetch
Nextzy
Published in
3 min readNov 24, 2016

มีใครเขียน iOS แล้วไม่รู้จัก strong กับ weak บ้างมั้ย? ถ้าอยากจะเขียนแอพให้ดี มีประสิทธิภาพ นี่คือ keyword ที่สำคัญตัวนึงเลยทีเดียว

ปัจจุบันเด็กจบใหม่ หันมาเขียน iOS Application ด้วยภาษา Swift เป็นจำนวนมาก ด้วยเหตุที่ว่า Syntax ง่าย และดูเป็นมิตรมากกว่า Objective-c ผมเองก็เป็นหนึ่งคนที่เริ่มหัดเขียน iOS Application ด้วยภาษา Swift

ในภาษา Swift นั้น เรามักจะไม่ค่อยได้เห็นคีย์เวิร์ดคำว่า Strong บ่อยซักเท่าไหร่ จะมีก็แต่คีย์เวิร์ด Weak — ต่างกับ Objective-c ที่ทุกครั้งที่เราประกาศตัวแปร จะต้องประกาศประเภทของตัวแปรว่าเป็น Strong หรือ Weak

คำถามที่มักจะเกิดขึ้นในใจของคนเริ่มเขียนก็คือ มันคืออะไร? และมันสำคัญอย่างไร? เอาเป็นว่าถ้าเราอยากจะเป็น iOS Developer ที่ดี เราก็ต้องศึกษาวิธีการจัดการกับเม็มโมรีที่ถูกต้องด้วย และคีย์เวิร์ดสองคำนี้แหละ ที่จะทำให้เราเป็น iOS Developer ที่ดีได้ :)

การจัดสรรหน่วยความจำนั้นสำคัญอย่างไร

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

ในการบริหารจัดการเม็มโมรีของแอพพลิเคชั่นนั้น Apple ใช้ Automatic Reference Counting (ARC) — แต่ ARC นั้นไม่ได้ฉลาดอย่างที่เราคิด เรา (Developer) คือคนที่จะต้องบอกว่า Object ไหนควรถูกทำลายทิ้ง (เมื่อถึงเวลาอันสมควร) โดยใช้คีย์เวิร์ด Strong หรือ Weak และคีย์เวิร์ดทั้ง 2 ตัวนี้ยังมีไว้ป้องกันปัญหา Retain cycle อีกด้วย .. อ้ะๆ ศัพท์แปลกๆ มาอีกแล้ว อย่าเพิ่งงงว่า Retrain cycle คืออะไร เดี๋ยวจะมีอธิบายข้างล่าง อดในรอนิดนึงนาจา

A Strong and Weak Reference, Retain cycle

กาลครั้งหนึ่ง

ลองเปรียบ Object ของเราเป็นบอลลูนหนึ่งลูก บอลลูนลูกนี้ไม่สามารถลอยไปไหนได้ถ้าหาก มีคนอย่างน้อยหนึ่งคนถือเชือกที่ผูกกับบอลลูนลูกนี้ไว้ หรือเป็นเจ้าของบอลลูนนั่นเอง

จำนวนคนที่ถือเชือกเรียกว่า Retain count, บอลลูนหนึ่งลูกสามารถมีคนถือเชือกได้หลายคน ซึ่งคนที่ถือเชือกสามารถ get/set proterties ต่างๆ รวมถึงเรียก method จากบอลลูนลูกนั้นได้ผ่านเชือก (ทั้ง strong และ weak reference)

ถ้าหากทุกคน ปล่อยเชือกหมด ไม่มีใครถือเชือกไว้เลย บอลลูนก็จะลอยหายไป หรือ Object ถูกทำลายทิ้ง

A strong reference

คือการที่เราเป็นเจ้าของ object นั้นๆ ก็เหมือนกับเราเป็นเจ้าของบอลลูนที่ถือเชือกที่ผูกบอลลูนไว้ และบอลลูนก็จะไม่ลอยไปไหนถ้าหากเราไม่ปล่อยมือหรือมีคนอื่นถือเชือกไว้อย่างน้อยหนึ่งคน

A weak reference

เปรียบได้กับมีคนอีกคนอยู่บนบอลลูน คนนั้นสามารถเรียก set/get properties ต่างๆ รวมถึง method ผ่านเชือกของเรา แต่จะแตกต่างกันที่ว่า คนนั้นไม่ได้เป็นคนถือเชือก ถ้าหากเราหรือคนถือเชือกปล่อยมือเมื่อไหร่ บอลลูนและคนนั้นก็จะลอยหายวั้บไป และไม่สามารถ access properties ได้อีกต่อไป หรือถูกทำลายทิ้งซึ่งคนที่อยู่บนบอลลูนก็หายไปด้วยนั่นเอง

Retain cycle

สมมติว่า เราเป็นเจ้าของบอลลูน (เราเป็นคนถือเชือก, strong) และคนบนบอลลูน ก็มีเชือกผูกเราไว้เช่นกัน (strong) ลองนึกตามนะครับ มีเชือกสองเส้น เส้นนึงเราผูกบอลลูน อีกเส้น บอลลูนผูกเราไว้

ถ้าหากเราไม่ต้องการบอลลูนลูกนี้อีกต่อไป เราจะปล่อยมันลอยไปได้อย่างไรหากบอลลูนยังถูกผูกติดไว้กับเรา? นี่คือสิ่งที่เรียกว่า Retain cycle

ดังนั้น มันจะดีกว่ามั้ย ถ้าหากเราเป็นเจ้าของบอลลูนอย่างเดียว (Strong) ในขณะที่บอลลูนไม่ได้เป็นเจ้าของเรา (Weak) เมื่อเราต้องการจะทำลายบอลลูนทิ้ง เราก็แค่ตัดเชือกที่มีอยู่เส้นเดียวทิ้ง

แล้วจะใช้ Strong กับ Weak ตอนไหนหล่ะ?

เราจะใช้ Weak ก็ต่อเมื่อตัวแปรนั้นเป็น Asynchronous callbacks,
Asynchronous dispatches, Delegates patterns, Two way ownership (Objects สองตัวมีการถือกันและกัน)

มาดูโค้ดตัวอย่างกันดีกว่า

ผมจะขอยกตัวอย่างด้วยการใช้ MVVM Patterns ซึ่งผมคิดว่ามันเห็นภาพได้มากที่สุดแล้ว

อธิบายให้ฟังนิดๆ หากใครไม่รู้จัก MVVM, MVVM คือ design pattern ตัวหนึ่งซึ่งจะแยก code ที่เกี่ยวกับ view และ business logic ออกจากกัน โดยแยกเป็น class ViewController สำหรับควบคุม view หรือ interface และ class ViewModel สำหรับ business login

แต่ทั้งสองก็ต้องรู้จักกันผ่านตัวแปร โดย ViewController ก็จะมี ViewModel ข้างใน และใน ViewModel ก็สามารถเรียกใช้งาน ViewController ได้โดยผ่านตัวแปร Delegate

มาเริ่มกันเลยดีกว่า ผมจะสร้าง class ขึ้นมาชื่อว่า ViewController ซึ่งข้างในจะมีตัวแปรหนึ่งตัวชื่อ ViewModel, และผมจะมีอีกหนึ่ง class ชื่อ ViewModel ข้างในจะมีตัวแปรชื่อ delegate เป็นตัวแปรประเภท ViewController โดยที่ผมจะใส่ Print(class name) ไว้ที่คลาสทั้งสอง เพื่อเช็คว่า มันถูกทำลายแล้วนาจา

สำหรับวิธีการทดสอบก็คือ ผมจะ present ViewController นี้ขึ้นมา แล้ว dismiss มันทิ้งซะ เพื่อดูว่า method viewDidLoad, และ deinit ถูกเรียกหรือเปล่า

โดยในตัวอย่างนี้ ViewController นั้นเปรียบเสมือน คนถือเชือก และ ViewModel เปรียบเสมือนบอลลูน, การ present ViewController คือการสร้างบอลลูน และการ dismiss คือการปล่อยบอลลูนให้ลอยหายไป

พอรัน Application ผมก็ show/dismiss ไปเรื่อยๆ เพื่อสังเกต log
ปรากฏว่าไม่มีการเรียก deinit เลย และ memory ก็เพิ่มไปสูงถึง 30.6 MB และเพิ่มมากขึ้นเรื่อยๆ ตามการกด show/dismiss

ผมเลยลองแก้ไข จาก strong delegate เป็น weak delegate ตามรูปด้านล่าง

และทดสอบด้วยการ show/dismiss ไปเรื่อยๆ แบบเดิมอีกครั้ง ปรากฏว่าครั้งนี้มีการเรียก deinit แสดงว่าบอลลูนถูกปล่อยไปเรียบร้อยแล้ววว และ memory ก็มีเพิ่มมีลดบ้าง แต่จะอยู่ที่ประมาณ 22.2 MB นิดๆ ไม่มากน้อยไปกว่านี้ซักเท่าไหร่

Debug Memory Graph

ถ้ายังไม่เห็นภาพ เราสามารถกดไปดูที่ Debug Memory Graph บริเวณ Debug Area ได้ โดยเครื่องมือตัวนี้สามารถแสดงให้เราเห็นได้ว่า ณ ช่วงเวลาๆ นั้นมี Object ตัวใดที่ถูกเก็บอยู่หน่วยความจำบ้าง

โดยภาพด้าน ซ้าย ผมใส่ Weak refernece ให้กับ delegate จะสังเกตุได้ว่าหลังจากที่ผมกด Show/Dismiss ไปเรื่อยๆ ในส่วนของ Weak reference นั้นจะมีการคืนหน่วยความจำ โดยจะมีแค่ ViewController กับ ViewModel ที่กำลังถูกใช้งานอยู่เท่านั้น

แต่ในภาพด้าน ขวา ซึ่งเป็น Strong reference นั้น เมื่อผมทำการกด Show/Dismiss ไปเรื่อยๆ จะเกิด Retain Cycle ขึ้นมา และไม่มีการถูกทำลายทิ้งทั้งๆ ที่เรา Dismiss ไปแล้ว ทำให้ ViewController และ ViewModel นั้นค้างอยู่ในหน่วยความจำมากถึงอย่างละ 11 อันแหน่ะ

Weak Reference (ซ้าย) vs Strong Reference (ขวา)

สรุป

Memory allocation เป็นอีกเรื่องที่ iOS Developer ต้องรู้ ยิ่งถ้าหากคุณกำลังเขียนแอพพลิเคชั่นขนาดใหญ่ มีการโหลดรูปภาพจาก Web services มี content มากมาย การจัดการเม็มโมรี่ถือว่าเป็นเรื่องที่สำคัญพอสมควร ซึ่ง Strong and Weak reference ก็เป็นหนึ่งเรื่องที่เราควรจะรู้ไว้

สุดท้าย ผมอัพโหลดโปรเจคที่ใช้ทดสอบขึ้นไว้ที่ Github สามารถโหลดได้ที่
https://github.com/khemmachart/LearnRetainCycle

--

--