Working with NSNotifciationCetner in Swift is fairly straightforward.
// This class will correctly work because it inherits from
// NSObject and thus automatically can be used by Objective-C.
// It does not use any Swift specific features.
//
class OkExample: NSObject {
override init() {
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handler:", name: "MyNotification", object: nil)
}
func handler(notif: NSNotification) {
println("MyNotification was handled")
}
}
Take for instance the example shown above. This class will work correctly with NSNotificationCenter because the class inherits from NSObject (you can also mark your class with the @objc
).
We can easily call emit this notification by using notification center.
NSNotificationCenter.defaultCenter().postNotificationName("MyNotification", object: nil);
That's all well and good, but what happens if your class uses Swift specific features. Well it won't work. Consider the following:
// This class will not work becuase it users the Swift
// specific feature of Generics. As a result, Objective-C will
// be unable to find the `handler` method resulting in a runtime exception
//
class BadExample<T>: NSObject {
override init() {
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handler:", name: "MyNotification", object: nil)
}
func handler(notif: NSNotification) {
println("MyNotification was handled")
}
}
In Swift and Objective-C in the Same Project it mentions a few caveats that have an impact on how selectors can be used from Objective-C code.
You’ll have access to anything within a class or protocol that’s marked with the @objc attribute as long as it’s compatible with Objective-C. This excludes Swift-only features such as:
- Generics
- Tuples
- Enumerations defined in Swift
- Structures defined in Swift
- Top-level functions defined in Swift
- Global variables defined in Swift
- Typealiases defined in Swift
- Swift-style variadics
- Nested types
- Curried functions
The end result, is that if you attempt to use NSNotificationCenter
2014-12-04 20:56:50.271 iOSExamples-ModelSync[4360:149860] -[_TtC21iOSExamples_ModelSync10BadExample00007F8DB3456710 handler:]: unrecognized selector sent to instance 0x7f8db3455360
2014-12-04 20:56:50.281 iOSExamples-ModelSync[4360:149860] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_TtC21iOSExamples_ModelSync10BadExample00007F8DB3456710 handler:]: unrecognized selector sent to instance 0x7f8db3455360'
*** First throw call stack:
(
0 CoreFoundation 0x000000010eb69f35 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001106adbb7 objc_exception_throw + 45
2 CoreFoundation 0x000000010eb7104d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x000000010eac927c ___forwarding___ + 988
4 CoreFoundation 0x000000010eac8e18 _CF_forwarding_prep_0 + 120
5 CoreFoundation 0x000000010eb39cec __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
6 CoreFoundation 0x000000010ea398a4 _CFXNotificationPost + 2484
7 Foundation 0x000000010ef426b8 -[NSNotificationCenter postNotificationName:object:userInfo:] + 66
8 iOSExamples-ModelSync 0x000000010e971d9e _TFC21iOSExamples_ModelSync11AppDelegate11applicationfS0_FTCSo13UIApplication29didFinishLaunchingWithOptionsGSqGVSs10DictionaryCSo8NSObjectPSs9AnyObject____Sb + 1278
9 iOSExamples-ModelSync 0x000000010e9720b0 _TToFC21iOSExamples_ModelSync11AppDelegate11applicationfS0_FTCSo13UIApplication29didFinishLaunchingWithOptionsGSqGVSs10DictionaryCSo8NSObjectPSs9AnyObject____Sb + 560
10 UIKit 0x000000010f3f0475 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 234
11 UIKit 0x000000010f3f0fbc -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2463
12 UIKit 0x000000010f3f3d2c -[UIApplication _runWithMainScene:transitionContext:completion:] + 1350
13 UIKit 0x000000010f3f2bf2 -[UIApplication workspaceDidEndTransaction:] + 179
14 FrontBoardServices 0x000000011223a2a3 __31-[FBSSerialQueue performAsync:]_block_invoke + 16
15 CoreFoundation 0x000000010ea9f53c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
16 CoreFoundation 0x000000010ea95285 __CFRunLoopDoBlocks + 341
17 CoreFoundation 0x000000010ea95045 __CFRunLoopRun + 2389
18 CoreFoundation 0x000000010ea94486 CFRunLoopRunSpecific + 470
19 UIKit 0x000000010f3f2669 -[UIApplication _run] + 413
20 UIKit 0x000000010f3f5420 UIApplicationMain + 1282
21 iOSExamples-ModelSync 0x000000010e97236e top_level_code + 78
22 iOSExamples-ModelSync 0x000000010e9723aa main + 42
23 libdyld.dylib 0x0000000110e87145 start + 1
24 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
The solution I came up with is to use a proxy class that will listen to NSNotificationCenter events and execute a closure upon observing that notification. The closure, becuase it is part of Swift can execute for any Swift class.
///
/// Provides proxy functionality for NSNotificationCenter
/// events for Swift classes that do not support integration
/// with Objective-C code.
///
@objc class ObserverProxy {
var closure: (NSNotification) -> ();
var name: String;
var object: AnyObject?;
init(name: String, closure: (NSNotification) -> ()) {
self.closure = closure;
self.name = name;
self.start();
}
convenience init(name: String, object: AnyObject, closure: (NSNotification) -> ()) {
self.init(name: name, closure);
self.object = object;
}
deinit {
stop()
}
func start() {
NSNotificationCenter.defaultCenter().addObserver(self, selector:"handler:", name:name, object: object);
}
func stop() {
NSNotificationCenter.defaultCenter().removeObserver(self);
}
func handler(notification: NSNotification) {
closure(notification);
}
}
Then, your Swift class can use this proxy class to listen for events.
// This class uses a proxy class that will listen for the notification.
// When the notification is fired, the proxy class will execute the supplied
// closure... which happens to be the handler on this class. This structure allows
// us to get around the limitation of Objective-C not having access to the methods
// in a Generic class
class ProxyExample<T>: NSObject {
var myNotificationProxy: ObserverProxy?;
override init() {
super.init()
myNotificationProxy = ObserverProxy(name: "MyNotification", closure: handler);
}
func handler(notif: NSNotification) {
println("MyNotification was called")
}
}
This technique could be modified to provide a cleaner syntax to allows more than a single closure to be applied to a single proxy. But this should help you get around limitations with Swift specific features preventing NSNotificationCenter from working.