Skip to content

Commit 87d6db4

Browse files
authored
Merge pull request #2389 from Cyberbeni/nsnotification-refactor
refactored NotificationCenter to use hash based storage for faster filtering
2 parents 19ba0cc + d1d56d5 commit 87d6db4

File tree

1 file changed

+46
-63
lines changed

1 file changed

+46
-63
lines changed

Foundation/NSNotification.swift

Lines changed: 46 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -83,85 +83,56 @@ open class NSNotification: NSObject, NSCopying, NSCoding {
8383
}
8484

8585
private class NSNotificationReceiver : NSObject {
86-
fileprivate weak var object: NSObject?
8786
fileprivate var name: Notification.Name?
8887
fileprivate var block: ((Notification) -> Void)?
8988
fileprivate var sender: AnyObject?
9089
fileprivate var queue: OperationQueue?
9190
}
9291

93-
extension Sequence where Iterator.Element : NSNotificationReceiver {
94-
95-
/// Returns collection of `NSNotificationReceiver`.
96-
///
97-
/// Will return:
98-
/// - elements that property `object` is not equal to `observerToFilter`
99-
/// - elements that property `name` is not equal to parameter `name` if specified.
100-
/// - elements that property `sender` is not equal to parameter `object` if specified.
101-
///
102-
fileprivate func filterOutObserver(_ observerToFilter: AnyObject, name:Notification.Name? = nil, object: Any? = nil) -> [Iterator.Element] {
103-
return self.filter { observer in
104-
105-
let differentObserver = observer.object !== observerToFilter
106-
let nameSpecified = name != nil
107-
let differentName = observer.name != name
108-
let objectSpecified = object != nil
109-
let differentSender = observer.sender !== __SwiftValue.store(object)
110-
111-
return differentObserver || (nameSpecified && differentName) || (objectSpecified && differentSender)
112-
}
113-
}
114-
115-
/// Returns collection of `NSNotificationReceiver`.
116-
///
117-
/// Will return:
118-
/// - elements that property `sender` is `nil` or equals specified parameter `sender`.
119-
/// - elements that property `name` is `nil` or equals specified parameter `name`.
120-
///
121-
fileprivate func observersMatchingName(_ name:Notification.Name? = nil, sender: Any? = nil) -> [Iterator.Element] {
122-
return self.filter { observer in
123-
124-
let emptyName = observer.name == nil
125-
let sameName = observer.name == name
126-
let emptySender = observer.sender == nil
127-
let sameSender = observer.sender === __SwiftValue.store(sender)
128-
129-
return (emptySender || sameSender) && (emptyName || sameName)
130-
}
131-
}
132-
}
133-
13492
private let _defaultCenter: NotificationCenter = NotificationCenter()
13593

13694
open class NotificationCenter: NSObject {
95+
private lazy var _nilIdentifier: ObjectIdentifier = ObjectIdentifier(_observersLock)
96+
private lazy var _nilHashable: AnyHashable = AnyHashable(_nilIdentifier)
13797

138-
private var _observers: [NSNotificationReceiver]
98+
private var _observers: [AnyHashable /* Notification.Name */ : [ObjectIdentifier /* object */ : [ObjectIdentifier /* notification receiver */ : NSNotificationReceiver]]]
13999
private let _observersLock = NSLock()
140100

141101
public required override init() {
142-
_observers = [NSNotificationReceiver]()
102+
_observers = [AnyHashable: [ObjectIdentifier: [ObjectIdentifier: NSNotificationReceiver]]]()
143103
}
144104

145105
open class var `default`: NotificationCenter {
146106
return _defaultCenter
147107
}
148108

149109
open func post(_ notification: Notification) {
110+
let notificationNameIdentifier: AnyHashable = AnyHashable(notification.name)
111+
let senderIdentifier: ObjectIdentifier? = notification.object.map({ ObjectIdentifier(__SwiftValue.store($0)) })
112+
150113

151-
let sendTo = _observersLock.synchronized({
152-
return _observers.observersMatchingName(notification.name, sender: notification.object)
114+
let sendTo: [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values] = _observersLock.synchronized({
115+
var retVal = [Dictionary<ObjectIdentifier, NSNotificationReceiver>.Values]()
116+
(_observers[_nilHashable]?[_nilIdentifier]?.values).map({ retVal.append($0) })
117+
senderIdentifier.flatMap({ _observers[_nilHashable]?[$0]?.values }).map({ retVal.append($0) })
118+
(_observers[notificationNameIdentifier]?[_nilIdentifier]?.values).map({ retVal.append($0) })
119+
senderIdentifier.flatMap({ _observers[notificationNameIdentifier]?[$0]?.values}).map({ retVal.append($0) })
120+
121+
return retVal
153122
})
154123

155-
for observer in sendTo {
156-
guard let block = observer.block else {
157-
continue
158-
}
159-
160-
if let queue = observer.queue, queue != OperationQueue.current {
161-
queue.addOperation { block(notification) }
162-
queue.waitUntilAllOperationsAreFinished()
163-
} else {
164-
block(notification)
124+
sendTo.forEach { observers in
125+
observers.forEach { observer in
126+
guard let block = observer.block else {
127+
return
128+
}
129+
130+
if let queue = observer.queue, queue != OperationQueue.current {
131+
queue.addOperation { block(notification) }
132+
queue.waitUntilAllOperationsAreFinished()
133+
} else {
134+
block(notification)
135+
}
165136
}
166137
}
167138
}
@@ -176,12 +147,23 @@ open class NotificationCenter: NSObject {
176147
}
177148

178149
open func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object: Any?) {
179-
guard let observer = observer as? NSObject else {
150+
guard let observer = observer as? NSNotificationReceiver,
151+
// These 2 parameters would only be useful for removing notifications added by `addObserver:selector:name:object:`
152+
aName == nil || observer.name == aName,
153+
object == nil || observer.sender === __SwiftValue.store(object)
154+
else {
180155
return
181156
}
182157

158+
let notificationNameIdentifier: AnyHashable = observer.name.map { AnyHashable($0) } ?? _nilHashable
159+
let senderIdentifier: ObjectIdentifier = observer.sender.map { ObjectIdentifier($0) } ?? _nilIdentifier
160+
let receiverIdentifier: ObjectIdentifier = ObjectIdentifier(observer)
161+
183162
_observersLock.synchronized({
184-
self._observers = _observers.filterOutObserver(observer, name: aName, object: object)
163+
_observers[notificationNameIdentifier]?[senderIdentifier]?.removeValue(forKey: receiverIdentifier)
164+
if _observers[notificationNameIdentifier]?[senderIdentifier]?.count == 0 {
165+
_observers[notificationNameIdentifier]?.removeValue(forKey: senderIdentifier)
166+
}
185167
})
186168
}
187169

@@ -191,20 +173,21 @@ open class NotificationCenter: NSObject {
191173
}
192174

193175
open func addObserver(forName name: NSNotification.Name?, object obj: Any?, queue: OperationQueue?, using block: @escaping (Notification) -> Void) -> NSObjectProtocol {
194-
let object = NSObject()
195-
196176
let newObserver = NSNotificationReceiver()
197-
newObserver.object = object
198177
newObserver.name = name
199178
newObserver.block = block
200179
newObserver.sender = __SwiftValue.store(obj)
201180
newObserver.queue = queue
181+
182+
let notificationNameIdentifier: AnyHashable = name.map({ AnyHashable($0) }) ?? _nilHashable
183+
let senderIdentifier: ObjectIdentifier = newObserver.sender.map({ ObjectIdentifier($0) }) ?? _nilIdentifier
184+
let receiverIdentifier: ObjectIdentifier = ObjectIdentifier(newObserver)
202185

203186
_observersLock.synchronized({
204-
_observers.append(newObserver)
187+
_observers[notificationNameIdentifier, default: [:]][senderIdentifier, default: [:]][receiverIdentifier] = newObserver
205188
})
206189

207-
return object
190+
return newObserver
208191
}
209192

210193
}

0 commit comments

Comments
 (0)