Coordinator: แนวคิดหลัก รูปแบบผู้ประสานงานสำหรับการนำทาง iOS

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-02-18 เวลาอ่าน: 9 นาที

Coordinator (ผู้ประสานงาน) — รูปแบบสถาปัตยกรรมการนำทางที่ย้ายตรรกะการเปลี่ยนระหว่างหน้าจอจาก ViewController ไปยังคลาสที่แยกต่างหาก รูปแบบนี้ถูกเสนอโดย Soroush Khanlou ในปี 2015 และได้รับการนำไปใช้อย่างกว้างขวางในชุมชน iOS Coordinator จัดการโฟลว์ของแอปพลิเคชัน: สร้างและแสดง ViewController ส่งข้อมูลระหว่างหน้าจอ และจัดการการสิ้นสุดโฟลว์ รูปแบบนี้แก้ปัญหา Massive View Controller โดยการนำการนำทางออกจากคอนโทรลเลอร์ เรียนรู้เพิ่มเติมใน บทความต้นฉบับเกี่ยวกับ Coordinator

ประเด็นสำคัญ

  • Coordinator — รูปแบบการนำทางที่แยกตรรกะการเปลี่ยนหน้าออกจาก ViewController
  • การแยกความรับผิดชอบ — ViewController จัดการ UI, Coordinator จัดการการนำทาง
  • Router — คอมโพเนนต์เสริมของ Coordinator สำหรับทำให้ UINavigationController เป็นนามธรรม
  • Flow — ลำดับหน้าจอที่จัดการโดย Coordinator หนึ่งตัว (เช่น การแนะนำ)
  • การมอบหมาย — ผู้ประสานงานลูกค้ารายงานไปยังผู้ปกครองผ่าน delegate/protocol

Coordinator คืออะไร: แก่นแท้ของรูปแบบการนำทาง

Coordinator — รูปแบบที่รับผิดชอบการนำทางในแอปพลิเคชัน iOS ใน UIKit มาตรฐาน ViewController จัดการการเปลี่ยนหน้าด้วยตนเอง: present, push, show segue — วิธีการนำทางทั้งหมดถูกเรียกจาก UIViewController Coordinator แยกตรรกะนี้ออกมา: ViewController รายงานเหตุการณ์ (เช่น «ผู้ใช้คลิกปุ่มเข้าสู่ระบบ») Coordinator ตัดสินใจว่าจะแสดงหน้าจอใดต่อไป ViewController เหลือเพียงตรรกะ UI และมอบหมายการนำทางให้กับผู้ประสานงาน

โครงสร้างรูปแบบ — CoordinatorProtocol พร้อมเมธอด start() และ finish() start() — จุดเริ่มต้นของโฟลว์: การสร้าง ViewController แรกและการแสดงผล finish() — การสิ้นสุดโฟลว์พร้อมการแจ้งเตือนไปยังผู้ประสานงานผู้ปกครอง Router — ตัวห่อหุ้มรอบ UINavigationController (หรือ UISplitViewController) ที่ให้เมธอด show, push, pop, dismiss ผู้ประสานงานไม่ได้ทำงานโดยตรงกับ UINavigationController — ผ่าน Router เท่านั้น ซึ่งช่วยให้สามารถทดสอบการนำทางและเปลี่ยนเฟรมเวิร์ก UI ได้

คอมโพเนนต์บทบาทตัวอย่าง
Coordinatorการจัดการโฟลว์การนำทางAuthCoordinator, ProfileCoordinator
Routerนามธรรมเหนือ UINavigationControllerpush, present, pop, dismiss
ViewControllerUI + การมอบหมายเหตุการณ์ให้ CoordinatorLoginViewController.delegate

ปัญหาที่ Coordinator แก้ไข — Massive View Controller (การนำทางเป็นสาเหตุทั่วไปของการขยายตัวของคอนโทรลเลอร์) ใน UIKit มาตรฐาน ViewController มี prepareForSegue, ตัวแทนการนำทาง, การจัดการ unwind segues Coordinator กำจัดสิ่งเหล่านี้ Segues ใน storyboard เป็นการเชื่อมต่อแบบคงที่ระหว่างหน้าจอ Coordinator ให้การนำทางแบบไดนามิกพร้อมเงื่อนไข การทดสอบการนำทางกลายเป็นไปได้: สามารถทดสอบ Coordinator โดยไม่ต้อง UI โดยตรวจสอบลำดับการเรียก Router

Coordinator ใน Swift: การนำไปใช้กับ Router และ Flow

Coordinator พื้นฐานใน Swift — โปรโตคอลพร้อมประเภทที่เกี่ยวข้องสำหรับ Router และเมธอด start/finish Router — โปรโตคอลที่ทำให้ UINavigationController เป็นนามธรรม การนำ Router ไปใช้อย่างเป็นรูปธรรมจะห่อหุ้ม UINavigationController และมอบหมายเมธอดให้กับมัน Coordinator รับ Router ใน init และใช้สำหรับการนำทาง ผู้ประสานงานลูกจะถูกเก็บไว้ในอาร์เรย์ childCoordinators สำหรับการจัดการวงจรชีวิต

swift
// Router — นามธรรมการนำทาง
protocol RouterProtocol: AnyObject {
    func push(_ viewController: UIViewController, animated: Bool)
    func pop(animated: Bool)
    func present(_ viewController: UIViewController, animated: Bool)
    func dismiss(animated: Bool)
}

final class NavigationRouter: RouterProtocol {
    private let navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func push(_ vc: UIViewController, animated: Bool) {
        navigationController.pushViewController(vc, animated: animated)
    }

    func pop(animated: Bool) {
        navigationController.popViewController(animated: animated)
    }

    func present(_ vc: UIViewController, animated: Bool) {
        navigationController.present(vc, animated: animated)
    }

    func dismiss(animated: Bool) {
        navigationController.dismiss(animated: animated)
    }
}

// Coordinator — การจัดการโฟลว์
protocol CoordinatorProtocol: AnyObject {
    var childCoordinators: [CoordinatorProtocol] { get set }
    var router: RouterProtocol { get }
    func start()
    func finish()
}

class AuthCoordinator: CoordinatorProtocol {
    var childCoordinators: [CoordinatorProtocol] = []
    let router: RouterProtocol

    init(router: RouterProtocol) {
        self.router = router
    }

    func start() {
        let loginVC = LoginViewController()
        loginVC.onLogin = { [weak self] in
            self?.showHome()
        }
        router.push(loginVC, animated: true)
    }

    private func showHome() {
        let homeCoordinator = HomeCoordinator(router: router)
        childCoordinators.append(homeCoordinator)
        homeCoordinator.start()
    }

    func finish() {
        childCoordinators.removeAll()
        router.pop(animated: true)
    }
}

การสร้าง Coordinator ใน AppDelegate/SceneDelegate — AppDelegate หรือ SceneDelegate สร้าง UINavigationController ห่อหุ้มใน NavigationRouter สร้าง Coordinator หลัก (AppCoordinator) และเรียก start() AppCoordinator ตัดสินใจว่าจะแสดงการแนะนำ การเข้าสู่ระบบ หรือหน้าจอหลัก — ขึ้นอยู่กับสถานะของแอปพลิเคชัน Coordinator เป็นจุดเข้าเดียวสำหรับการนำทาง ViewController ไม่รู้จักหน้าจออื่น

Coordinator ลูกและลำดับชั้นของผู้ประสานงาน

ลำดับชั้นของผู้ประสานงาน — AppCoordinator → AuthCoordinator/MainCoordinator → ProfileCoordinator/SettingsCoordinator ผู้ประสานงานลูกถูกสร้างโดยผู้ปกครองและเก็บไว้ในอาร์เรย์ childCoordinators เมื่อผู้ประสานงานลูกทำงานเสร็จ มันจะเรียก finish() บนผู้ปกครอง และผู้ปกครองจะลบมันออกจาก childCoordinators ซึ่งป้องกันการรั่วไหลของหน่วยความจำ: Coordinator มีการอ้างอิงที่แข็งแกร่งไปยัง ViewController (ผ่าน Router) และหากไม่ถูกลบออกจาก childCoordinators ออบเจกต์จะไม่ถูกปล่อย

swift
// Delegate สำหรับการสื่อสาร Coordinator -> Parent
protocol AuthCoordinatorDelegate: AnyObject {
    func authCoordinatorFinished(_ coordinator: AuthCoordinator)
}

class AuthCoordinator: CoordinatorProtocol {
    weak var delegate: AuthCoordinatorDelegate?

    func finish() {
        delegate?.authCoordinatorFinished(self)
    }
}

// AppCoordinator — ผู้ปกครอง
class AppCoordinator: AuthCoordinatorDelegate {
    func startAuthFlow() {
        let authCoordinator = AuthCoordinator(router: router)
        authCoordinator.delegate = self
        childCoordinators.append(authCoordinator)
        authCoordinator.start()
    }

    func authCoordinatorFinished(_ coordinator: AuthCoordinator) {
        childCoordinators.removeAll { $0 is AuthCoordinator }
        startMainFlow()
    }
}

การจัดการ childCoordinators — การลบ Coordinator ออกจากอาร์เรย์เป็นวิธีเดียวที่จะปล่อยมัน หากคุณลืมลบ Coordinator ที่เสร็จสมบูรณ์ มันจะยังคงอยู่ในหน่วยความจำพร้อมกับ ViewControllers ของมัน วิธีการที่แนะนำ: didMove(toParent:) ของผู้ปกครอง, callback เมื่อเสร็จสิ้น หรือ Combine publisher สำหรับการลบอัตโนมัติ รูปแบบ Coordinator ไม่ได้ระบุกลไกการแจ้งเตือน — delegate, closure หรือ Combine — ตัวเลือกขึ้นอยู่กับนักพัฒนา

วิธีการส่งข้อมูลระหว่างผู้ประสานงาน

การส่งข้อมูลผ่าน delegate — Coordinator ลูกกำหนดโปรโตคอล delegate พร้อมเมธอดที่ใช้ส่งผลลัพธ์: func authCoordinator(_:didLoginWith user: User) ผู้ปกครองนำโปรโตคอลไปใช้และรับข้อมูลเมื่อโฟลว์ลูกเสร็จสมบูรณ์ วิธีนี้ปลอดภัยต่อชนิดและชัดเจน ข้อเสีย: Coordinator ลูกแต่ละตัวต้องมีโปรโตคอลแยกต่างหาก สำหรับโปรเจกต์ที่มี 10+ Coordinator จะทำให้จำนวนไฟล์เพิ่มขึ้น

การส่งข้อมูลผ่านชนิด Result — เมธอด finish รับ Result โดยที่ Output เป็นชนิดเจเนอริกของผลลัพธ์โฟลว์ Coordinator — เจเนอริกพร้อมประเภทผลลัพธ์ที่เกี่ยวข้อง start() พร้อม callback: start(completion: @escaping (Output) -> Void) ซึ่งช่วยลดโค้ด: ไม่ต้องเขียนโปรโตคอลแยกต่างหากสำหรับแต่ละ coordinator RxSwift/Combine: Coordinator เผยแพร่ผลลัพธ์ผ่าน PassthroughSubject/Publisher ตัวเลือกขึ้นอยู่กับแนวทางสถาปัตยกรรมของทีม

วิธีการข้อดีข้อเสีย
Delegateปลอดภัยต่อชนิด, ชัดเจน, โปรโตคอลแยกโปรโตคอลจำนวนมาก, โบยเลอร์เพลตมาก
Closureกะทัดรัด, ไฟล์น้อยลงดีบัก retain cycle ยาก
Combine/Rxรีแอกทีฟ, รวมกันง่ายขึ้นอยู่กับไลบรารี, ดีบักยากขึ้น

เลเยอร์ข้อมูลที่ใช้ร่วมกัน — Coordinator ไม่ได้ส่งข้อมูลโดยตรง แต่ใช้บริการ/พื้นที่เก็บข้อมูลที่ใช้ร่วมกัน AuthCoordinator บันทึกโทเค็นใน Keychain/UserDefaults ProfileCoordinator อ่านจากที่นั่น Coordinator สื่อสารผ่านสถานะที่ใช้ร่วมกัน (คอนเทนเนอร์ Dependency Injection) แทนการเรียกโดยตรง ซึ่งลดการเชื่อมโยงระหว่าง Coordinator แต่สร้างการพึ่งพาโดยนัยต่อสถานะที่ใช้ร่วมกัน

การเปรียบเทียบ Coordinator กับ Router, VIPER และ MVVM-C

Coordinator vs Router — Router เป็นคอมโพเนนต์ของ Coordinator ที่ทำให้ UINavigationController เป็นนามธรรม Coordinator รับผิดชอบโฟลว์ (จะแสดงหน้าจอใด) Router รับผิดชอบกลไก (วิธีแสดง: push/present) Router คือ «อย่างไร» Coordinator คือ «อะไร» สามารถใช้ Router โดยไม่มี Coordinator ได้ (เช่น Navigator singleton) แต่ Coordinator ที่ไม่มี Router ก็แค่ ViewController ที่มีนามธรรมแตกต่าง โดยปกติทั้งสองรูปแบบใช้ร่วมกัน

Coordinator vs VIPER — VIPER มีคอมโพเนนต์ Wireframe ที่รับผิดชอบการนำทาง — คล้ายกับ Coordinator ใน VIPER Wireframe เป็นส่วนหนึ่งของโมดูล Coordinator เป็นเลเยอร์แยกต่างหากเหนือโมดูล โมดูล VIPER (View-Interactor-Presenter-Entity-Router) รวมการนำทางเป็นส่วนหนึ่งของโมดูล Coordinator อยู่ภายนอกโมดูล: มันสร้างและเชื่อมต่อโมดูล แต่ไม่ได้เป็นส่วนหนึ่งของมัน Coordinator ยืดหยุ่นกว่าสำหรับการใช้หน้าจอซ้ำในโฟลว์ต่าง ๆ

MVVM-C — ส่วนขยายของ MVVM ด้วย Coordinator ViewModel ไม่รู้จัก Coordinator โดยตรง — ViewController มอบหมายการนำทางผ่าน ViewModel ViewModel เรียก coordinator ผ่านโปรโตคอล MVVM-C เป็นแนวทางมาตรฐานสำหรับโปรเจกต์ iOS ด้วย SwiftUI: Coordinator จัดการ NavigationStack หรือ fullScreenCover ViewModel เรียก coordinator โดยการเผยแพร่สถานะ Apple ไม่แนะนำ Coordinator สำหรับ SwiftUI — NavigationStack และ NavigationPath เป็นกลไกการนำทางในตัว

swift
// MVVM-C: ViewModel เรียก Coordinator ผ่านโปรโตคอล
protocol AuthNavigationProtocol: AnyObject {
    func showMainScreen()
    func showForgotPassword()
}

class AuthViewModel: ObservableObject {
    weak var navigation: AuthNavigationProtocol?

    func loginTapped() {
        // ตรรกะ...
        navigation?.showMainScreen()
    }
}

คำถามที่พบบ่อย

จำเป็นต้องใช้ Coordinator สำหรับ SwiftUI หรือไม่?

สำหรับ SwiftUI การนำทางในตัว (NavigationStack, NavigationPath) มักแทนที่ Coordinator Apple แนะนำการนำทางแบบ path-based Coordinator เหมาะสมสำหรับโฟลว์ที่ซับซ้อนพร้อมเงื่อนไขลึก (การแนะนำ-เข้าสู่ระบบ-หน้าจอหลักขึ้นอยู่กับบทบาท) สำหรับแอปพลิเคชัน SwiftUI ง่าย ๆ Coordinator มากเกินไป — ใช้ NavigationPath

Coordinator คือ Router หรือไม่?

ไม่ใช่ ทั้งสองเป็นรูปแบบที่แตกต่างกัน Coordinator จัดการโฟลว์การนำทาง: ตัดสินใจว่าจะแสดงหน้าจอใด สร้าง ViewControllers และเชื่อมต่อ它们 Router เป็นนามธรรมเหนือ UINavigationController: push, present, pop, dismiss Coordinator ใช้ Router เพื่อดำเนินการนำทาง ในการนำไปใช้บางอย่าง Router รวมตรรกะของ Coordinator (Router-per-screen) แต่สิ่งนี้เบี่ยงเบนไปจากรูปแบบดั้งเดิม

วิธีหลีกเลี่ยง retain cycle ใน Coordinator?

แหล่งรั่วไหลหลักสองแห่ง: childCoordinators (ผู้ปกครองเก็บลูกไว้ ลืมลบ) และ Router (UINavigationController เก็บ ViewController) วิธีแก้: ลบ Coordinator ลูกออกจากอาร์เรย์เสมอเมื่อ finish() ใช้การอ้างอิง weak สำหรับ delegate สำหรับ Router — อย่าเก็บการอ้างอิงที่แข็งแกร่งไปยัง UINavigationController หากอยู่ในลำดับชั้น window แล้ว ทดสอบ deinit ของ Coordinator

เมื่อใดที่ Coordinator มากเกินไป?

สำหรับแอปพลิเคชันที่มี 3-5 หน้าจอ Coordinator มากเกินไป — segue หรือ navigationController.pushViewController ง่ายกว่า สำหรับแอปพลิเคชัน SwiftUI ที่มี NavigationStack — ก็มากเกินไปเช่นกัน Coordinator เหมาะสมสำหรับแอปพลิเคชันที่มี 15+ หน้าจอ โฟลว์ที่ซับซ้อน (การแนะนำแบบมีกิ่ง การยืนยันตัวตนพร้อมการกู้คืนรหัสผ่าน) และโปรเจกต์แบบผสม UIKit/SwiftUI

วิธีทดสอบ Coordinator?

Mock Router — ตรวจสอบว่าเรียกเมธอดใดและด้วยพารามิเตอร์ใด ตรวจสอบ childCoordinators: หลัง start() อาร์เรย์ไม่ว่าง หลัง finish() — ว่าง Coordinator ถูกทดสอบโดยไม่ต้องใช้ UI: Router เป็นโปรโตคอล Mock ของมันไม่ต้องการ UIKit ใช้ XCTestExpectation สำหรับโฟลว์แบบอะซิงโครนัส ใน Android — การทดสอบที่คล้ายกันของ NavigationController และ NavHost ด้วยการนำทางแบบ mock

สรุป

  • Coordinator — รูปแบบการนำทางที่แยกการเปลี่ยนหน้าจาก ViewController ไปยังคลาสแยกต่างหาก
  • Router — นามธรรมของ UINavigationController ที่ Coordinator ใช้
  • ลำดับชั้น — Coordinator ผู้ปกครองและลูกพร้อมการมอบหมายผลลัพธ์
  • MVVM-C — แนวทางมาตรฐานสำหรับโปรเจกต์ UIKit ด้วย Coordinator
  • SwiftUI — การนำทางในตัวผ่าน NavigationStack แทนที่ Coordinator
  • การทดสอบ — Coordinator ถูกทดสอบผ่าน mock Router โดยไม่ต้องใช้ UIKit

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม