NotificationCenter — یک مکانیزم سیستمی iOS برای ارسال و دریافت اعلانها بین مؤلفههای برنامه بدون ارتباط مستقیم بین فرستنده و گیرنده است. NotificationCenter مبتنی بر الگوی Observer، به اشیا امکان اشتراک در رویدادها و واکنش ناهمگام به آنها را میدهد. طبق Apple Documentation (2025)، NSNotificationCenter از ارسال همگام اعلانها از طریق post(name:object:) و همچنین ارسال تأخیری از طریق NotificationQueue پشتیبانی میکند. مرکز اعلان در چارچوب یک فرآیند کار میکند و از مرزهای برنامه عبور نمیکند.
نکات اصلی
NotificationCenter (NSNotificationCenter) — یک مکانیزم داخلی iOS برای پیادهسازی ارتباط ضعیف بین اشیا است. الگوی Observer به یک شی (فرستنده) اجازه میدهد بدون مرجع مستقیم، بسیاری از اشیای دیگر (ناظران) را از وقوع یک رویداد مطلع کند. NotificationCenter با سه موجودیت کار میکند: Notification.Name (شناسه اعلان)، Notification (ظرف حاوی داده) و NotificationCenter (توزیعکننده). هر برنامه یک default center مشترک دارد.
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 API نیاز به حذف دستی ندارد، اما selector-based همچنان به removeObserver نیاز دارد.
// اشتراک در اعلان (selector-based)
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-based، 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 یک جدول نگاشت (name ← مجموعه ناظران) نگهداری میکند. وقتی فرستنده post(name:object:) را فراخوانی میکند، مرکز اعلان به صورت همگام همه ناظران مشترک در این نام را پیمایش کرده و selector یا بلوکهای آنها را فراخوانی میکند. ویژگی کلیدی: post نخ فعلی را تا پایان همه handlerها مسدود میکند. اگر handlerها عملیات سنگین انجام دهند، این کار فرستنده را به تأخیر میاندازد. NotificationQueue این مشکل را با تأخیر در تحویل اعلانها حل میکند.
متد post(name:object:userInfo:) اعلان را بلافاصله به همه ناظران ارسال میکند. فراخوانی همگام است — کد بعد از post فقط پس از اتمام همه handlerها اجرا میشود. ترتیب فراخوانی ناظران تضمین نشده و ممکن است بین اجراها تغییر کند. برای پردازش ترتیبی از NotificationQueue با coalescing استفاده کنید. post را در داخل handler همان اعلان فراخوانی نکنید — این منجر به بازگشت بینهایت میشود.
NotificationQueue اعلانها را برای تحویل ناهمگام به صف اضافه میکند. از coalescing (ادغام اعلانهای یکسان) و انتخاب صف تحویل (asap، idle، modal) پشتیبانی میکند. Coalescing برای رویدادهای مکرر (وضعیت بارگذاری) مفید است، زمانی که فقط باید با آخرین مقدار اعلان داد. NotificationQueue از run loop برای فعال شدن استفاده میکند، بنابراین فقط در نخهای با run loop فعال کار میکند.
// ارسال تأخیری از طریق 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 برای رویدادهایی ایدهآل است که چندین مؤلفه مستقل باید به آنها واکنش نشان دهند. مثالها: تغییر تنظیمات برنامه، خروج کاربر از حساب، دریافت اعلان push در پسزمینه. NotificationCenter همچنین برای ماژولهای ضعیف مناسب است (ویژگی A نباید از ویژگی B مطلع باشد). نقطه ضعف — عدم امنیت نوع: کلیدهای userInfo رشتهای هستند، نه enum.
Delegate را برای رابطه یک-به-یک با قرارداد واضح انتخاب کنید (tableView.delegate). Delegate سریعتر و از نظر نوع امنتر است. KVO را برای مشاهده تغییر یک ویژگی خاص مدل انتخاب کنید (isLoading، progress). KVO نیاز به ارثبری از NSObject دارد و ممکن است در اشکالزدایی مشکل ایجاد کند (رشتههای جادویی کلید). در Swift مدرن، Combine و async sequences هر سه رویکرد را جایگزین میکنند.
متد addObserver از دو نوع اشتراک پشتیبانی میکند: selector-based (سنتی) و block-based (با closure). Selector-based نیاز به سازگاری @objc و حذف دستی ناظر دارد. Block-based (iOS 9+) امکان استفاده از capture list را میدهد و هنگام استفاده از بلوکهای بدون ارجاع قوی، به طور خودکار توسط OS مدیریت میشود. Block-based همچنین از queue پشتیبانی میکند — ناظر اعلان را در صف مشخص شده دریافت میکند.
روش سنتی اشتراک از طریق selector. متد handler باید با @objc مشخص شده و یک Notification اختیاری دریافت کند. مزیت: قابلیت استفاده توسط هر کلاسی، از جمله legacy Objective-C. معایب: عدم امنیت نوع selector، خطر اشتباه تایپی در نام selector، removeObserver اجباری در deinit. اگر ناظر زودتر از شی حذف شود، handler فراخوانی نخواهد شد.
Block-based API یک closure میپذیرد که هنگام دریافت اعلان اجرا میشود. پارامتر queue مشخص میکند که بلوک در کدام صف اجرا شود — main queue برای بهروزرسانی UI یا background 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. اگر ناظر قبل از deallocation حذف نشود، هنگام ارسال اعلان مرکز سعی میکند متد شیء آزاد شده را فراخوانی کند که منجر به EXC_BAD_ACCESS میشود. از iOS 9 به بعد، block-based addObserver از ارجاعات ضعیف استفاده میکند، اما selector-based همچنان به removeObserver دستی نیاز دارد. Best practice: حذف ناظر در deinit.
Selector-based: حتماً NotificationCenter.default.removeObserver(self) را در deinit فراخوانی کنید. اگر ناظر در چند اعلان مشترک است، میتوان همه را یکجا (بدون پارامتر) یا یک مورد خاص را با نام حذف کرد. Block-based: از طریق removeObserver با token دریافت شده از 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 مدیریت ناظران را خودکار میکند. هنگام اشتراک، یک شی token (NSObjectProtocol) بازگردانده میشود که در زمان deallocation به طور خودکار ناظر را حذف میکند. NotificationTokenWrapper یک ارجاع ضعیف به NotificationCenter و token ناظر نگه میدارد و removeObserver را در deinit فراخوانی میکند. این کار NotificationCenter را به رویکرد Combine نزدیک میکند، جایی که AnyCancellable چرخه عمر اشتراک را مدیریت میکند.
Thread safety NotificationCenter تضمین میکند که post میتواند از هر نخی فراخوانی شود و همه ناظران اعلان را در همان نخی که post فراخوانی شده دریافت میکنند. این برای برنامههای چندنخی حیاتی است: اگر اعلان از نخ پسزمینه ارسال شود، handlerها نیز در نخ پسزمینه اجرا میشوند. برای بهروزرسانی UI باید پردازش را از طریق DispatchQueue.main.async به main queue ارسال کرد.
NotificationCenter برای فراخوانیهای post و addObserver از نخهای مختلف ایمن است. همگامسازی داخلی از قفل استفاده میکند، بنابراین postهای مکرر از چند نخ میتوانند contention ایجاد کنند. برای سناریوهای با بار بالا (وضعیت بارگذاری ۱۰۰۰ فایل) از صف اعلان جداگانه یا Combine publisher استفاده کنید. NotificationQueue با postingStyle .now معادل post مستقیم است.
NotificationCenter از Combine publisher از طریق NotificationCenter.default.publisher(for:object:) پشتیبانی میکند. Publisher هر اعلان را به یک رویداد Combine تبدیل میکند که میتوان از طریق map، filter، debounce و throttle تبدیل کرد. این مشکل تحویل همگام را حل میکند: Combine اعلانها را به صورت ناهمگام در Scheduler مشخص شده پردازش میکند. NotificationCenter.publisher — پلی بین مکانیزم legacy و برنامهنویسی واکنشی مدرن است.
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 از هر نخی ایمن است. با این حال handlerها در همان نخی که post فراخوانی شده اجرا میشوند. برای بهروزرسانی UI از queue: .main در block-based addObserver یا DispatchQueue.main.async در داخل handler استفاده کنید. Combine publisher با receive(on:) نیز مشکل نخ را حل میکند.
Selector-based: crash EXC_BAD_ACCESS هنگام ارسال اعلان پس از deallocation ناظر. Block-based (iOS 9+): به دلیل ارجاع ضعیف نشتی وجود ندارد، اما مرکز اعلان همچنان بلوک را در حافظه تا removeObserver صریح نگه میدارد. توصیه میشود همیشه ناظر را در deinit حذف کنید یا از الگوی Token برای مدیریت خودکار استفاده کنید.
NotificationCenter — پخش رویدادهای دلخواه بین مؤلفههای غیرمرتبط. KVO — مشاهده تغییر یک ویژگی خاص یک شی خاص. KVO نیاز به ارثبری NSObject دارد و هنگام تغییر ویژگی از طریق setter به طور خودکار اعلان میدهد. NotificationCenter فقط با فراخوانی صریح post اعلان میدهد. برای مشاهده مدل، KVO یا Combine ترجیح داده میشود.
یک default center برای هر فرآیند برنامه. مراکز اضافی را میتوان از طریق NotificationCenter() ایجاد کرد، اما در عمل از default مشترک استفاده میشود. هر مرکز مستقل کار میکند — post در یکی به ناظران دیگری تحویل داده نمیشود. برای ایزولهسازی ماژولها از فضاهای نام جداگانه Name از طریق نامهای معکوس-دامنه اعلانها استفاده کنید.
تا حدی. Combine ارائهدهنده NotificationCenter.Publisher است که NotificationCenter را در یک جریان واکنشی میپیچد. Combine مشکل همگامی (از طریق receive(on:)) را حل میکند، عملگرهای تبدیل و مدیریت خودکار اشتراک (AnyCancellable) را اضافه میکند. با این حال NotificationCenter برای اعلانهای سیستمی iOS (UIApplication، UIKeyboard) و کد legacy باقی میماند. Combine یک لایه اضافی است، نه جایگزین.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید