Reading plists into a typed Dictionary
A question was asked over on iOS Developers about a ‘swifty’ way of reading a plist into a Dictionary so I decided to do a little research. Currently there are two ways to read in plist data.
Lets have a look at each method and how they are used.
NSPropertyListSerialization
This class works with plist data in a manner that’s similar to how NSJSONSerialization works with JSON. You pass it a NSData object and, if successful, it gives you back an AnyObject which you can then try to cast to NSDictionary.
While fairly simple this is the more verbose means of reading a plist but only because of the little bit of extra work due to the do/catch.
NSDictionary(contentsOfFile:)
NSDictionary has a failable initialiser that accepts a file name. If the provided file exists and the contents can be represented as a dictionary you will get an NSDictionary.
This is a much more concise way to get at the plist data and we don’t have to deal with any do/catch so we will go with this approach for our final implementation.
Goal
We are going to make a new initialiser for Dictionary that accepts a plist file name. As such our final implementation will use generics/associated types so that it’s flexible enough that if we only want String or Int values we can get back a [String: String] or [String: Int]. This way we don’t have to cast values from AnyObject if we don’t want to.
Issues
There is a small hiccup however: both of these methods for reading a plist result in an NSDictionary and we cannot simply add a generic cast from this to a swift Dictionary. To illustrate this here is a simple function that represents what are are trying to achieve, it has the same characteristics as a function in a Dictionary extension:
The constraints and outputs match the requirements for a swift Dictionary so why the warning? Well it’s the input that is the source of the problem, let’s have a quick look at what’s happening…
Bridging Recap
Swift bridges NSDictionary as [NSObject: AnyObject] (unless the NSDictionary provides parameterised types) from there you can attempt to downcast from [NSObject: AnyObject] to a more specific type like our [Key: Value]
The problem here is that Key can be of the type Any but NSObject is bridged as AnyObject; therefore since the compiler cannot guarantee that this cast will ever succeed we get the warning.
Resolution
Unfortunately the fix for this issue requires us to iterate over the key/value pairs from the NSDictionary and attempt to cast them into the types we want like so:
It’s not as ‘swifty’ as we might like, but at least its hidden away inside a function that is more ‘swifty’.
Solution
Putting everything we now know together this is the solution I landed on for importing plist data into a swift Dictionary:
Now we have a nice way of accessing plist data in a type safe manner. It’s worth noting that a side-effect of the workaround here is that the data will also be filtered based on the types you supply. So if you ask for a [String: Int] any values that are not Int will be filtered out.
If you have any feedback I’d love to hear about it — You can usually find me over at iOS Developers or on Twitter.