NotificationCenter هي آلية نظام في iOS لإرسال واستقبال الإشعارات بين مكونات التطبيق دون اتصال مباشر بين المرسل والمستقبل. استناداً إلى نمط Observer، يسمح NotificationCenter للكائنات بالاشتراك في الأحداث والاستجابة لها بشكل غير متزامن. وفقاً لوثائق Apple (2025)، يدعم NSNotificationCenter الإرسال المتزامن للإشعارات عبر post(name:object:) والإرسال المؤجل عبر NotificationQueue. يعمل مركز الإشعارات ضمن عملية واحدة ولا يعبر حدود التطبيقات.
الخلاصة
NotificationCenter (NSNotificationCenter) هي آلية مدمجة في iOS لتنفيذ تواصل ضعيف الاقتران بين الكائنات. يسمح نمط Observer لكائن واحد (مرسل) بإعلام العديد من الكائنات الأخرى (مراقبين) بحدث دون مرجع مباشر إليها. يعمل NotificationCenter مع ثلاث كيانات: Notification.Name (معرف الإشعار)، Notification (حاوية بالبيانات) وNotificationCenter (الموزع). كل تطبيق لديه مركز افتراضي مشترك.
Notification.Name هو هيكل يحدد نوع الإشعار. يتم إنشاؤه عبر extension Name: Notification.Name(“MyNotification”). Notification هو كائن يحتوي على name وobject (المرسل) وuserInfo (قاموس بالبيانات). يتم تعريف إشعارات النظام كثوابت: UIApplication.didBecomeActiveNotification، UIResponder.keyboardWillShowNotification. يجب تجميع الإشعارات المخصصة عبر extension لتجنب تعارض الأسماء. يجب أن تكون الأسماء بنطاق عكسي.
// تعريف الإشعارات المخصصة
extension Notification.Name {
static let dataDidUpdate =
Notification.Name("com.app.dataDidUpdate")
static let userLoggedOut =
Notification.Name("com.app.userLoggedOut")
}
// إرسال إشعار مع بيانات
let userInfo: [String: Any] = [
"userId": 123,
"timestamp": Date()
]
NotificationCenter.default.post(
name: .dataDidUpdate,
object: nil,
userInfo: userInfo
)
يشترك المراقب في إشعار عبر طريقة addObserver(_:selector:name:object:). Selector هو الطريقة التي سيتم استدعاؤها عند استلام الإشعار. تسمح معلمة object بتصفية الإشعارات من مرسل محدد. إذا كان object يساوي nil، يتلقى المراقب جميع الإشعارات بالاسم المحدد من أي مرسل. منذ iOS 9، لا يتطلب addObserver الإزالة اليدوية لواجهة block-based، لكن selector-based لا يزال يتطلب removeObserver.
// الاشتراك في إشعار (قائم على selector)
NotificationCenter.default.addObserver(
self,
selector: #selector(handleDataUpdate),
name: .dataDidUpdate,
object: nil
)
@objc func handleDataUpdate(_ notification: Notification) {
guard let userId = notification.userInfo?["userId"] as? Int else { return }
updateUI(for: userId)
}
// الاشتراك في إشعار (قائم على block، iOS 9+)
var observer: NSObjectProtocol?
observer = NotificationCenter.default.addObserver(
forName: .dataDidUpdate,
object: nil,
queue: .main
) { [weak self] notification in
guard let self else { return }
self.handleNotification(notification)
}
يخزن NotificationCenter جدول تعيين (اسم → مجموعة مراقبين). عندما يستدعي المرسل post(name:object:)، يجتاز مركز الإشعارات بشكل متزامن جميع المراقبين المشتركين بهذا الاسم ويستدعي selectors أو blocks الخاصة بهم. ميزة رئيسية: post يحجب الخيط الحالي حتى اكتمال جميع المعالجات. إذا نفذت المعالجات عمليات ثقيلة، فإن هذا يؤخر المرسل. يحل NotificationQueue هذه المشكلة بتأجيل تسليم الإشعارات.
ترسل طريقة post(name:object:userInfo:) إشعاراً فورياً لجميع المراقبين. الاستدعاء متزامن — يتم تنفيذ الكود بعد post فقط بعد اكتمال جميع المعالجات. ترتيب استدعاء المراقبين غير مضمون وقد يتغير بين عمليات التشغيل. للمعالجة التسلسلية، استخدم NotificationQueue مع coalescing. لا تستدع post داخل معالج لنفس الإشعار — يؤدي هذا إلى تكرار لا نهائي.
NotificationQueue يضيف إشعارات إلى قائمة انتظار للتسليم غير المتزامن. يدعم coalescing (دمج الإشعارات المتطابقة) واختيار قائمة انتظار التسليم (asap، idle، modal). يعتبر coalescing مفيداً للأحداث المتكررة (تقدم التنزيل) عندما تحتاج فقط إلى الإعلام بالقيمة الأخيرة. يستخدم NotificationQueue حلقة التشغيل للتفعيل، لذلك يعمل فقط في الخيوط ذات حلقة تشغيل نشطة.
// الإرسال المؤجل عبر NotificationQueue
let notification = Notification(
name: .dataDidUpdate,
object: self,
userInfo: ["progress": 0.5]
)
// Coalescing: دمج إشعارات متعددة في إشعار واحد
NotificationQueue.default.enqueue(
notification,
postingStyle: .whenIdle,
coalesceMask: .onName,
forModes: [.common]
)
// التسليم غير المتزامن عبر DispatchQueue
DispatchQueue.main.async {
NotificationCenter.default.post(name: .dataDidUpdate, object: nil)
}
يوفر iOS ثلاث آليات رئيسية للتواصل بين الكائنات: NotificationCenter وDelegate وKVO (Key-Value Observing). كل منها يحل مشكلة الإعلام ولكن بمقايضات مختلفة في الاقتران والأداء وأمان الأنواع. يعتمد اختيار الآلية على علاقة واحد-إلى-واحد أو واحد-إلى-متعدد والحاجة إلى نقل البيانات.
| الخاصية | NotificationCenter | Delegate | KVO |
|---|---|---|---|
| الاقتران | ضعيف (اسم الإشعار) | قوي (بروتوكول) | متوسط (مفتاح) |
| العلاقة | واحد-إلى-متعدد | واحد-إلى-واحد | واحد-إلى-متعدد |
| أمان الأنواع | منخفض (userInfo كـ Dictionary) | عالي (طرق البروتوكول) | متوسط (Any?) |
| الأداء | متوسط (اجتياز الجدول) | عالي (استدعاء مباشر) | منخفض (NSObject) |
| عدم التزامن | متزامن (post يحجب) | متزامن في خيط المرسل | متزامن عند التغيير |
NotificationCenter مثالي للأحداث التي يجب أن تستجيب لها مكونات مستقلة متعددة. أمثلة: تغييرات إعدادات التطبيق، تسجيل خروج المستخدم، استلام إشعار دفع في الخلفية. NotificationCenter مناسب أيضاً للوحدات ضعيفة الاقتران (الوحدة A لا يجب أن تعرف عن الوحدة B). العيب هو نقص أمان الأنواع: مفاتيح userInfo هي سلاسل نصية وليست تعدادات.
Delegate هو الخيار للعلاقات واحد-إلى-واحد بعقد واضح (tableView.delegate). Delegate أسرع وأكثر أماناً من حيث الأنواع. KVO هو الخيار لمراقبة تغييرات خاصية محددة للنموذج (isLoading، progress). يتطلب KVO وراثة من NSObject وقد يسبب صعوبات في التصحيح (سلاسل مفاتيح سحرية). في Swift الحديث، تحل Combine وتسلسلات async محل جميع المناهج الثلاثة.
تدعم طريقة addObserver نوعين من الاشتراك: selector-based (تقليدي) وblock-based (مع إغلاق). Selector-based يتطلب توافق @objc وإزالة يدوية للمراقب. Block-based (iOS 9+) يسمح باستخدام capture list وتتم إدارته تلقائياً بواسطة النظام عند استخدام الكتل بدون مراجع قوية. يدعم block-based أيضاً queue — يتلقى المراقب الإشعار في قائمة الانتظار المحددة.
الطريقة التقليدية للاشتراك عبر selector. يجب أن تكون طريقة المعالج محددة بـ @objc وتقبل Notification اختيارياً. الميزة: يمكن استخدامها من قبل أي فئة بما في ذلك Objective-C القديم. العيوب: نقص أمان الأنواع للـ selector، خطر الأخطاء المطبعية في اسم selector، removeObserver إلزامي في deinit. إذا تمت إزالة المراقب قبل الكائن، لن يتم استدعاء المعالج.
تقبل واجهة block-based إغلاقاً يتم تنفيذه عند استلام الإشعار. معلمة queue تحدد في أي قائمة انتظار يتم تنفيذ الكتلة — القائمة الرئيسية لتحديثات واجهة المستخدم أو قائمة خلفية لمعالجة البيانات. تُستخدم قيمة الإرجاع NSObjectProtocol لإزالة المراقب: NotificationCenter.default.removeObserver(observer). يُفضل block-based في Swift الحديث.
protocol NotificationToken {
func dispose()
}
extension NotificationCenter {
func observe(
name: NSNotification.Name,
object: Any? = nil,
queue: OperationQueue? = .main,
using block: @escaping (Notification) -> Void
) -> NotificationToken {
let observer = addObserver(forName: name, object: object,
queue: queue, using: block)
return NotificationTokenWrapper(observer: observer, center: self)
}
}
// الاستخدام مع الإزالة التلقائية
class ViewModel {
private var tokens: [NotificationToken] = []
func startObserving() {
let token = NotificationCenter.default.observe(
name: .dataDidUpdate,
queue: .main
) { [weak self] notification in
self?.handleUpdate(notification)
}
tokens.append(token)
}
deinit {
tokens.forEach { $0.dispose() }
}
}
تسرب الذاكرة هو أحد المشكلات الرئيسية عند العمل مع NotificationCenter. إذا لم تتم إزالة المراقب قبل إلغاء التخصيص، عند إرسال إشعار سيحاول المركز استدعاء طريقة على كائن تم إلغاء تخصيصه بالفعل، مما يؤدي إلى EXC_BAD_ACCESS. منذ iOS 9، يستخدم block-based addObserver مراجع ضعيفة، لكن selector-based لا يزال يتطلب removeObserver يدوي. أفضل ممارسة: إزالة المراقب في deinit.
Selector-based: استدع دائماً NotificationCenter.default.removeObserver(self) في deinit. إذا كان المراقب مشتركاً في إشعارات متعددة، يمكنك إزالة الكل مرة واحدة (بدون معلمات) أو إشعار محدد بالاسم. Block-based: قم بالإزالة عبر removeObserver مع التوكن الذي تم إرجاعه من addObserver. بالنسبة لـ block-based على iOS 9+، لا يحدث تسرب، لكن الإزالة لا تزال موصى بها للأداء: لن يتم اجتياز المراقبين الملغى تخصيصهم أثناء post.
class SafeObserver {
private var observers: [NSObjectProtocol] = []
func addSubscriptions() {
let token1 = NotificationCenter.default.addObserver(
forName: .dataDidUpdate, object: nil,
queue: .main) { [weak self] _ in
self?.refreshData()
}
let token2 = NotificationCenter.default.addObserver(
forName: .userLoggedOut, object: nil,
queue: .main) { [weak self] _ in
self?.logout()
}
observers.append(contentsOf: [token1, token2])
}
deinit {
observers.forEach { NotificationCenter.default.removeObserver($0) }
}
private func refreshData() { }
private func logout() { }
}
يقوم نمط Token بأتمتة إدارة المراقبين. عند الاشتراك، يتم إرجاع كائن توكن (NSObjectProtocol) يقوم تلقائياً بإزالة المراقب عند إلغاء التخصيص. NotificationTokenWrapper يخزن مرجعاً ضعيفاً لـ NotificationCenter وتوكن المراقب، مستدعياً removeObserver في deinit. هذا يقرب NotificationCenter من نهج Combine، حيث يدير AnyCancellable دورة حياة الاشتراك.
أمان الخيوط: يضمن NotificationCenter أنه يمكن استدعاء post من أي خيط، وسيتلقى جميع المراقبين الإشعار في نفس الخيط حيث تم استدعاء post. هذا أمر بالغ الأهمية للتطبيقات متعددة الخيوط: إذا تم إرسال إشعار من خيط خلفية، فسيتم تنفيذ المعالجات أيضاً في خيط الخلفية. لتحديثات واجهة المستخدم، وجه المعالجة إلى قائمة الانتظار الرئيسية عبر DispatchQueue.main.async.
NotificationCenter آمن للخيوط لاستدعاءات post وaddObserver من خيوط مختلفة. المزامنة الداخلية تستخدم الأقفال، لذلك يمكن أن تخلق الاستدعاءات المتكررة من خيوط متعددة تنافساً. لسيناريوهات الحمل العالي (تقدم تنزيل 1000 ملف)، استخدم قائمة انتظار إشعارات منفصلة أو publisher Combine. NotificationQueue مع postingStyle .now يعادل post المباشر.
يدعم NotificationCenter publisher Combine عبر NotificationCenter.default.publisher(for:object:). Publisher يحول كل إشعار إلى حدث Combine يمكن تحويله عبر map وfilter وdebounce وthrottle. هذا يحل مشكلة التسليم المتزامن: يعالج Combine الإشعارات بشكل غير متزامن على المجدول المحدد. NotificationCenter.publisher هو جسر بين الآلية القديمة والبرمجة التفاعلية الحديثة.
import Combine
class ReactiveViewModel {
private var cancellables = Set<AnyCancellable>()
func setupCombineSubscription() {
NotificationCenter.default
.publisher(for: .dataDidUpdate)
.receive(on: DispatchQueue.main)
.compactMap { $0.userInfo?["progress"] as? Float }
.debounce(for: .seconds(0.3), scheduler: RunLoop.main)
.sink { [weak self] progress in
self?.progressLabel.text = "\(Int(progress * 100))%"
}
.store(in: &cancellables)
}
}
الأسئلة الشائعة
نعم، NotificationCenter آمن للخيوط لاستدعاءات post وaddObserver من أي خيط. ومع ذلك، يتم تنفيذ المعالجات في نفس الخيط حيث تم استدعاء post. لتحديثات واجهة المستخدم، استخدم queue: .main في block-based addObserver أو DispatchQueue.main.async داخل المعالج. publisher Combine مع receive(on:) يحل أيضاً مشكلة الخيط.
Selector-based: تعطل EXC_BAD_ACCESS عند إرسال إشعار بعد إلغاء تخصيص المراقب. Block-based (iOS 9+): لا يوجد تسرب بفضل المرجع الضعيف، لكن مركز الإشعارات يستمر في الاحتفاظ بالكتلة في الذاكرة حتى removeObserver صريح. يوصى دائماً بإزالة المراقب في deinit أو استخدام نمط Token للإدارة التلقائية.
NotificationCenter — آلية بث لأحداث عشوائية بين مكونات غير مرتبطة. KVO — مراقبة تغييرات خاصية محددة لكائن محدد. يتطلب KVO وراثة NSObject ويُعلم تلقائياً عند تغيير الخاصية عبر setter. يُعلم NotificationCenter فقط عند استدعاء post صريح. لمراقبة النموذج، يُفضل KVO أو Combine.
مركز افتراضي واحد لكل عملية تطبيق. يمكن إنشاء مراكز إضافية عبر NotificationCenter()، لكن عملياً يتم استخدام default المشترك. كل مركز يعمل بشكل مستقل — post في واحد لا يُسلم لمراقبي الآخر. لعزل الوحدات، استخدم مساحات أسماء Name منفصلة عبر أسماء إشعارات بنطاق عكسي.
جزئياً. يوفر Combine NotificationCenter.Publisher الذي يغلف NotificationCenter في تدفق تفاعلي. يحل Combine مشكلة التزامن (عبر receive(on:))، ويضيف عوامل تحويل وإدارة تلقائية للاشتراكات (AnyCancellable). ومع ذلك، يبقى NotificationCenter لإشعارات نظام iOS (UIApplication، UIKeyboard) والكود القديم. Combine هو تحسين وليس استبدالاً.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.