Last active
September 18, 2018 12:38
-
-
Save ChrisLawther/b28dfb0b12dd0e3a8139a92b9a0243ec to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Custom Notifications, in a Swift-y way | |
// Firstly, to make testing much easier, let's write a protocol to describe what we want | |
// to be able to do with notifications: | |
protocol NotificationSource { | |
func post(_ notification: Notification) | |
func post(name: Notification.Name, object: Any?) | |
func post(name: Notification.Name, object: Any?, userInfo: [AnyHashable: Any]?) | |
func addObserver(forName name: NSNotification.Name?, | |
object obj: Any?, | |
queue: OperationQueue?, | |
using block: @escaping (Notification) -> Swift.Void) -> NSObjectProtocol | |
func removeObserver(_ observer: Any) | |
} | |
// Looks familiar? That's because it describes what NotificationCenter already does, so | |
// we can trivially opt NotificationCenter into conformance and in the future use a mock, | |
// an alternative or a default NotificationCenter interchangeably: | |
extension NotificationCenter: NotificationSource { } | |
// Now, the first thing we need to be able to do is post a notification. Lets make use of | |
// generics and another protocol to make that trivial at the call site: | |
protocol Postable { | |
static var name: Notification.Name { get } | |
} | |
extension NotificationSource { | |
func post<T: Postable>(_ thing: T) { | |
post(name: T.name, object: thing) | |
} | |
} | |
// So now we can post anything that conforms to `Postable`: | |
struct SomeMessage { | |
let payload: String | |
} | |
extension SomeMessage: Postable { | |
static let name = Notification.Name("SomeMessageDidHappen") | |
} | |
// Next, we need to be able to observe these. Borrowing from talk.objc.io, we'll | |
// wrap the token NotificationCenter returns in a self de-registering class: | |
// (see https://talk.objc.io/episodes/S01E27-typed-notifications-part-1) | |
class Token { | |
let token: NSObjectProtocol | |
let center: NotificationSource | |
init(token: NSObjectProtocol, center: NotificationSource) { | |
self.token = token | |
self.center = center | |
} | |
deinit { | |
center.removeObserver(token) | |
} | |
} | |
// The registration then becomes: | |
extension NotificationSource { | |
func addObserver<N: Postable>(using block: @escaping (N) -> ()) -> Token { | |
return Token(token: addObserver(forName: N.name, object: nil, queue: nil, using: { note in | |
block(note.object as! N) | |
}), center: self) | |
} | |
} | |
// And usage is then as simple as: | |
// Arrange to handle the arrival of our `SomeMessage` notification | |
var token:Token? = NotificationCenter.default.addObserver { (msg:SomeMessage) in | |
print("Received notification saying: \(msg.payload)") | |
} | |
// Create one to send | |
let message = SomeMessage("Hello world") | |
// Send it. The block above will report its arrival | |
NotificationCenter.default.post(message) | |
// Deregister our interest | |
token = nil | |
// Send it again. Nothing will receive it | |
NotificationCenter.default.post(message) | |
// NOTE: This isn't applicable for receiving standard system notifications as they | |
// use the `userInfo` property of the `Notification` and we can't immediately/automatically | |
// decode them. See the talk.objc.io video linked above for more on that topic. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment