ViewController Lifecycle คือลำดับของวิธีการที่ UIKit เรียกโดยอัตโนมัติเมื่อจัดการหน้าจอใน iOS ตาม เอกสารของ Apple แต่ละ UIViewController จะผ่านชุดสถานะที่คาดการณ์ได้ ตั้งแต่การสร้าง View ไปจนถึงการปรากฏและการซ่อน การเข้าใจลำดับและวัตถุประสงค์ของวิธีการเหล่านี้เป็นเงื่อนไขจำเป็นสำหรับการทำงานที่เสถียรของแอป iOS
ประเด็นสำคัญ
ViewController Lifecycle คือชุดของวิธีการที่ UIViewController ได้รับจาก UIKit ตลอดอายุการใช้งาน แต่ละหน้าจอในแอป iOS จะผ่านขั้นตอนการสร้าง การโหลด View การปรากฏบนหน้าจอ การซ่อน และการปล่อยหน่วยความจำตามลำดับ UIKit จะเรียกวิธีการที่เกี่ยวข้องในแต่ละขั้นตอนโดยอัตโนมัติ และนักพัฒนาจะเขียนทับวิธีการเหล่านั้นเพื่อเพิ่มตรรกะของตนเอง
สถาปัตยกรรม UIViewController เป็นพื้นฐานของ UIKit และยังคงมีความเกี่ยวข้องแม้ในยุค SwiftUI — หลายโปรเจกต์ยังคงใช้แนวทางคลาสสิกหรือสถาปัตยกรรมแบบไฮบริด การเข้าใจ Lifecycle ช่วยให้คุณคาดการณ์ได้ว่าเมื่อใดที่ subviews พร้อมใช้งาน เมื่อใดที่ปลอดภัยในการแก้ไขเลย์เอาต์ และควรดำเนินการใดเมื่อหน้าจอปรากฏหรือซ่อน
แต่ละวิธีการของวงจรชีวิตมี วัตถุประสงค์เฉพาะ: บางวิธีถูกเรียกเพียงครั้งเดียวตลอดอายุของคอนโทรลเลอร์ ส่วนวิธีอื่น — ทุกครั้งที่ปรากฏหรือซ่อน การผสมตรรกะระหว่างวิธีการนำไปสู่บักที่ค้นหายาก: หน่วยความจำรั่ว การอัปเดตข้อมูลที่ไม่ถูกต้อง และคำขอเครือข่ายที่ไม่จำเป็น
หกวิธีการ สร้างวงจรชีวิตที่สมบูรณ์ของ UIViewController ลำดับการเรียกถูกกำหนดไว้แล้วและไม่ขึ้นอยู่กับวิธีการนำทาง — push, present หรือ unwind segue ทั้งหมดปฏิบัติตามตารางเวลาเดียวกัน
loadView เป็นวิธีแรกของวงจร ถูกเรียกเมื่อ View ของคอนโทรลเลอร์ยังไม่มีอยู่ หากคุณใช้ Storyboard UIKit จะโหลด View จากไฟล์ xib โดยอัตโนมัติ เมื่อสร้างอินเทอร์เฟซด้วยโปรแกรม คุณเขียนทับวิธีนี้โดยกำหนด View รากด้วยตนเอง ในโปรเจกต์ส่วนใหญ่ loadView จะไม่ถูกแตะต้อง — งานจะทำใน viewDidLoad
การเขียนทับ loadView จำเป็นเฉพาะใน กรณีเฉพาะ: เมื่ออินเทอร์เฟซทั้งหมดถูกสร้างด้วยโค้ดโดยไม่มี Storyboard หรือเมื่อ View รากต้องเป็นคลาสที่ไม่ได้มาตรฐาน Apple แนะนำไม่ให้เรียก super.loadView เมื่อเขียนทับ — คุณรับผิดชอบทั้งหมดในการสร้าง View
override func loadView() {
view = UIView()
view.backgroundColor = .white
}
viewDidLoad เป็นวิธีที่ใช้บ่อยที่สุดของวงจร มันถูกเรียกหนึ่งครั้งหลังจากที่ View ถูกโหลดเข้าสู่หน่วยความจำ แต่ยังไม่แสดงบนหน้าจอ ที่นี่คุณกำหนดค่า subviews เติมข้อมูลในตาราง ลงทะเบียนเซลล์ และสมัครรับการแจ้งเตือนที่คงอยู่ตลอดอายุของคอนโทรลเลอร์
คุณสมบัติที่สำคัญ: viewDidLoad ไม่ถูกเรียกอีก เมื่อหน้าจอแสดงอีกครั้ง หากคุณต้องการอัปเดตข้อมูลทุกครั้งที่หน้าจอปรากฏ — ให้ใช้ viewWillAppear วางเฉพาะการดำเนินการครั้งเดียวใน viewDidLoad ที่จำเป็นสำหรับการกำหนดค่าพื้นฐาน
viewWillAppear ถูกเรียกทุกครั้งก่อนที่ View จะปรากฏต่อผู้ใช้ วิธีการนี้รับพารามิเตอร์ animated ที่ระบุว่าการปรากฏเป็นแบบเคลื่อนไหวหรือไม่ ที่นี่คุณอัปเดตข้อมูล โหลดตารางใหม่ กำหนดค่า NavigationBar และซ่อนหรือแสดงองค์ประกอบตามสถานะของแอปพลิเคชัน
ใช้ viewWillAppear สำหรับ การซิงโครไนซ์สถานะ ระหว่างหน้าจอ: หากผู้ใช้สามารถเปลี่ยนแปลงข้อมูลบนหน้าจอก่อนหน้าได้ วิธีนี้คือสถานที่ที่เหมาะสมในการอัปเดตอินเทอร์เฟซ ทุกครั้งที่เรียก viewWillAppear จะเกิดขึ้นก่อนการปรากฏของหน้าจอ แม้เมื่อกลับมาจากคอนโทรลเลอร์ย่อย
viewDidAppear แจ้งว่า View ปรากฏบนหน้าจออย่างสมบูรณ์และแอนิเมชันการเปลี่ยนภาพทั้งหมดเสร็จสมบูรณ์ ณ จุดนี้ หน้าจอพร้อมสำหรับการโต้ตอบ — ผู้ใช้เห็นอินเทอร์เฟซที่สมบูรณ์และสามารถโต้ตอบได้ วิธีการนี้เหมาะสมสำหรับการเริ่มแอนิเมชันที่ควรเริ่มหลังจากการปรากฏ เริ่มตัวจับเวลา และติดตามการแสดงผลการวิเคราะห์
ต่างจาก viewWillAppear viewDidAppear รับประกันว่าหน้าจอไม่เพียงแต่มองเห็นได้ แต่ยังเรนเดอร์อย่างสมบูรณ์ หากคุณเริ่มแอนิเมชันใน viewWillAppear บางเฟรมอาจถูกข้ามไปเพราะ UIKit ยังไม่เสร็จสิ้นการเปลี่ยนภาพ สำหรับแอนิเมชันที่ราบรื่น ให้ใช้ viewDidAppear
viewWillDisappear ถูกเรียกก่อนที่ View จะหายไปจากหน้าจอ — เมื่อเปลี่ยนไปยังคอนโทรลเลอร์อื่น ปิดหน้าต่าง modal หรือพักแอป นี่คือสถานที่ที่เหมาะสมในการบันทึกสถานะ ยกเลิกการสมัครรับการแจ้งเตือน หยุดกระบวนการที่กำลังทำงาน และปล่อยทรัพยากรที่ไม่จำเป็นเมื่อหน้าจอไม่ปรากฏ
สิ่งสำคัญที่ต้องจำ: viewWillDisappear ไม่รับประกันว่า View จะหายไปในที่สุด — ท่าทางอาจถูกยกเลิก ดังนั้น ให้บันทึกข้อมูลสำคัญใน viewDidDisappear ซึ่งถูกเรียกหลังจากที่หายไปจริงเท่านั้น
viewDidDisappear ทำให้วงจรการปรากฏและการหายไปสมบูรณ์ มันถูกเรียกหลังจากที่ View ถูกซ่อนจากหน้าจอแล้ว ในวิธีนี้ แอนิเมชันจะถูกหยุดอย่างถาวร วัตถุชั่วคราวจะถูกลบออก และการบันทึกข้อมูลที่เริ่มใน viewWillDisappear จะถูกยืนยัน
วิธีนี้ยังมาก่อน deinit ของคอนโทรลเลอร์ — หาก UIViewController ของคุณกำลังถูกทำลาย viewDidDisappear จะเป็นวิธีสุดท้ายของ Lifecycle ก่อนที่จะเรียก deinit ใช้มันสำหรับการทำความสะอาดครั้งสุดท้ายที่ควรเกิดขึ้นก่อนที่วัตถุจะถูกทำลาย
ลำดับการเรียกขึ้นอยู่กับว่าหน้าจอปรากฏอย่างไร: ครั้งแรก เมื่อกลับไป หรือเมื่อแสดงแบบ modal ลองพิจารณาสามสถานการณ์หลักจากมุมมองของ UIKit
เมื่อหน้าจอปรากฏครั้งแรก UIKit จะผ่านวงจรการสร้างที่สมบูรณ์: loadView ถูกเรียก จากนั้น viewDidLoad หลังจากนั้นแอนิเมชันการปรากฏจะเริ่มขึ้น ระหว่างแอนิเมชัน viewWillAppear ถูกเรียก และหลังจากเสร็จสิ้น — viewDidAppear นี่คือ สถานการณ์เดียวที่วิธีการทั้งหมดจาก loadView ถึง viewDidAppear ทำงานตามลำดับ
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad — View โหลดเข้าสู่หน่วยความจำแล้ว")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear — กำลังจะปรากฏ")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear — หน้าจอปรากฏอย่างสมบูรณ์")
}
เมื่อผู้ใช้กลับไปยังหน้าจอก่อนหน้า UIKit จะไม่เรียก viewDidLoad อีก — View ถูกโหลดในหน่วยความจำแล้ว แทนที่จะเรียกเฉพาะ viewWillAppear และ viewDidAppear บนหน้าจอที่กลับมา และบนหน้าจอปัจจุบัน — viewWillDisappear และ viewDidDisappear loadView และ viewDidLoad จะถูกข้ามไปเนื่องจากหน้าจอมีอยู่ในสแต็กนำทางแล้ว
การแสดงแบบ modal ปฏิบัติตามกฎเดียวกัน: คอนโทรลเลอร์ใหม่ผ่านวงจรที่สมบูรณ์ในการปรากฏครั้งแรก ในขณะที่คอนโทรลเลอร์ปัจจุบันได้รับ viewWillDisappear และ viewDidDisappear เมื่อ dismiss ลำดับจะกลับกัน: คอนโทรลเลอร์ที่กลับมาได้รับ viewWillAppear และ viewDidAppear อีกครั้ง ในขณะที่คอนโทรลเลอร์ที่ถูกยกเลิกได้รับวิธีการสุดท้าย พฤติกรรมนี้ เหมือนกัน สำหรับการเปลี่ยนภาพทุกประเภทใน UIKit
มาดู สี่สถานการณ์สำคัญที่การเข้าใจ Lifecycle ส่งผลโดยตรงต่อคุณภาพโค้ดและประสบการณ์ผู้ใช้ สำหรับแต่ละสถานการณ์ เราจะให้ตัวอย่างพร้อมคำแนะนำ
viewDidLoad คือสถานที่สำหรับการตั้งค่าเริ่มต้นที่ไม่ขึ้นอยู่กับการมองเห็นหน้าจอ ที่นี่คุณกำหนดค่า collectionView ลงทะเบียนไฟล์ nib สำหรับเซลล์ สร้างแหล่งข้อมูลและเลย์เอาต์ หากคุณกำลังโหลดข้อมูลจากเครือข่าย ใน viewDidLoad ควรเริ่มต้นคำขอเท่านั้น และอัปเดต UI ใน viewWillAppear เมื่อหน้าจอพร้อมสำหรับการแสดงผล
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(
MyCell.self,
forCellReuseIdentifier: MyCell.identifier
)
viewModel.loadInitialData()
}
ใช้ viewWillAppear สำหรับการซิงโครไนซ์ข้อมูลทุกครั้งที่หน้าจอปรากฏ ตัวอย่างเช่น หากผู้ใช้สามารถเปลี่ยนการตั้งค่าบนหน้าจอก่อนหน้า ที่นี่คุณอัปเดตค่าที่แสดง โหลดตารางใหม่ และปรับสถานะ NavigationBar สิ่งนี้รับประกันว่าหน้าจอจะแสดง ข้อมูลล่าสุด เสมอในทุกสถานการณ์การนำทาง
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
navigationController?.setNavigationBarHidden(false, animated: animated)
}
viewDidAppear เหมาะสำหรับเริ่มแอนิเมชันที่ควรเริ่มหลังจากที่ผู้ใช้เห็นหน้าจอ ที่นี่คุณยังส่งเหตุการณ์การวิเคราะห์: การดูหน้าจอ การเริ่มต้น onboarding หรือการเริ่มเล่นวิดีโอ การเริ่มแอนิเมชันก่อนที่การเปลี่ยนภาพจะเสร็จสมบูรณ์นำไปสู่ อินเทอร์เฟซที่กระตุก — UIKit ไม่มีเวลาเพียงพอในการเตรียมจำนวนเฟรมที่เหมาะสม
ใน viewWillDisappear คุณบันทึกฉบับร่าง หยุดตัวจับเวลา และยกเลิกการสมัครจาก NotificationCenter นี่คือช่วงเวลาสุดท้ายที่หน้าจอยังคงมองเห็นและสามารถเข้าถึงได้สำหรับการดำเนินการที่ต้องการบริบทผู้ใช้ สำหรับข้อมูลสำคัญ ให้ใช้ viewDidDisappear เพิ่มเติมเป็นการป้องกันท่าทางที่ถูกยกเลิก
การใช้ผิดวิธีของวิธีการวงจรชีวิตเป็นหนึ่งในแหล่งที่มาของบักที่พบบ่อยที่สุดในแอป iOS มาดูข้อผิดพลาดหลักที่นักพัฒนาทำในขั้นตอนต่างๆ ของการทำงานกับ UIViewController
ข้อผิดพลาดแรก — การสร้าง subviews ใน init หรือ loadView เมื่อใช้ Storyboard หากคุณใช้ Interface Builder อย่าเขียนทับ loadView โดยไม่จำเป็น การสร้าง View ใน loadView เมื่อมี storyboard อยู่จะทำให้ไฟล์ xib ถูกละเว้นและได้หน้าจอว่างเปล่า
ข้อผิดพลาดที่สอง — การสมัครรับการแจ้งเตือนแป้นพิมพ์ใน viewDidLoad โดยไม่ยกเลิกการสมัคร หากคุณสมัครรับ UIResponder.keyboardWillShowNotification แต่ไม่ได้ยกเลิกการสมัครเมื่อซ่อนหน้าจอ บล็อกจะยังคงถูกเรียกแม้หลังจาก deinit ของคอนโทรลเลอร์ — นี่คือ หน่วยความจำรั่ว ที่อาจทำให้แอป crash
ข้อผิดพลาดที่สาม — ตัวจับเวลาและคำขอเครือข่ายที่เริ่มก่อนที่หน้าจอจะปรากฏ การโหลดรูปภาพหรือการทำแอนิเมชันเมื่อ View ยังไม่ปรากฏเป็นการสิ้นเปลืองทรัพยากร ย้ายการอัปเดตภาพไปยัง viewWillAppear หรือ viewDidAppear
ข้อผิดพลาดที่สี่ — การบันทึกข้อมูลเฉพาะใน viewWillDisappear ด้วยท่าทาง pop แบบโต้ตอบ ผู้ใช้อาจเริ่มปัดและยกเลิก — วิธีถูกเรียกแต่หน้าจอไม่ได้หายไป ให้ทำซ้ำการบันทึกสำคัญใน viewDidDisappear หรือในตัวจัดการ applicationDidEnterBackground
คำถามที่พบบ่อย
หนึ่งครั้ง — หลังจากที่ View ถูกโหลดเข้าสู่หน่วยความจำ เมื่อหน้าจอปรากฏอีกครั้ง viewDidLoad จะไม่ถูกเรียก หากคุณต้องการสร้าง View ใหม่ คอนโทรลเลอร์ต้องถูกทำลายและสร้างใหม่
UIKit ต้องการการเรียก super.viewDidLoad เพื่อให้วงจรชีวิตทำงานได้อย่างถูกต้อง หากไม่มี อาจเกิดปัญหากับการอัปเดตเลย์เอาต์และการจัดการการเปลี่ยนภาพ เรียก super เป็นอันดับแรกภายในวิธีการเสมอ
ไม่แนะนำ หากคอนโทรลเลอร์ถูกเริ่มต้นจาก Storyboard UIKit จะโหลด View จาก xib โดยอัตโนมัติ การเขียนทับ loadView จะยกเลิกกระบวนการนี้และ storyboard ของคุณจะถูกละเว้น
สมัครใน viewDidLoad หรือ viewWillAppear และยกเลิกการสมัครใน viewWillDisappear หรือ viewDidDisappear โดยใช้การอ้างอิงแบบ weak ถึง self เพื่อหลีกเลี่ยงหน่วยความจำรั่วกับ closures
Force quit ฆ่ากระบวนการอย่างกะทันหัน — UIKit ไม่มีเวลาเรียกวิธีการ Lifecycle หากต้องการบันทึกข้อมูล ให้ใช้การแจ้งเตือน UIApplication.willTerminateNotification ใน AppDelegate
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ