Combine: ما هو، المفاهيم الأساسية و Publisher مع Subscriber

المؤلف: IT Sectr نُشر: 2026-03-17 وقت القراءة: 8 دق

Combine هو إطار عمل للبرمجة التفاعلية من Apple، تم تقديمه في iOS 13. وفقاً لـ Apple Documentation, 2025، يوفر Combine واجهة برمجة تطبيقات تصريحية موحدة لمعالجة الأحداث غير المتزامنة. Publisher يحدد مصدر البيانات، و Subscriber يشترك لاستقبالها.

الوجبات الرئيسية

  • Combine — إطار عمل مدمج من Apple للبرمجة التفاعلية، متاح منذ iOS 13
  • Publisher — بروتوكول يحدد مصدر قيم Output مع إمكانية خطأ Failure
  • Subscriber — بروتوكول يستقبل القيم من Publisher مع إدارة الطلب (demand)
  • Subject — Publisher يسمح بالإدراج اليدوي للأحداث (PassthroughSubject, CurrentValueSubject)
  • Operators — map, filter, combineLatest, flatMap لتحويل ودمج التدفقات

ما هو Combine؟

Combine هو إطار عمل Swift تصريحي لمعالجة الأحداث غير المتزامنة عبر الزمن. على عكس الأساليب القائمة على الاستدعاءات (callback)، يسمح Combine بوصف خطوط أنابيب معالجة البيانات من خلال سلسلة من المشغلات. الإطار متكامل مع Foundation (URLSession, Timer, NotificationCenter) و SwiftUI (ObservableObject, @Published).

بروتوكول Publisher

أي نوع يطبق بروتوكول Publisher يجب أن يحدد Output (نوع القيم) و Failure (نوع الخطأ). Publisher لا يكون نشطاً حتى الاشتراك — يبدأ في إصدار الأحداث فقط بعد استدعاء subscribe. هذه هي الدلالات الباردة، المميزة للتدفقات التفاعلية، مما يحسن الكفاءة.

بروتوكول Subscriber

Subscriber يستقبل الأحداث من خلال ثلاث طرق: receive(subscription:) — تأكيد الاشتراك مع إدارة الطلب؛ receive(_:) — استقبال قيمة جديدة؛ receive(completion:) — إنهاء التدفق بنجاح أو خطأ. Demand تحدد عدد القيم التي يكون Subscriber مستعداً لاستقبالها — هذه هي آلية الضغط العكسي (backpressure).

Publishers المدمجة

توفر Apple العديد من Publishers المدمجة: Just يصدر قيمة واحدة وينتهي، Future — نتيجة غير متزامنة مع إغلاق (closure)، Deferred — إنشاء مؤجل لـ Publisher حتى لحظة الاشتراك. URLSession.dataTaskPublisher يحول طلب الشبكة إلى تدفق تفاعلي. Timer.publish ينشئ مؤقتاً دورياً. NotificationCenter.default.publisher يحول الإشعارات إلى Publisher. هذه التكاملات تسمح بالاشتراك في الأحداث دون جسر يدوي.

Publisher و Subscriber في Combine

في Combine، Publisher و Subscriber مرتبطان عبر بروتوكول Subscription. Subscription هو كائن اتصال يتحكم في تدفق البيانات. لا يحتاج المطورون إلى إنشاء أنواع Publisher خاصة بهم — توفر Apple أنواعاً مدمجة: Just, Future, Deferred, Fail, Empty، بالإضافة إلى Publishers لـ URLSession و Timer.

Subject في Combine

Subject هو Publisher يمكن إرسال القيم إليه يدوياً. PassthroughSubject لا يخزن حالة: المشترك يستقبل فقط الأحداث التي حدثت بعد الاشتراك. CurrentValueSubject، المشابه لـ BehaviorRelay في RxSwift، يخزن القيمة الحالية وينقلها فوراً إلى المشتركين الجدد.

الضغط العكسي (Backpressure)

آلية الضغط العكسي في Combine تنظم سرعة نقل البيانات. يخبر Subscriber الـ Publisher من خلال subscription.request(.unlimited) أو subscription.request(.max(N)) بعدد القيم التي هو مستعد لمعالجتها. هذا يمنع تجاوز سعة المخزن المؤقت عندما تكون سرعات المنتج والمستهلك غير متساوية.

مشغلات Combine

يتضمن Combine أكثر من 100 مشغل، مقسمة إلى فئات: التحويل (map, tryMap, flatMap)، التصفية (filter, compactMap, removeDuplicates)، الدمج (combineLatest, merge, zip)، إدارة الوقت (debounce, throttle, delay)، ومعالجة الأخطاء (catch, replaceError, retry).

المشغل combineLatest يجمع أحدث القيم من Publisherين — في كل مرة يصدر أحدهما قيمة جديدة، يتم استدعاء الإغلاق (closure) بكلتا القيمتين المحدثتين. هذا المشغل لا غنى عنه للتحقق من صحة النماذج حيث تحتاج إلى تتبع حالة حقول إدخال متعددة في وقت واحد.

المشغل debounce يؤخر نشر القيم حتى يمر فاصل زمني محدد دون أحداث جديدة. هذا أمر بالغ الأهمية لحقول البحث: يتم إرسال الطلب إلى الخادم فقط بعد أن يتوقف المستخدم عن الكتابة لمدة 300–500 ميللي ثانية، مما يقلل الحمل بمقدار 5–10 مرات.

معالجة الأخطاء

يقدم Combine عدة استراتيجيات لمعالجة الأخطاء على مستوى المشغلات. catch يعترض Failure ويستبدل Publisher بآخر احتياطي — يستمر التدفق دون تعطل. replaceError(with:) يوفر قيمة افتراضية بدلاً من الخطأ. retry(_:) يعيد محاولة الاشتراك عدداً محدداً من المرات عند الفشل. المهم: نوع الخطأ Never في توقيع Publisher يضمن أن التدفق لن ينتهي أبداً بخطأ — وهذا يسمح باستخدام Publisher في SwiftUI دون معالجة Failure.

أمثلة كود Combine

المثال الأول يظهر الاستخدام الأساسي لـ Just — Publisher يصدر قيمة واحدة وينتهي:

swift
let publisher = Just("Hello Combine")

publisher
    .sink(receiveCompletion: { completion in
        print("اكتمل: \(completion)")
    }, receiveValue: { value in
        print("تم الاستقبال: \(value)")
    })

المثال الثاني يوضح تكامل Combine مع URLSession لطلب شبكة مع معالجة النتيجة في الخيط الرئيسي:

swift
let url = URL(string: "https://api.example.com")!

URLSession.shared
    .dataTaskPublisher(for: url)
    .map { $0.data }
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("خطأ: \(error)")
        }
    }, receiveValue: { data in
        print("البيانات المستلمة: \(data.count)")
    })

المثال الثالث يظهر استخدام CurrentValueSubject لإدارة الحالة:

swift
let counter = CurrentValueSubject<Int, Never>(0)

counter
    .sink { value in
        print("العداد: \(value)")
    }

counter.send(1) // يطبع: العداد: 1
counter.send(2) // يطبع: العداد: 2
print(counter.value) // يطبع: 2

Combine مقابل RxSwift

Combine و RxSwift يحلان نفس المشكلة — البرمجة التفاعلية — لكن بأساليب مختلفة. Combine هو إطار عمل أصلي من Apple مدمج في النظام: لا يتطلب تثبيت تبعيات، متكامل مع SwiftUI و Foundation. RxSwift هي مكتبة خارجية تدعم iOS 9+ ومجموعة أوسع من المشغلات.

أداء Combine أعلى في السيناريوهات النموذجية بفضل التنفيذ الأصلي بلغة C. يوفر RxSwift أدوات تصحيح أخطاء أكثر (RxSwift.Resources.total, Debug)، لكن هيكلته الموجهة للكائنات قد تكون أقل كفاءة على تدفقات البيانات الكبيرة. الاختيار بينهما هو مقايضة بين الإمكانيات والتكامل.

الاختلاف المهم هو نموذج الضغط العكسي. في Combine، يدير Subscriber الطلب من خلال Subscription، مما يعطي تحكماً صريحاً في سرعة التدفق. في RxSwift، لا يتم التحكم في الضغط العكسي افتراضياً — Observable يصدر جميع القيم، و Subscriber يعالجها فور وصولها. لتدفقات البيانات الكبيرة، يكون Combine أكثر قابلية للتنبؤ وأماناً من حيث الذاكرة.

Combine متكامل بعمق مع SwiftUI على مستوى اللغة. @Published يولد Publisher، ObservableObject يوفر objectWillChange Publisher. SwiftUI View تشترك تلقائياً في هذه Publishers وتعايد الرسم عند التغييرات. لـ UIKit هناك أغلفة خارجية مثل CombineCocoa، لكن Combine صُمم أصلاً لـ SwiftUI، لذلك يتطلب تكامله في UIKit كوداً إضافياً. RxSwift، من ناحية أخرى، لديه نظام بيئي غني لـ UIKit: RxCocoa يوفر أغلفة تفاعلية لجميع عناصر UI.

Combine يدعم التكامل مع Core Data من خلال NSFetchedResultsController Publisher. يصدر Publisher بيانات جديدة عند كل تغيير في كومة Core Data: إضافة، حذف، تحديث سجلات. هذا يسمح ببناء قوائم تفاعلية دون استدعاء reloadData يدوياً. بدمج Combine مع NotificationCenter Publisher لـ UIApplication.willResignActiveNotification، ينشئ المطورون طبقة تفاعلية لدورة حياة التطبيق بالكامل دون مفوضين (delegates) واستدعاءات (callbacks). لفئات ObservableObject، يقوم SwiftUI بتحديث View تلقائياً عند تغيير خصائص @Published — يدير Combine السلسلة بأكملها دون سطر واحد من الكود اليدوي. يتم اختبار كود Combine من خلال XCTestExpectation و Publisher.sink في حالات الاختبار مع عزل Scheduler عبر ImmediateScheduler.

عند العمل مع Combine في مشاريع UIKit، غالباً ما يشترك المطورون عبر sink ويخزنون AnyCancellable في Set. النمط النموذجي: ViewController ينشئ عرضاً، يخزن cancellables، يشترك في Publisher من ViewModel. عند deinit، يتم إلغاء جميع الاشتراكات تلقائياً من خلال مجموعة Cancellables. للتعامل الصحيح مع UIControl في Combine، يمكن استخدام UIControl.Event Publisher من خلال امتداد publisher(for:).

يتم تصحيح أخطاء Combine من خلال المشغل print() أو handleEvents(). print(String) يسجل جميع الأحداث: receive subscription, request demand, receive value, receive completion. handleEvents يوفر تحكماً أدق: يمكن تحديد closures لكل مرحلة من دورة حياة Publisher. للاختبار، يوجد CombineExpectations — مكتبة تسمح بالتحقق من أن Publisher يصدر القيم المتوقعة في الاختبارات بوقت افتراضي والتحكم في demand.

Combine في التطبيق العملي

يُستخدم Combine بنشاط في SwiftUI لإدارة النماذج: كل خاصية @Published تولد Publisher، SwiftUI يشترك فيها من خلال View.body. بدمج عدة Publishers عبر combineLatest، يصبح التحقق من صحة النموذج تصريحياً: كل Publisher يتتبع حقلاً واحداً، combineLatest يجمع كل القيم، map يتحقق منها ويعيد حالة زر Submit. Publishers.Merge يدمج عدة Publishers من نفس النوع في واحد — مفيد لجمع الأحداث من عناصر تحكم مختلفة في تدفق واحد.

لتكامل Combine مع UIKit، استخدم UIControl Publisher من خلال امتداد: button.publisher(for: .touchUpInside) يعيد Publisher يصدر حدثاً عند الضغط. CombineCocoa هي مكتبة مجتمعية توفر Publishers لجميع أحداث UIControl و UITextView.text و UIScrollView.contentOffset. عند إلغاء الاشتراك، يتم مسح مخزن cancellables في deinit، مما يضمن عدم بقاء أي اشتراك بعد مالكه. لـ UIControl بحالات متعددة، استخدم Publishers.MergeMany لدمج Publishers في مصفوفة.

الأسئلة الشائعة

من أي إصدار iOS يتوفر Combine؟

Combine متاح من iOS 13 و macOS 10.15 و tvOS 13 و watchOS 6. هذا يحد من استخدامه في المشاريع التي تتطلب دعم الإصدارات الأقدم. لإصدارات iOS 12 وما دون، يُستخدم RxSwift أو مكتبات خارجية.

كيف يختلف PassthroughSubject عن CurrentValueSubject؟

PassthroughSubject لا يخزن حالة ولا يعيد القيمة الأخيرة للمشتركين الجدد — يتلقون فقط الأحداث المستقبلية. CurrentValueSubject يخزن القيمة الحالية وينقلها فوراً للمشتركين الجدد. CurrentValueSubject.value يسمح بقراءة وكتابة القيمة الحالية بشكل متزامن.

كيف يتكامل Combine مع SwiftUI؟

Combine هو جوهر SwiftUI: @Published يولد Publisher، ObservableObject يستخدم objectWillChange Publisher. SwiftUI View تشترك تلقائياً في @ObservedObject و @StateObject من خلال خط أنابيب Combine، معايدة رسم View عند تغيير الخصائص.

ما هو الضغط العكسي (backpressure) في Combine؟

الضغط العكسي هو آلية للتحكم في سرعة نقل البيانات. يخبر Subscriber الـ Subscription بعدد القيم المستعد لمعالجتها (demand). لا يمكن لـ Publisher إصدار أكثر مما هو مطلوب. هذا أمر بالغ الأهمية عند العمل مع تدفقات بيانات كبيرة من مآخذ الشبكة.

كيف يتم معالجة الأخطاء في Combine؟

المشغل catch يعترض الخطأ ويستبدل Publisher بآخر احتياطي. replaceError يستبدل الخطأ بقيمة افتراضية. retry يعيد محاولة الاشتراك عدداً محدداً من المرات عند الخطأ. يجب وضع جميع المشغلات قبل subscribe لمنع الأعطال عند Failure.

الخلاصة

  • Combine — إطار عمل مدمج من Apple للبرمجة التفاعلية منذ iOS 13
  • Publisher — بروتوكول مع Output و Failure، يبدأ العمل بعد الاشتراك
  • Subscriber — يستقبل القيم عبر receive مع دعم إدارة الطلب
  • Subject (PassthroughSubject, CurrentValueSubject) — Publisher مع إدراج يدوي للأحداث
  • المشغلات map, filter, combineLatest, debounce, catch — أساس خطوط الأنابيب
  • الضغط العكسي عبر demand يمنع تجاوز سعة مخزن Subscriber
  • اختر Combine للمشاريع الجديدة التي تستهدف iOS 13+، و RxSwift لدعم الإصدارات الأقدم

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا