MVVM in SwiftUI
สถาปัตยกรรม MVVM - แนวคิดสำคัญสำหรับผู้เริ่มต้นพัฒนาแอปด้วย SwiftUI
ในการพัฒนาแอปพลิเคชันสมัยใหม่ นักพัฒนาจำเป็นต้องออกแบบโครงสร้างของโปรแกรมให้สามารถจัดการข้อมูล ตรรกะ และส่วนติดต่อผู้ใช้ได้อย่างเป็นระบบ โดยเฉพาะอย่างยิ่งเมื่อแอปมีความซับซ้อนเพิ่มขึ้น SwiftUI ช่วยให้การสร้าง UI ทำได้สะดวกขึ้น แต่การจัดวางตำแหน่งของ State และ Logic อย่างถูกต้องยังคงเป็นปัจจัยสำคัญที่ส่งผลต่อคุณภาพของซอฟต์แวร์โดยตรง สถาปัตยกรรมแบบ Model–View–ViewModel (MVVM) จึงเป็นแนวทางที่เหมาะสมสำหรับการพัฒนาแอปใน SwiftUI เพราะช่วยแยกความรับผิดชอบของแต่ละส่วนออกจากกันอย่างชัดเจนตามหลักการ Separation of Concerns (SoC) ซึ่งเป็นหลักการพื้นฐานของการออกแบบซอฟต์แวร์คุณภาพสูง
เมื่อใช้ SwiftUI ในการสร้างส่วนติดต่อผู้ใช้ นักพัฒนาจะต้องทำงานกับเฟรมเวิร์กที่สนับสนุนแนวคิด Declarative UI ซึ่งกำหนดให้ UI เป็นผลลัพธ์ของ “State” หรือสถานะของข้อมูล โดยหากข้อมูลมีการเปลี่ยนแปลง UI ก็จะเปลี่ยนตามโดยอัตโนมัติ การที่ UI ปรับการแสดงผลให้สอดคล้องกับสถานะของข้อมูลได้นั้นต้องอาศัยโครงสร้างที่สนับสนุนแนวคิด Reactive Programming และกลไกสำคัญก็คือ Combine ซึ่งจะทำงานผ่านองค์ประกอบอย่าง ObservableObject, @Published และ @StateObject ซึ่งช่วยให้ระบบสามารถตอบสนองต่อข้อมูลที่เปลี่ยนแปลงได้ทันที โดยนักพัฒนาไม่ต้องจัดการด้วยการเขียนคำสั่งแบบเดิมที่มีความยุ่งยาก
บทความนี้จะอธิบายถึงเหตุผลที่ต้องใช้สถาปัตยกรรม MVVM และบทบาทของ Combine รวมถึงวิธีนำแนวคิดเหล่านี้ไปใช้ในการพัฒนาแอปด้วย SwiftUI พร้อมตัวอย่างประกอบอย่างชัดเจน
ปัญหา: การพัฒนาแอปโดยไม่ใช้สถาปัตยกรรม MVVM

ในการพัฒนาแอปด้วย SwiftUI นักพัฒนามือใหม่มักเริ่มต้นด้วยการเขียนโค้ดทั้งหมดเอาไว้รวมกันภายใน View ไม่ว่าจะเป็นการจัดการข้อมูล การคำนวณ การจัดเก็บค่า ตลอดจนการตอบสนองต่อการกระทำของผู้ใช้ โดยเฉพาะเมื่อแอปมีขนาดเล็กหรือหน้าจอไม่ซับซ้อน ตัวอย่างเช่น
แม้ว่าโค้ดลักษณะนี้จะเขียนได้ง่ายและมองเห็นภาพรวมได้ในไฟล์เดียว แต่หากมองในมุมของสถาปัตยกรรมแล้ว จะพบว่า มันรวมเอา “ทุกอย่าง” เข้าไปอยู่ในวัตถุเพียงตัวเดียว ไม่ว่าจะเป็นข้อมูลสมาชิก (ชื่อ วันเกิด เพศ อาชีพ รายได้ สถานะการเป็นสมาชิก) หรือฟังก์ชันสำหรับเปลี่ยนสถานะสมาชิก หากในอนาคต เราจำเป็นต้องเพิ่มรายละเอียดอื่นๆ ลงไปในโปรแกรม เช่น การเก็บข้อมูลที่อยู่ หรือ การคำนวณค่าดัชนีมวลกาย (BMI) โค้ดใน ContactView ก็จะยิ่งมีความยาวมากขึ้นและมีแนวโน้มในการเกิดความซ้ำซ้อนหรือข้อผิดพลาดได้ง่าย
การสร้าง View ที่กำหนดให้รับภาระหลายอย่างมากเกินไปเช่นนี้ มักถูกเรียกว่า Massive View หรือ Massive Object ซึ่งเป็นจุดเริ่มต้นของปัญหาด้านการออกแบบสถาปัตยกรรม เช่น
ความซับซ้อนของโค้ดและความยากในการดูแลรักษา - เมื่อโค้ดในการส่วนติดต่อผู้ใช้และการควบคุมขั้นตอนการทำงานของโปรแกรม (Business logic) อยู่ในไฟล์เดียวกัน โค้ดจะเติบโตอย่างรวดเร็วและยากต่อการทำความเข้าใจ นักพัฒนาต้องไล่ดูโค้ดที่ยาวและปนกันหลายส่วน ทำให้เพิ่มความเสี่ยงในการทำให้ส่วนอื่นเสียหายโดยไม่ตั้งใจ
การขยายฟีเจอร์ใหม่ทำได้ยาก - การแก้ไขหรือเพิ่มฟีเจอร์ใน View ที่มีทั้ง Logic และ UI ปนกันอยู่จะกระทบส่วนอื่นได้ง่าย การเปลี่ยนแปลงเพียงเล็กน้อยอาจส่งผลเสียต่อส่วนอื่นของแอปที่ไม่เกี่ยวข้อง
ไม่สามารถทดสอบ Logic ได้อย่างอิสระ - ขั้นตอนการทำงานที่ปะปนอยู่ใน View ไม่สามารถทดสอบด้วย Unit Test ได้ง่าย ๆ เพราะ View ของ SwiftUI ไม่ได้ถูกออกแบบมาให้ทดสอบโดยตรง การแยก Logic ออกไปใน ViewModel ช่วยให้การทดสอบเป็นไปได้อย่างชัดเจนและเป็นระบบ
การแชร์ข้อมูลระหว่างหลายหน้าจอมีความยุ่งยาก - เมื่อข้อมูลกระจัดกระจายอยู่ใน View หลายตัว ทำให้การแชร์ข้อมูลระหว่างหน้าจอหรือ Component เป็นเรื่องซับซ้อนและเพิ่มความเสี่ยงต่อความไม่สอดคล้องของข้อมูล
โค้ดไม่สามารถนำกลับมาใช้ซ้ำได้ - เมื่อ View บรรจุทั้งข้อมูลและ Logic การนำส่วนของระบบกลับมาใช้ซ้ำมักเป็นไปได้ยาก เนื่องจากส่วนประกอบไม่ถูกออกแบบมาให้เป็นอิสระต่อกัน
ปัญหาเหล่านี้เป็นเหตุผลสำคัญที่นำไปสู่การใช้ สถาปัตยกรรมแบบ Model–View–ViewModel (MVVM) ซึ่งเหมาะสมอย่างยิ่งสำหรับการพัฒนาแอปพลิเคชันด้วย SwiftUI เพราะเราสามารถแยกความรับผิดชอบระหว่างโครงสร้างข้อมูล (Model) ส่วนติดต่อผู้ใช้ (View) ขั้นตอนการทำงานและการจัดการสถานะ (ViewModel) ออกจากกันอย่างชัดเจน
หลักการและองค์ประกอบของสถาปัตยกรรม MVVM

สถาปัตยกรรม Model–View–ViewModel (MVVM) ช่วยจัดระเบียบโค้ดและแยกความรับผิดชอบอย่างเป็นระบบ ตามหลัก Separation of Concerns (SoC)โดยประกอบด้วย 3 ส่วนสำคัญ ได้แก่
Model — โครงสร้างข้อมูล เป็นส่วนที่ทำหน้าที่เก็บรายละเอียดของโครงสร้างที่ใช้แทนวัตถุหรือสิ่งที่สนใจ เช่น รายละเอียดเกี่ยวกับสมาชิก ผู้ใช้ ผลิตภัณฑ์ หรือข้อมูลอื่นๆ โดยไม่มีขั้นตอนการทำงานที่ซับซ้อนอยู่ภายใน มักถูกนิยามเป็น
structหรือclassและไม่มีหน้าที่เกี่ยวข้องกับการแสดงผล
ViewModel — จัดการตรรกะ ขั้นตอนการทำงาน และสถานะของข้อมูล เป็นตัวกลางระหว่าง Model และ View โดยมีหน้าที่ในการจัดการเกี่ยวกับการเปลี่ยนแปลงสถานะของข้อมูล การประมวลผล และการเตรียมข้อมูลเพื่อให้ View ใช้ในการแสดงผล ภายใน ViewModel จะมีการเรียกใช้เฟรมเวิร์กชื่อ Combine เพื่อแจ้งให้ View มีการปรับปรุง UI เมื่อสถานะของข้อมูลมีการเปลี่ยนแปลง
View — ส่วนในการโต้ตอบกับผู้ใช้ ทำหน้าที่สร้างหน้าจอและแสดงข้อมูลที่จัดเตรียมโดย ViewModel โดยไม่มีการบรรจุขั้นตอนการทำงานทางตรรกะภายใน ทั้งนี้เพื่อรักษาความเรียบง่ายของ UI
Combine: กลไกสำคัญของการอัปเดต UI แบบ Reactive
Reactive Programming เป็นแนวคิดการเขียนโปรแกรมที่ให้ความสำคัญกับ “ข้อมูลที่เปลี่ยนแปลง” โดยการมองข้อมูลเป็น กระแสข้อมูล (data stream) ที่ไหลผ่านระบบ และส่วนต่างๆ ของโปรแกรมจะต้อง “ตอบสนอง” ต่อการเปลี่ยนแปลงของข้อมูลแบบอัตโนมัติ โดยไม่จำเป็นต้องสั่งให้อัปเดตด้วยคำสั่งเชิงสั่งการแบบเดิม (imperative) เช่น reloadData() หรือ refreshUI()
หัวใจสำคัญของ Reactive Programming คือ Publisher–Subscriber
Publisher คือ แหล่งข้อมูลที่เกิดการเปลี่ยนแปลง
Subscriber คือ View ที่ต้องการรับรู้ถึงการเปลี่ยนแปลงของข้อมูลนั้น
เมื่อ Publisher เปลี่ยน → Subscriber ได้รับข้อมูล → UI บนหน้าจอก็จะเปลี่ยนตามทันที
แม้ SwiftUI จะมีความสามารถในการวาด UI แบบ Declarative แต่เบื้องหลังการอัปเดต UI ตามข้อมูลที่เปลี่ยนแปลงนั้น มาจากเฟรมเวิร์ก Combine ซึ่งเป็นกลไกหลักที่ทำหน้าที่จัดการกระแสข้อมูลแบบ Reactive ให้กับ SwiftUI โดยมีองค์ประกอบสำคัญดังนี้:
ObservableObject – ตัวแจ้งเตือนว่ามีข้อมูลเปลี่ยนแปลง โดยเมื่อ ViewModel ประกาศตัวเองว่าเป็น
ObservableObjectมันจะมีช่องทางสำหรับแจ้ง SwiftUI ว่า “ในตัวฉันมีข้อมูลเปลี่ยนแล้วนะ”@Published – ตัวประกาศค่าแบบ Reactive – เมื่อใส่
@Publishedหน้าตัวแปรใน ViewModel ตัวแปรนั้นจะถูกจับตามองโดย Combine ซึ่งหมายความว่า เมื่อค่าของตัวแปรนี้เปลี่ยน → Combine จะส่งสัญญาณให้ View ทราบทันที@StateObject – การใช้
@StateObjectกำกับตัวแปร viewModel ซึ่งเป็นอินแสตนท์ของ ViewModel ทำให้ View ทำหน้าที่รับสัญญาณผ่านการสมัครเป็น Subscriber ที่จะรับสัญญาณจาก ViewModel โดยเมื่อข้อมูลที่ถูกใส่@Publishedไว้มีการเปลี่ยนแปลง SwiftUI จะ re-render UI โดยอัตโนมัติ
หากกล่าวอย่างกระชับและเข้าใจง่ายก็จะสามารถอธิบายได้ว่า
Reactive Programming คือ แนวคิดในการเขียนโปรแกรมที่กำหนดให้ UI มีการตอบสนองต่อการเปลี่ยนแปลงของข้อมูลแบบอัตโนมัติ
Combine คือ เครื่องมือที่ทำให้แนวคิดนี้เกิดขึ้นจริง
MVVM คือ สถาปัตยกรรมที่จัดวางบทบาทของส่วนต่าง ๆ อย่างเป็นระเบียบ
SwiftUI คือ เครื่องมือในการสร้างส่วนแสดงผลที่ตอบสนองต่อข้อมูลได้แบบ Reactive
การทำความเข้าใจแนวคิดของสถาปัตยกรรม MVVM ควบคู่กับการใช้ Combine จึงเป็นพื้นฐานสำคัญของการสร้างการตอบสนองต่อการเปลี่ยนแปลงของข้อมูลแบบอัตโนมัติ จะช่วยให้นักศึกษาสามารถใช้ SwiftUI ในการออกแบบและพัฒนาแอปพลิเคชันที่ดูแลง่าย ขยายเพิ่มเติมได้ในอนาคต และรองรับการทำงานในระบบที่มีความซับซ้อนสูงได้อย่างดี
ลองทำ : ระบบติดตามคะแนนผู้เล่น (Player Score Tracker)
คราวนี้เราลองมาออกแบบ "ระบบติดตามคะแนนผู้เล่นอย่างง่าย" ประกอบด้วย Player 1 คน ซึ่งมีชื่อผู้เล่น และคะแนนเริ่มต้นเป็น 0 จากนั้นสร้างปุ่มเพื่อเพิ่มคะแนนและปุ่มเพื่อลดคะแนน โดยมีเงื่อนไขว่า คะแนนต้องไม่ติดลบ
หมายเหตุ: เมื่อคะแนนมีการเปลี่ยนแปลง UI ต้องอัปเดตทันที (อาศัย Combine)
ขั้นตอนที่ 1 : การสร้าง Model ของผู้เล่น
ขั้นตอนที่ 2 : สร้าง ViewModel ที่ใช้ Combine
ขั้นตอนที่ 3 : สร้าง View และผูกเข้ากับกับ ViewModel
หากเราต้องการเพิ่มกฎว่า "ห้ามให้คะแนนเกิน 10" เราจะแก้โค้ดที่ ViewModel เพียงที่เดียว เนื่องจากเป็นส่วนที่ควบคุมตรรกะและขั้นตอนการทำงาน (Business logic) โดยไม่ต้องแก้อะไรใน View หรือ Model
หากต้องการเพิ่มปุ่ม Reset Score ก็ทำได้ง่าย โดยเพิ่มฟังก์ชัน resetScore() ลงใน ViewModel
และสร้างปุ่ม Reset Score ใน View ซึ่งทำหน้าที่ในการโต้ตอบกับผู้ใช้ โดยกำหนดให้มีการเรียกใช้ resetScore() จาก ViewModel
จะเห็นได้ว่า สถาปัตยกรรม MVVM ช่วยให้การพัฒนาแอปด้วย SwiftUI มีความเป็นระเบียบ โดยแยกบทบาทระหว่าง Model, View และ ViewModel ออกจากกันอย่างชัดเจน ทำให้โค้ดอ่านง่าย แก้ไขง่าย และรองรับการปรับปรุงหรือขยายคุณสมบัติของแอปในอนาคตได้ดี ในขณะเดียวกัน Combine ก็ทำหน้าที่เป็นกลไกสำคัญที่เชื่อมการเปลี่ยนแปลงของข้อมูลใน ViewModel กับการอัปเดต UI ใน View ผ่านแนวคิด Reactive Programming
เมื่อใช้ MVVM ร่วมกับ Combine นักพัฒนาจะได้โครงสร้างแอปที่สามารถจัดการสถานะของข้อมูลและอัปเดต UI ได้ทันทีโดยอัตโนมัติ เมื่อข้อมูลมีการเปลี่ยนแปลงสถานะ ลดความซับซ้อนในการเขียนโค้ด และเพิ่มความสามารถในการดูแลรักษาระยะยาว ซึ่งทั้งหมดนี้ทำให้การพัฒนาแอปด้วย SwiftUI มีประสิทธิภาพ ยืดหยุ่น และเหมาะกับโปรเจกต์ตั้งแต่ขนาดเล็กไปจนถึงขนาดใหญ่
แนวทางในการจัดกิจกรรมการเรียนรู้
Last updated