Phantom Types and Typed Notification Observers
Using these types, we can create two more wrappers, but this time around
NSFileHandle
’s initializers. We’ll use theRead
andWrite
types, even though theFileHandle
doesn’t contain any values of that type. That’s why theA
parameter is called a phantom type. The types are just there to track permissions.[…]
Trying to call this function on a file-handle that’s open for writing will not even compile. You can use these techniques for important parts of your own code as well, if you have areas where you want the compiler to check errors for you, rather than having the code fail at runtime.
Finally, we can create our observer object. Creating an instance of this object adds an observer to
NSNotificationCenter
, and when it deallocs, it removes itself from the notification center. This allows us to store it as a property on an object, and once the property gets set tonil
(e.g. when the object deallocs), it automatically removes itself from the notification center.
Then they use phantom types to make sure that a given notification is always posted with the proper type of user info object.
6 Comments RSS · Twitter
Sorry, but the whole Swift mania is now going off the deep end. Who in their right mind would use NSFileHandle from Swift? That class uses exceptions for flow control and, for those that weren't paying attention, Swift doesn't support exceptions.
@John I think the technique is sound, but, yes, you shouldn’t literally do this with NSFileHandle
because of exceptions. It’s a mystery to me why Apple never updated that class to use NSError
.
After implementing the typed notification observer pattern in my own project, I was pleasantly surprised to discover it was even more expressive than I initially thought. The original article presented the following example usage with an NSError type:
let globalPanicNotification: Notification<NSError> = Notification(name: "Global panic") postNotification(globalPanicNotification, myError)
Because I'm a total OOP lackey, I refactored that into an instance method for my own use:
globalPanicNotification.post(myError)
But I kept the observer the same as the article, handling the notification through a block:
NotificationObserver(notification: globalPanicNotification) { err in println(err.localizedDescription) }
With all the types checked at compile time, I thought this was pretty boss. Then, I had a use case where I wanted to send multiple objects via notification. I was going to do the standard, write-a-struct-wrapper solution I've done so many times before when I thought, can I do this through tuples?
let tupleNotification: Notification<(String, Double)> = Notification(name: "Tuple Notification")
Cool, it compiles. How about named parameters?
let tupleNotification: Notification<(description: String, value: Double)> = Notification(name: "Tuple Notification")
Nice, still compiles, so now I can just call the post method, and it will only compile if I provide the correct tuple WITH the argument labels:
tupleNotification.post((description: "Tuples rule", value: 5.0))
A little ugly...it won't accept it if I drop extraneous parenthesis, will it?
tupleNotification.post(description: "Tuples rule", value: 5.0)
Yes, it compiles! And how will the observer ingest this? Can I drop the parenthesis there too?
NotificationObserver(notification: tupleNotification) { description, value in println("description: \(description), value: \(value)") }
Totally sweet. So, to really hammer home how awesome this is: You can use this pattern with labeled tuples to define a different set of method input parameters per phantom type but still have all of your method invocations be COMPLETELY VALIDATED AT COMPILE TIME. To me, this is why I put up with all of Swift's growing pains: You simply can't do this safely with Objective-C.
Ugh, should have known all the angle brackets would have been stripped from my comment. I've reformatted my comment as a Gist and it is much more readable: