Combine — แนวคิดหลัก Publisher และการเขียนโปรแกรมเชิงปฏิกิริยา

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

Combine คือเฟรมเวิร์กการเขียนโปรแกรมเชิงปฏิกิริยาแบบเนทีฟของ Apple ที่เปิดตัวใน iOS 13, macOS Catalina, tvOS 13 และ watchOS 6 โดยมี Swift API แบบประกาศสำหรับจัดการเหตุการณ์แบบอะซิงโครนัสผ่านรูปแบบ Publisher และ Subscriber แทนที่ delegates, closures และ NotificationCenter ด้วยเชนแบบรวมศูนย์ ตามข้อมูลของ Apple, 2025 Combine เป็นรากฐานสำหรับ SwiftUI และสถาปัตยกรรม iOS สมัยใหม่ ทำงานร่วมกับ async/await และ Structured Concurrency อย่างใกล้ชิด เฟรมเวิร์กถูกออกแบบมาเพื่อการประกอบการดำเนินการแบบอะซิงโครนัสพร้อมการรับประกันความปลอดภัยของเธรด

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

  • Combine คือเฟรมเวิร์กเชิงปฏิกิริยาแบบเนทีฟของ Apple พร้อมชนิด Publisher, Subscriber, Subject และชนิดข้อผิดพลาดผ่าน Failure
  • Publisher ปล่อยเหตุการณ์ตามเวลา: ค่า Output สามารถเสร็จสมบูรณ์ด้วยความสำเร็จหรือข้อผิดพลาด Failure
  • Subscriber รับเหตุการณ์จาก Publisher และสามารถขอจำนวนรายการที่เฉพาะเจาะจงผ่าน Demand
  • ตัวดำเนินการ map, flatMap, filter, combineLatest, zip, debounce แปลงสตรีมโดยไม่มีสภาวะการแข่งขัน
  • SwiftUI ใช้ Combine ผ่าน @Published, ObservableObject และ @StateObject เพื่ออัปเดตอินเทอร์เฟซเชิงปฏิกิริยา

Combine คืออะไร

Combine คือเฟรมเวิร์กการเขียนโปรแกรมเชิงปฏิกิริยาแบบประกาศที่อยู่ใน Apple SDK มันใช้รูปแบบ Reactive Streams: Publisher สร้างค่า Subscriber ใช้ค่าเหล่านั้น และตัวดำเนินการแปลงสตรีมระหว่างพวกมัน Combine แก้ปัญหา callbacks และ delegates โดยให้โมเดลการประกอบแบบรวมศูนย์สำหรับเหตุการณ์แบบอะซิงโครนัสใด ๆ — จากการตอบสนองของเครือข่ายไปจนถึงการเปลี่ยนแปลง UI

ก่อน Combine นักพัฒนา iOS ใช้ไลบรารีของบุคคลที่สาม โดยเฉพาะ RxSwift Apple สร้าง Combine เป็นทางเลือกแบบเนทีฟพร้อมการบูรณาการอย่างลึกซึ้งในระบบนิเวศ: เฟรมเวิร์กรองรับ Objective-C ผ่านบริดจ์ @objc ทำงานร่วมกับ KVO (Key-Value Observing ผ่าน NSObject.keyValuePublisher) และ NotificationCenter และยังเป็นพื้นฐานสำหรับ SwiftUI คอมโพเนนต์ UIKit ทั้งหมดที่เผยแพร่ใน SwiftUI ใช้ Combine ภายในสำหรับการอัปเดตมุมมอง

Combine ถูกออกแบบโดยคำนึงถึง Swift Concurrency: เริ่มจาก iOS 15, Publisher สามารถแปลงเป็น AsyncSequence ผ่าน .values และใช้ในลูป for-await-in การแปลงกลับจากฟังก์ชัน async เป็น Publisher ทำผ่าน Future ตามข้อมูลของ Apple WWDC 2024, Combine ยังคงเป็นเฟรมเวิร์กที่แนะนำสำหรับการจัดการข้อมูลสตรีมมิงในแอปพลิเคชัน UIKit แม้จะมีการนำ async/await มาใช้สำหรับการเรียกแบบอะซิงโครนัสครั้งเดียว

แนวคิดพื้นฐานของ Combine

Combine ขึ้นอยู่กับสามโปรโตคอล: Publisher (ปล่อยค่าชนิด Output, สามารถล้มเหลวด้วยข้อผิดพลาดชนิด Failure), Subscriber (รับค่า, จัดการ Demand — จำนวนองค์ประกอบที่ร้องขอ), Subscription (แทนการเชื่อมต่อ Publisher-Subscriber ด้วยความสามารถในการยกเลิก) ช่องข้อมูลเริ่มต้นเมื่อ subscribe และสิ้นสุดเมื่อยกเลิก เสร็จสมบูรณ์ หรือมีข้อผิดพลาด Demand เป็นแนวคิดเฉพาะของ Combine: Subscriber บอก Publisher ว่าพร้อมที่จะประมวลผลกี่องค์ประกอบ โดยใช้ backpressure ในระดับโปรโตคอล

Publisher และ Subscriber: สถาปัตยกรรมสตรีมเชิงปฏิกิริยา

Publisher คือโปรโตคอลที่มีสองชนิดที่เกี่ยวข้อง: Output (ชนิดของค่าที่ปล่อย) และ Failure (ชนิดข้อผิดพลาดที่ใช้ Error) หากสตรีมไม่สามารถล้มเหลว Failure จะถูกระบุเป็น Never — สิ่งนี้รับประกันกับ Subscriber ว่า onReceive จะถูกเรียกด้วย Output เท่านั้น Publisher ในตัวประกอบด้วย Just (ค่าเดียว), Sequence (อาร์เรย์), URLSession.DataTaskPublisher (คำขอเครือข่าย), NotificationCenter.Publisher และ @Published property wrapper

swift
import Combine

// การสร้าง Publisher จากลำดับ
let publisher = [1, 2, 3, 4, 5].publisher

// การสร้าง Subscriber พร้อมการจัดการค่า
class PrintSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never

    func receive(subscription: Subscription) {
        subscription.request(.unlimited)
    }

    func receive(_ input: Int) -> Subscribers.Demand {
        print("Received: \(input)")
        return .unlimited
    }

    func receive(completion: Subscribers.Completion<Never>) {
        print("เสร็จสมบูรณ์")
    }
}

publisher.subscribe(PrintSubscriber())

Subscription และ Demand

Subscription คือโปรโตคอลที่แทนการเชื่อมต่อที่ใช้งานระหว่าง Publisher และ Subscriber Subscriber รับ Subscription ในเมธอด receive(subscription:) และเรียก request(_:) เพื่อระบุ Demand: .unlimited (ค่าทั้งหมด), .max(N) (จำนวนจำกัด) หรือ .none (หยุดชั่วคราว) Demand สามารถเปลี่ยนแปลงแบบไดนามิก — Subscriber สามารถเพิ่มหรือลดจำนวนองค์ประกอบที่ร้องขอระหว่างการรับข้อมูล สิ่งนี้ให้ backpressure โดยไม่ต้องบัฟเฟอร์ฝั่ง Publisher

Subject และ CurrentValueSubject

Subject คือชนิดที่รวม Publisher และ Subscriber เข้าด้วยกัน Subject สามารถใช้เป็น Publisher (สมาชิกสมัครสมาชิก) และพร้อมกันเป็น Subscriber (ส่งค่าไปยังมัน) Combine มี Subject สองชนิด: PassthroughSubject (ไม่เก็บสถานะ, ส่งต่อเฉพาะค่าใหม่) และ CurrentValueSubject (เก็บค่าปัจจุบันและส่งต่อไปยังสมาชิกใหม่) Subject จำเป็นสำหรับการรวมโค้ดแบบ imperative ในเชนเชิงปฏิกิริยาของ Combine

swift
let subject = PassthroughSubject<String, Never>()

// การสมัครสมาชิกเป็น Publisher
let cancellable = subject
    .map { $0.uppercased() }
    .sink { print($0) }

// การส่งค่าเป็น Subscriber
subject.send("hello")  // พิมพ์ "HELLO"
subject.send("world")  // พิมพ์ "WORLD"

CurrentValueSubject แตกต่างจาก PassthroughSubject ตรงที่มีค่าเริ่มต้นและคุณสมบัติ value: สมาชิกได้รับค่าปัจจุบันทันทีเมื่อสมัครสมาชิก จากนั้นได้รับการอัปเดตที่ตามมาทั้งหมด CurrentValueSubject.value สามารถอ่านและเขียนได้ — การเปลี่ยน value จะส่งค่าใหม่ไปยังสมาชิกทั้งหมดโดยอัตโนมัติ สิ่งนี้ทำให้ CurrentValueSubject เป็นตัวเลือกที่เหมาะสมสำหรับการแทนสถานะในสถาปัตยกรรม MVVM: ViewModel เผยแพร่ CurrentValueSubject, View สมัครสมาชิกการเปลี่ยนแปลงผ่าน sink

ทั้งสอง Subject สามารถยุติสตรีมโดยเรียก send(completion: .finished) หรือ send(completion: .failure(error)) หลังจากการยุติ Subject หยุดรับและปล่อยเหตุการณ์ สำหรับสตรีมระยะยาวที่ไม่ควรยุติ (เช่น เหตุการณ์ UI) แนะนำให้ใช้ PassthroughSubject กับ Never Failure เพื่อหลีกเลี่ยงการเรียก send(completion:) โดยไม่ตั้งใจ

ตัวดำเนินการแปลงและรวม

ตัวดำเนินการ Combine คือเมธอดของ Publisher ที่ส่งคืน Publisher ใหม่ แต่ละตัวดำเนินการสร้างออบเจกต์ใหม่ที่สมัครสมาชิก Publisher ต้นทางและปล่อยค่าที่แปลงแล้วปลายทาง เนื่องจาก Publisher เป็นชนิดทั่วไป ตัวดำเนินการรักษาการกำหนดชนิดที่เข้มงวด: map แปลง Output<A> เป็น Output<B>, tryMap เพิ่มความเป็นไปได้ของข้อผิดพลาด Combine มีตัวดำเนินการในตัวประมาณ 100 ตัว

หมวดหมู่ตัวดำเนินการวัตถุประสงค์
การแปลงmap / tryMap / flatMapแปลงค่าหรือสตรีม
การกรองfilter / compactMap / removeDuplicatesเลือกหรือทำความสะอาดค่า
การรวมcombineLatest / zip / mergeรวมหลาย Publisher
การควบคุมเวลาdebounce / throttle / delayหน่วงและเว้นระยะเหตุการณ์
การจัดการข้อผิดพลาดcatch / retry / replaceErrorกู้คืนหลังจาก Failure
การจัดการ Demandbuffer / collectการจัดกลุ่มหรือบัฟเฟอร์

flatMap ใน Combine มีความแตกต่างที่สำคัญจากเวอร์ชัน RxSwift: มันรับ closure ที่ส่งคืน Publisher ด้วยชนิด Failure เดียวกันและทำให้ Publisher ที่ซ้อนกันแบนลงในสตรีมหลัก flatMap กับ maxPublishers: .max(1) ทำตัวเหมือน switchMap — ยกเลิก Publisher ที่ซ้อนกันก่อนหน้าเมื่อมีค่าใหม่มาถึง สิ่งนี้สำคัญสำหรับสถานการณ์การค้นหา: เมื่อพิมพ์อักขระใหม่ คำขอ HTTP ก่อนหน้าจะถูกยกเลิกโดยอัตโนมัติ

swift
// การค้นหาแบบ debounce พร้อมยกเลิกคำขอก่อนหน้า
searchTextField.textPublisher
    .debounce(for: .seconds(0.3), scheduler: RunLoop.main)
    .removeDuplicates()
    .flatMap(maxPublishers: .max(1)) { query in
        apiService.searchPublisher(query)
            .catch { _ in Just([]) }
    }
    .receive(on: DispatchQueue.main)
    .sink { results in
        self.tableView.reloadData()
    }
    .store(in: &cancellables)

ตัวดำเนินการรวม — combineLatest และ zip — ทำงานคล้ายกับ RxSwift: combineLatest ปล่อยทูเพิลของค่าล่าสุดจาก Publisher ทั้งหมดเมื่อมีการเปลี่ยนแปลงใด ๆ zip จับคู่ค่าตามดัชนี merge รวม Publisher ชนิดเดียวกันเป็นสตรีมเดียว ไม่รับประกันการรักษาลำดับ Combine ยังมี select — ตัวดำเนินการหายากที่เลือก Publisher ที่เสร็จสมบูรณ์ตัวแรกจากหลายตัว และ share — การกระจายสตรีมไปยังสมาชิกหลายคนโดยไม่ต้องดำเนินการซ้ำ

Scheduler และการจัดการเธรด

Scheduler ใน Combine คือโปรโตคอลที่กำหนดบริบทการดำเนินการสำหรับตัวดำเนินการ ต่างจาก RxSwift ที่มี Scheduler ในตัวมากกว่า 5 ตัว Combine ใช้กลไกที่มีอยู่ของ Apple: DispatchQueue, RunLoop และ OperationQueue แต่ละชนิดเหล่านี้สอดคล้องกับโปรโตคอล Scheduler ทำให้สามารถส่งต่อไปยัง receive(on:) และ subscribe(on:) โดยตรงโดยไม่ต้องใช้อะแดปเตอร์เพิ่มเติม

receive(on:) สลับปลายทางไปยัง Scheduler ที่ระบุ — เทียบเท่ากับ observeOn ใน RxSwift ตัวดำเนินการทั้งหมดหลังจาก receive(on:) ดำเนินการบน Scheduler ที่ระบุ subscribe(on:) สลับต้นทาง — มีผลต่อการดำเนินการของ Publisher รูปแบบทั่วไป: subscribe(on: DispatchQueue.global()) สำหรับงานพื้นหลังและ receive(on: DispatchQueue.main) สำหรับการอัปเดต UI ใน SwiftUI เมื่อใช้ .onReceive การเชื่อมโยงในตัวกับเธรดหลักไม่จำเป็น แต่สำหรับ sink แนะนำให้ใช้ receive(on:).main อย่างชัดเจน

swift
// การโหลดพื้นหลัง + UI บนเธรดหลัก
URLSession.shared.dataTaskPublisher(for: url)
    .subscribe(on: DispatchQueue.global(qos: .background))
    .tryMap { data, response -> Data in
        guard let http = response as? HTTPURLResponse,
              http.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }
    .receive(on: DispatchQueue.main)
    .decode(type: User.self, decoder: JSONDecoder())
    .sink(receiveCompletion: { print($0) },
         receiveValue: { self.nameLabel.text = $0.name })
    .store(in: &cancellables)

RunLoop.main เป็นทางเลือกของ DispatchQueue.main สำหรับการดำเนินการ UI ความแตกต่างคือ RunLoop.main ผูกกับลูปเหตุการณ์ปัจจุบันของแอปพลิเคชัน ในขณะที่ DispatchQueue.main ผูกกับคิวเธรดหลักทั่วโลก สำหรับ UIKit แนะนำ DispatchQueue.main สำหรับ SwiftUI — RunLoop.main ImmediateWhenScheduler ดำเนินการแบบซิงโครนัสบนเธรดปัจจุบัน — ใช้โดยค่าเริ่มต้นสำหรับการทดสอบและ Publisher ธรรมดา

Combine และ SwiftUI: การบูรณาการผ่าน ObservableObject

ObservableObject คือโปรโตคอล SwiftUI สำหรับออบเจกต์ที่เผยแพร่การเปลี่ยนแปลง คลาสที่ใช้ ObservableObject สามารถใช้ @Published property wrapper สำหรับคุณสมบัติที่การเปลี่ยนแปลงแจ้งให้ SwiftUI ทราบโดยอัตโนมัติเกี่ยวกับความจำเป็นในการวาดใหม่ ภายใต้ฝาครอบ @Published สร้าง Publisher ที่แจ้ง Publisher objectWillChange เมื่อ wrappedValue เปลี่ยนแปลง SwiftUI สมัครสมาชิก objectWillChange ผ่าน @StateObject, @ObservedObject หรือ @EnvironmentObject

@Published และ @StateObject

@Published เป็นวิธีที่พบบ่อยที่สุดในการรวม Combine ใน SwiftUI เมื่อค่าของคุณสมบัติ @Published เปลี่ยนแปลง SwiftUI อัปเดต Views ทั้งหมดที่ใช้ออบเจกต์นั้น @StateObject สร้างอินสแตนซ์ ObservableObject และสมัครสมาชิกการเปลี่ยนแปลงของมัน View ที่สร้างด้วย @StateObject จะวาดใหม่โดยอัตโนมัติเมื่อคุณสมบัติ @Published เปลี่ยนแปลง หากออบเจกต์ต้องถูกแชร์ระหว่างหลาย Views จะใช้ @ObservedObject หรือ @EnvironmentObject

swift
class UserViewModel: ObservableObject {
    @Published var name: String = ""
    @Published var age: Int = 0
    private var cancellables = Set<AnyCancellable>()

    init() {
        $name
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .sink { [weak self] newName in
                AnalyticsService.logNameChange(newName)
            }
            .store(in: &cancellables)
    }
}

struct UserView: View {
    @StateObject var viewModel = UserViewModel()

    var body: some View {
        TextField("Name", text: $viewModel.name)
    }
}

AnyCancellable คือตัวห่อชนิดที่ลบสำหรับ Cancellable ที่เก็บโทเคนการยกเลิกการสมัครสมาชิก Set<AnyCancellable> จัดการวงจรชีวิตของการสมัครสมาชิก: เมื่อเจ้าของถูกดีอินิเชียลไลซ์ Cancellable ทั้งหมดจะถูกยกเลิกโดยอัตโนมัติ ในโปรเจกต์ SwiftUI ประกาศ Set<AnyCancellable> ในคลาส ObservableObject และเพิ่มการสมัครสมาชิกผ่าน .store(in: &cancellables) สำหรับ UIKit ใช้กลไกเดียวกันกับการจัดเก็บใน UIViewController ผ่าน &cancellables หรือการเรียก cancel() ด้วยตนเอง

Combine vs RxSwift

Combine และ RxSwift แก้ปัญหาเดียวกันของการเขียนโปรแกรมเชิงปฏิกิริยาแต่มีความแตกต่างทางสถาปัตยกรรมพื้นฐาน Combine เป็นส่วนหนึ่งของ Apple SDK ที่มีความเข้ากันได้ย้อนหลังถึง iOS 13 RxSwift เป็นไลบรารีของบุคคลที่สามที่รองรับ iOS 8+ Combine ใช้การกำหนดชนิดข้อผิดพลาดที่เข้มงวดผ่าน Failure ทั่วไป RxSwift ใช้ชนิด Error เดียว Combine บูรณาการกับ SwiftUI ในระดับแพลตฟอร์ม RxSwift ต้องใช้ RxCocoa สำหรับส่วนขยาย UI

การเลือกระหว่าง Combine และ RxSwift ขึ้นอยู่กับข้อกำหนดของโปรเจกต์ หากเวอร์ชัน iOS ขั้นต่ำ >= 13 และโปรเจกต์ใช้ SwiftUI — Combine เป็นตัวเลือกธรรมชาติเนื่องจากการบูรณาการในตัวและไม่มี dependencies เพิ่มเติม หากโปรเจกต์รองรับ iOS 11-12 มีโค้ดเบส RxSwift ที่มีอยู่ หรือต้องการตัวดำเนินการเฉพาะที่มีเฉพาะใน RxSwift (เช่น Observable.from(path:)) — RxSwift ยังคงเป็นโซลูชันที่ถูกต้อง

ลักษณะCombineRxSwift
ผู้พัฒนาApple (ในตัวใน SDK)ReactiveX (ชุมชน)
เวอร์ชัน iOSiOS 13+iOS 8+
ชนิดข้อผิดพลาดFailure ทั่วไป (Never สำหรับ UI)Error (ใด ๆ)
การบูรณาการ UI@Published + SwiftUIRxCocoa + UIKit
ตัวดำเนินการ~100 ในตัว400+ ตัวดำเนินการ
Swift Concurrencyผ่าน .values (ลำดับแบบ async)ผ่านไลบรารีบริดจ์

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

ความแตกต่างระหว่าง PassthroughSubject และ CurrentValueSubject คืออะไร

PassthroughSubject ไม่เก็บสถานะ — สมาชิกได้รับเฉพาะเหตุการณ์ที่ส่งหลังจากการสมัครสมาชิก CurrentValueSubject เก็บค่าปัจจุบันและส่งต่อไปยังสมาชิกใหม่แต่ละคนทันทีเมื่อสมัครสมาชิก CurrentValueSubject เหมาะสำหรับการแทนสถานะ (เช่น isLoggedIn)

จะยกเลิกการสมัครสมาชิกใน Combine ได้อย่างไร

การสมัครสมาชิกส่งคืน AnyCancellable ซึ่งจะถูกยกเลิกเมื่อเรียก cancel() หรือเมื่อดีอินิเชียลไลซ์ สำหรับการจัดการกลุ่มให้ใช้ Set<AnyCancellable> — การสมัครสมาชิกทั้งหมดจะถูกยกเลิกเมื่อล้างเซต สิ่งนี้คล้ายกับ DisposeBag ใน RxSwift

ฉันต้องเรียนรู้ Combine หลังจาก async/await หรือไม่

ใช่ Combine ยังคงสำคัญสำหรับข้อมูลสตรีมมิง: เหตุการณ์ UI, debounce, combineLatest, WebSocket async/await สะดวกสำหรับคำขอครั้งเดียว Combine — สำหรับสตรีมต่อเนื่องหรือหลายสตรีม เฟรมเวิร์กทั้งสองเสริมกัน — Publisher สามารถแปลงเป็น AsyncSequence

วิธีใช้ Combine กับ UIKit

UIKit ไม่มี Publisher ในตัว แต่ Apple มีส่วนขยาย: NotificationCenter.default.publisher(for:), Timer.publish, URLSession.dataTaskPublisher สำหรับเหตุการณ์ UI ที่กำหนดเอง ใช้ PassthroughSubject หรือ @IBAction ที่ห่อใน Publisher ผ่าน Future หรือ Subject

backpressure ใน Combine คืออะไร

Backpressure คือกลไกในการควบคุมความเร็วของสตรีม: Subscriber บอก Publisher ผ่าน Demand ว่าพร้อมที่จะประมวลผลกี่องค์ประกอบ หาก Demand = .max(1) Publisher จะรอคำขอก่อนส่งค่าถัดไป สิ่งนี้ป้องกันบัฟเฟอร์ล้นเมื่อความเร็วของผู้ผลิตและผู้บริโภคไม่ตรงกัน

สรุป

  • Combine คือเฟรมเวิร์กเชิงปฏิกิริยาแบบเนทีฟของ Apple สำหรับ iOS 13+ ด้วยรูปแบบ Publisher-Subscriber และการกำหนดชนิดข้อผิดพลาดที่เข้มงวด
  • Publisher และ Subscriber สร้างช่องทางการสื่อสารพร้อมการจัดการ Demand สำหรับ backpressure
  • Subject (Passthrough และ CurrentValue) รวมโค้ดแบบ imperative ในเชนเชิงปฏิกิริยา
  • ตัวดำเนินการ map, flatMap, combineLatest, debounce, catch ให้การประมวลผลสตรีมแบบประกาศ
  • Scheduler ผ่าน DispatchQueue และ RunLoop จัดการเธรดการดำเนินการโดยไม่บล็อก UI
  • SwiftUI ใช้ Combine ผ่าน @Published, ObservableObject และ @StateObject สำหรับ Views เชิงปฏิกิริยา
  • RxSwift ยังคงเป็นทางเลือกสำหรับโปรเจกต์ที่ใช้ iOS 8+ Combine เป็นตัวเลือกสำหรับโปรเจกต์ใหม่บน iOS 13+

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

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

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

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