text
NotificationCenter is a system component that coordinates and manages notifications and responses to events. Its basic principle is based onObserver mode! Apple is closed source for it, so it cannot view the source code of NotificationCenter, but it can understand the implementation of NotificationCenter by analyzing open source Swift. The following is a simplified implementation:
Simple implementation1. First define a NotificationCenter class definition
class RYNotificationCenter {
private init(){}
static let `default` = RYNotificationCenter()
private var observers: [RYNotificationObserver] = []
}
class RYNotificationCenter { private init(){} static let `default` = RYNotificationCenter() private var observers: [RYNotificationObserver] = [] }
Define a singleton for sharing throughout the program,observers
Arrays are used to store all registered observers.
2. Then define an observer object
Observer objects are used to encapsulate specific observers' information.
class RYNotificationObserver { var name: String var block: (Notification) -> Void init(name: String, block: @escaping (Notification) -> Void) { = name = block } }
3. Methods to add registered observers in NotificationCenter
func addObserver(name: String, block: @escaping (Notification) -> Void) -> RYNotificationObserver { let observer = RYNotificationObserver(name: name, block: block) (observer) return observer }
addObserver
Methods are used to register observers. In this implementation, we create a new oneRYNotificationObserver
and add the object toobservers
Array. This method returns the observer object so that it can be laterNotificationCenter
Removed in .
4. Add a method to send notifications in NotificationCenter
/// The essence of sending notifications is to use the observer mode/// Let the observer array execute the code in the closurefunc post(name: String, userInfo: [AnyHashable: Any]? = nil) { let notification = Notification(name: (name), userInfo: userInfo) observers .filter({ $ == name }) .forEach { $(notification) } }
post
Method is used to send notifications, which accepts notification names and optionallyuserInfo
dictionary. At the same time, the parameters are packaged inNotification
In the object, then traverseobservers
Array. If the observer's name matches the notification name, we will perform the savedblock
。
5. Add a method to remove notifiers in NotificationCenter
func removeObserver(_ observer: RYNotificationObserver) { if let index = (where: { $0 === observer }) { (at: index) } }
removeObserver
Methods are used to remove observers. It accepts an observer object and fromobservers
Remove it from the array.
Source code analysis of NotificationCenter
Generally speaking, now analyzeNotificationCenter
The source code is usually/gnustep/lib…, This is in the source code of the gnustep library. It is definitely different from the official specific implementation, but it can be used as a reference object. The source code notified here uses three main classes:
- NSNotification
- NSNotificationCenter
- NSNotificationQueue
NSNotificationCenter Implementation
Used to send notifications between observers and senders, this is the core class, its method is consistent with Objective-C, using**addObserver:selector:name:object:
Method to add observers, but it uses C language to implement the data structure of linked lists internally.Obs
Store observer-related information:
typedef struct Obs { id observer; /* Object to receive message. */ SEL selector; /* Method selector. */ struct Obs *next; /* Next item in linked list. */ int retained; /* Retain count for structure. */ struct NCTbl *link; /* Pointer back to chunk table */ } Observation;
And inpostNotificationName:object:userInfo:
When the method is executed, the encapsulated version will be found through the notification name.Obs
Observer, then perform the corresponding method:
- (void) postNotificationName: (NSString*)name object: (id)object userInfo: (NSDictionary*)info { // Encapsulate notification first GSNotification *notification; notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone()); notification->_name = [name copyWithZone: [self zone]]; notification->_object = [object retain]; notification->_info = [info retain]; [self _postAndRelease: notification]; } // Then call the observer's selector method- (void) _postAndRealse: (NSNotification*)notification { ...... [o->observer performSelector: o->selector withObject: notification]; ...... }
Of course, the packagednotification
, passed as a parameter to the observer to performselector
。
NSNotification implementation
So what about Notifiation? It is an immutable object containing the name of the notification, the sender object, and the user information dictionary.
- (id) initWithCoder: (NSCoder*)aCoder { NSString *name; id object; NSDictionary *info; id n; [aCoder decodeValueOfObjCType: @encode(id) at: &name]; [aCoder decodeValueOfObjCType: @encode(id) at: &object]; [aCoder decodeValueOfObjCType: @encode(id) at: &info]; n = [NSNotification notificationWithName: name object: object userInfo: info]; RELEASE(name); RELEASE(object); RELEASE(info); DESTROY(self); return RETAIN(n); }
Implementation of NSNotificationQueue
Finally, there is the implementation of NSNotificationQueue, which is a queue for managing notification sending, which can queue notifications in a specific sending mode (such as merging the same notifications or in order of sending).
- (void) enqueueNotification: (NSNotification*)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask: (NSUInteger)coalesceMask forModes: (NSArray*)modes { if (modes == nil) { modes = defaultMode; } if (coalesceMask != NSNotificationNoCoalescing) { [self dequeueNotificationsMatching: notification coalesceMask: coalesceMask]; } switch (postingStyle) { case NSPostNow: { NSString *mode; mode = [[NSRunLoop currentRunLoop] currentMode]; if (mode == nil || [modes indexOfObject: mode] != NSNotFound) { [_center postNotification: notification]; } } break; case NSPostASAP: add_to_queue(_asapQueue, notification, modes, _zone); break; case NSPostWhenIdle: add_to_queue(_idleQueue, notification, modes, _zone); break; } }
When usingNSNotificationQueue
When we do, we don't need to send Notification manually.NSNotificationQueue
It will automatically send us, in the above code, if it isNSPostNow
, the notification will be sent immediately, otherwise you will be added to the queue first:_asapQueue
or_idleQueue
, and then execute notifications in the queue when appropriate, such as:
void GSPrivateNotifyIdle(NSString *mode) { NotificationQueueList *item; for (item = currentList(); item; item = item->next) { if (item->queue) { notify(item->queue->_center, item->queue->_idleQueue, mode, item->queue->_zone); } } }
Question: IfNotificationCenter
Will the added observer be self, which causes a circular reference?
The answer is:Won't!
NotificationCenter references observers by weak references rather than strong holdings. Therefore, when an object is destroyed, itsdeinit
The method will be called, even if it is an observer. So even if we are not heredeinit
It is also possible to add and remove self in the method, because the NotificationCenter does not force the observer to hold it.
Question: IfNotificationCenter
What is added is block, and block forcefully holds self , will this cause a circular reference?
The answer is:meeting!
Starting from iOS 9, if you use block-based observers, you need to be careful about the observer's life cycle, becauseNotificationCenter
The added block is strongly held. As in the simple implementation above, it is also strongly held on variables captured in the closure, so in order to avoid this phenomenon, it is necessary to ensure that it is used.[weak self]
to capture the list.
In actual use, due to coding inertia, it may bedeinit
Remove block-based observers from the method to solve this problem:
class ViewController: UIViewController { private weak var observer: NSObjectProtocol! func addObserver() { observer = (forName: ("test"), object: nil, queue: ) { _ in = } } deinit { (observer!) } }
But in this case, deinit
The method will not be executed!The reason isNotificationCenter
Holding block, indirectly holding self, andNotificationCenter
It is a singleton, so this holding relationship has always existed, resulting indeinit
The method will not be executed!
Question: Observer'sselector
Is the execution thread related to the thread sending the notification?
The answer is: positive correlation!
The conclusion can basically be seen from the simple implementation above and the source code of GNU. Adding the observer's thread has no effect.The thread sending the notification is actually the thread calling the method execution, so the two are executed on the same thread.
func addObserver() { (self, selector: #selector(click), name: ("test"), object: nil) ().async { (name: ("test"), object: nil) NSLog("curretThread1: \()") } } @objc func click() { NSLog("curretThread2: \()") } // curretThread2: <NSThread: 0x600001358240>{number = 6, name = (null)} // curretThread1: <NSThread: 0x600001358240>{number = 6, name = (null)}
At the same time, it is also necessary to pay attention to the notification.selector
Being executed, this process is essentially an observation mode implementation method. At the same time, it is also executed synchronously. After executing the method of sending the message, it will look for the corresponding one.Observer
, after finding it, execute the correspondingselector
, After the execution, the method of sending the message is completed.
Therefore, the core of the method of sending notifications and listening to notification execution is: execution by the same thread and execution synchronously.
The above is the detailed content of the implementation principle of the NotificationCenter class. For more information about the NotificationCenter principle, please pay attention to my other related articles!