Table of Contents
مقدمة إلى البرمجة التفاعلية مع اللجنة في نظام iOS
ومن حيث الطلب على الشبكة والاستفسارات إلى الواجهة بين المستخدمين وبيانات الاستشعار، يجب على المطورين أن يديروا تدفقا مستمرا للمعلومات تصل إلى أوقات غير متوقعة، ومن الناحية التقليدية، تتطلب معالجة هذه الأحداث المندوبين أو مراكز الابلاغ، مما يؤدي في كثير من الأحيان إلى وضع مدونة معقدة ومعرضة للأخطاء، ويوفر إطار عمل كومبين، الذي أدخله مكتب خدمات المشاريع في البرمجة 13، أنماطا موحَّدة.
ما هو إطار كومبين؟
Combine is a native Swift framework that provides a declarative API for processing values over time. It is part of Apple’s ecosystem and integrates seamlessly with SwiftUI, Foundation, and UIKit. Combine is built on three core abstractions: publishers[FopeT:1] emit values,
وعلى عكس الأنماط السابقة مثل أو KVO، تقدم اللجنة السلامة التجميعية، والاختبارات من نوع إلى آخر، والتكوين القوي، وهي مماثلة في مفهوم التمديدات التفاعلية مثل RxSwift ولكنها طرف أول ومتكاملة تماما مع منابر آبل، وفهم الكمبين أساسي للمطورين الحديثين في مجال تكنولوجيا المعلومات، ولا سيما تلك المباني التي تستخدم في نظام سويفت أو في حيث توجد بيانات كثيرة.
المفاهيم الأساسية للكومبين
وقبل أن يغزو القانون، من الأهمية بمكان فهم العناصر الرئيسية الأربعة التي تشكل كل خط أنابيب كومبين:
- Publisher ] — A type that can emit a sequence of values over time. It declares what kind of values it outputs (]) and what kind of errors it can fail with () A publisher may emit an arbitrary number of values, then eventually complete or fail.
- Subscriber] — A type that receives values from a publisher. It defines three methods: , (to handle a new value), and (to handle success or failure). Most developers use ready-made participantrs instead
- Operator] — A publisher that transforms values from an upstream publisher. Operators like , , ], and are themselves publishers that subscribe to upstream publishers, apply a transformation, and republishing
- الاشتراك ] - الصلة بين ناشر ومشترك، ويتحكم الاشتراك في تدفق الطلب (الضغط) ويمكن إلغاء الاشتراكات لوقف تلقي القيم، التي يتم التعامل معها تلقائياً عندما يتم التعامل مع أو إلغاؤها يدوياً.
وبالإضافة إلى ذلك، توفر اللجنة بروتوكولاً قابلاً للإلغاء (ونوعاً محدداً ) لإدارة دورة حياة الاشتراك، ويكفل تخزين المبالغ الملغومة في مجموعة (عادة ]]) أن تُلغى الاشتراكات تلقائياً عندما يُعالج الجسم الخاص.
صانعو المطبوعات
وتوفر اللجنة العديد من الناشرين المبنيين الذين يغطيون المهام المتناثرة المشتركة، كما يمكن أن تخلق ناشرين متخصصين عن طريق تنفيذ بروتوكول أو باستخدام مبدئيات ملائمة.
جهاز نشر الوقت
(ج) استخدام لرسم التاريخ الحالي على فترات منتظمة، ولبدء تشغيل جهاز التوقيت على الفور، الاتصال :
let timerPublisher = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
ويُقدِّر الناشرون كل ثانية على حلقة العمل الرئيسية.
URLSession Data Publisher
Combine extends with a publisher that performs network requests:
guard let url = URL(string: "https://api.example.com/data") else { return }
let publisher = URLSession.shared.dataTaskPublisher(for: url)
This publisher emits a tuple on completion, or a on failure.
جهاز نشر
تحويل أي إخطار إلى ناشر:
let notificationPublisher = NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)
مفيد للرد على أحداث النظام دون إضافة مراقبين يدوياً.
فقط والمستقبل
]] emits a single value and then completes immediately. It’s perfect for testing or wrapping a constant into a publisher:
let justPublisher = Just("Hello, Combine!")
ناشر ينتج في نهاية المطاف قيمة واحدة (أو يفشل) على نحو غير متزامن، وينفذ الإغلاق المقدم مرة واحدة ويضع النتيجة على المشتركين في وقت لاحق:
let futurePublisher = Future<String, Error> { promise in
// Perform async work, then call promise(.success(value)) or promise(.failure(error))
}
جواز السفر في الموقع الشبكي ورقم الهاتف الفرعي الحالي
ويعمل المواضع كناشر ومشترك، مما يتيح لكم حقن القيم يدويا في مجرى. ] يبث قيما للمشتركين دون الاحتفاظ بقيمة حالية؛ يحتفظ بأحدث قيمة ويعيد تشغيلها للمشتركين الجدد:
let subject = PassthroughSubject<Int, Never>()
subject.send(1)
subject.send(2)
let currentValueSubject = CurrentValueSubject<String, Never>("initial")
currentValueSubject.send("updated")
print(currentValueSubject.value) // "updated"
مقدم إلى ناشر
ولتفعيل خط أنابيب، تحتاج إلى مشترك، وأكثرها شيوعا هي [(FLT:32] و.
"المُتجرّد المُتجرّد"
توفر مناولة قائمة على الإغلاق للقيم المتلقاة ومناسبات الإنجاز:
let subscription = timerPublisher.sink { time in
print("Timer fired at \(time)")
}
يمكنك أيضا معالجة الأخطاء بتوفير إغلاق
publisher
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Stream finished")
case .failure(let error):
print("Error: \(error)")
}
}, receiveValue: { value in
print("Received: \(value)")
})
المُسَكِّر الفرعي للمُسَعَلِّم
يُلزم ناتج الناشر بممتلكات جسم ما، على أن يكون ذلك اختيارياً من خلال مسار رئيسي، ويجب أن تكون الممتلكات مُميّزة أو ممتثلة لـه:
class ViewModel {
@Published var text: String = ""
}
let viewModel = ViewModel()
let publisher = Just("Hello")
let cancellable = publisher.assign(to: \.text, on: viewModel)
print(viewModel.text) // "Hello"
Custom Subscriber
وفيما يتعلق بحالات الاستخدام المتطورة، يمكن أن تنفذوا بروتوكول مباشرة، غير أن هذا نادرا ما يكون ضروريا؛ و] يغطي معظم السيناريوهات.
بيانات التحول مع المشغلين
المشغلون هم العمود الفقري للكومبين مما يسمح لكم بتجميع خطوط الأنابيب المعقدة من المباني البسيطة
خريطة
تحويل كل قيمة منقولة باستخدام إغلاق:
publisher
.map { $0 * 2 }
.sink { print($0) }
المصور
يمر عبر القيم التي تلبي الفرضية:
publisher
.filter { $0.isMultiple(of: 2) }
.sink { print("Even: \($0)") }
FlatMap
يُستخدم عادةً عندما تُحدث كل قيمة عملية جديدة غير مُتقاربة:
let searchPublisher = searchTextPublisher
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.flatMap { text in
return api.searchPublisher(for: text)
}
CombineLatest
يجمع بين القيم الأخيرة من ناشرين أو أكثر في وعاء، وهو ينطوي على قيمة جديدة كلما كان أي من الناشرين يبرز:
Publishers.CombineLatest(usernamePublisher, passwordPublisher)
.map { username, password in
return username.count > 0 && password.count > 0
}
.assign(to: \.isEnabled, on: loginButton)
Zip
قيم العيادات من ناشرين متعددين في انتظار أن يُظهر جميع الناشرين القيمة التالية قبل أن يُطلقوا على الزوج:
Publishers.Zip(emailsPublisher, namesPublisher)
.sink { email, name in
print("\(name): \(email)")
}
رقيب
تتبادل القيم من ناشرين متعددين من نفس النوع إلى مجرى واحد:
Publishers.Merge(leftPublisher, rightPublisher)
.sink { print($0) }
المجموعة
يُعدّل القيم إلى صفائف، ويُستهلّد عندما يُملأ الحاجز أو يُكمل التيار:
publisher
.collect(3) // emit arrays of 3 values
.sink { print($0) }
Debounce and Throttle
تنتظر وقفاً محدداً بين القيم قبل أن تُحدث آخرها. ]
textField.textPublisher
.debounce(for: .seconds(0.3), scheduler: RunLoop.main)
.sink { text in
// Perform search after user stops typing
}
معالجة الرعب في كومبين
ويمكن أن تفشل خطوط أنابيب التكوين، ويحدد نوع ] من الناشرين الأخطاء التي يمكن أن تنجم عن ذلك، وعندما يفشل الناشر، يتم إنهاء الاشتراك في المشروع، واستخدام مشغلي معالجة الأخطاء في استرداد الأخطاء أو تحويلها.
محاولة قتل ومحاولة
إذا كان إغلاق التحويل الخاص بك يمكن أن يلقي، أو يستخدم أو .
publisher
.tryMap { value in
guard value > 0 else { throw InputError.invalid }
return value * 2
}
Catch
يستعاض عن الفشل بناشر بديل، مفيد لتوفير قيم التراجع:
publisher
.catch { error in
return Just(0) // fallback value
}
Retry
:: إعادة تسجيل الناشرين في الموقع الشبكي لعدد محدد من المرات إذا لم يفعل ذلك:
publisher
.retry(3)
.sink(receiveCompletion: { ... }, receiveValue: { ... })
استبدال الخطأ والاستعاضة عن
يحل محل الفشل ذي قيمة معينة. يوفر قيمة إذا أكمل الناشر دون أن يُحدث أي شيء.
إدارة الذاكرة وإلغاءها
وتستهلك كل اشتراكات الموارد ويجب إلغاؤها على النحو المناسب لتجنب تسرب الذاكرة، وتوفر اللجنة إشارة قابلة للإلغاء من نوع إلى آخر.
(أ) المبالغ القابلة للإلغاء في مجموعة، عادة ما تكون ]، التي تُعالج عندما يُلغى الجسم المُمتلك (مثل مُراقبة النظر) مثلاً:
private var cancellables = Set<AnyCancellable>()
func setupBindings() {
publisher
.sink { [weak self] value in
self?.updateUI(value)
}
.store(in: &cancellables)
}
ويمكنك أيضاً أن تدعو يدوياً إلى إلغاء الطلب، الذي ينهي الاشتراك ويُطلق الموارد.
المقتطف العملي الكامل: طلب الشبكة مع تحديثات نظام المعلومات المتكامل
فلنبني سيناريو عالم حقيقي: جلب بيانات المستخدمين من نظام تقييم الأداء، وفك رموزه، وتحديث وجهة نظر نظام سويفت - يو - أي، وهذا المثال يدل على وجود خطأ في التعامل مع المشغلين وإدارة الخيوط.
import SwiftUI
import Combine
struct User: Decodable, Identifiable {
let id: Int
let name: String
let email: String
}
class UserViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
@Published var errorMessage: String? = nil
private var cancellables = Set<AnyCancellable>()
func fetchUsers() {
isLoading = true
errorMessage = nil
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else {
errorMessage = "Invalid URL"
isLoading = false
return
}
URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: [User].self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { [weak self] completion in
self?.isLoading = false
switch completion {
case .finished:
break
case .failure(let error):
self?.errorMessage = error.localizedDescription
}
}, receiveValue: { [weak self] users in
self?.users = users
})
.store(in: &cancellables)
}
}
النقاط الرئيسية:
- يستخرج البيانات الخام من وعاء الاستجابة.
- تستخدم شركة Combine ' s built-in operator to decode JSON. If decoding fails, the pipeline endeds with an mistake.
- كفل تحديثات UI يحدث على الخيط الرئيسي.
- Storing the involvement in ties its life cycle to the view model.
- ويظهر الخطأ عن طريق الممتلكات التي يمكن أن تكون ملزمة بالتنبيه في سويفتو.
Combine and SwiftUI: A Natural Partnership
ويعتمد هيكل رد الفعل في شركة سويفتو على اللجنة اعتماداً شديداً، فالعمليات المشروحة بـ [(FLT:72]]] تنشئ ناشرين يخطرون سويفتو تلقائياً عند تغييرهم، ويسمحون لك مشغلو الكوكب بتحويل البيانات المتناظرة قبل أن تصل إلى وجهة النظر، مثلاً، يمكنكم أن تنزعوا مدخلات ميدانية، ثم تحققوا نتائج بحثية وتستكملوا المعلومات المتعلقة بالشركة.
ويقضي استخدام الكومبين مع سويفتوئي على الحاجة إلى أنماط يدوية للكميات أو المندوبين، مما يؤدي إلى وضع مدونة أكثر قابلية للتنبؤ بها وقابلية للاختبار.
Combine vs. Other Reactive Frameworks
وقبل أن تستخدم شركات تطوير نظم المعلومات الإلكترونية في كثير من الأحيان أطراً للأطراف الثالثة مثل RxSwift أو .() وتمنح الشركة عدة مزايا: التكامل بين الأطراف الأولى، وعدم إدارة التبعية، والقابلية للتشغيل المتبادل دون سابق إنذار، والخيارات الجديدة المتعلقة بالنظم الإيكولوجية، والنموذج الأكبر.
أفضل الممارسات لاستخدام الكومبين
- Keep pipelines simple] – Complex chains can be difficult to debug. Break them into smaller, named publishers if needed.
- Manage subscriptions carefully] — always store repeallables and avoid retain cycles by using in closures that capture .
- Usese appropriate schedulers] – Use ] to shift to the main queue for UI updates, and ] to perform heavy work on background queues.
- Prefer ] for simple property bindings] — it reduces boilerplate code.
- testing your pipelines] — Combine’s testability is excellent because you can create publishers with or and verify outputs using expectations.
- Beware of backpressure] – Combine’s demand-driven system ensures participantrs control the flow. Operators like or ] can help when data arrives faster than it can be processed.
خاتمة
ويحدث إطار عمل اللجنة ثورة في كيفية تعامل مطوري خدمات تكنولوجيا المعلومات مع الأحداث غير المتزامنة ومجاري البيانات، ويمكنك، من خلال فهم الناشرين والمشتركين والمشغلين، أن تبني خطوط أنابيب مختصرة ومتفاعلة يسهل فهمها والحفاظ عليها، وأن تبدأ بأمثلة بسيطة مثل الاستجابة للإخطارات أو جلب بيانات الشبكة، ثم تستكشف تدريجياً جهات مشغلة متقدمة مثل ، [الخطأ في الاسترداد].
For further reading, consult the official Apple Combine documentation, Hacking with Swift’s Combine introduction, or Ray Wenderlich’s Combine book for deeper dives.