Swift 4 and iOS 11 brings us the Codable protocol as a way to convert a type to and from an external format. The most popular format may be JSON but it also works great for old school Cocoa property lists.
NSDictionary and NSArray
While I was playing with custom fonts and dynamic type last week I wanted to read a dictionary of settings from a Cocoa property list file. In the old days I might have written something like this in Objective-C:
NSURL *settingsURL = ... // location of plist file
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfURL:settingsURL];
To write the dictionary back to the property list file:
[settings writeToURL:settingsURL atomically:YES];
A similar API exists for reading and writing an NSArray. Both API’s are usable from Swift with some ugly casting between NSDictionary and Dictionary (or NSArray and Array):
let settingsURL: URL = ... // location of plist file
let settings = NSDictionary(contentsOf: settingsURL) as? Dictionary<String, AnyObject>
To write the dictionary to a file you need to cast to NSDictionary:
(settings as NSDictionary).write(to: settingsURL, atomically: true)
This works but does not feel Swift like with all those casts and use of AnyObject.
Using Codable For Property Lists
The Codable protocol is new to Swift in iOS 11 and handles converting a type to and from an external format such as JSON. It also works great for property lists. In fact Apple will deprecate the old NSDictionary and NSArray methods in a future release.
Reading A Property List
For example, a property list which is a dictionary containing a boolean, string and integer:

Define a structure that matches this format and adopt the Codable protocol:
struct MySettings: Codable {
var someFlag: Bool
var someString: String
var someInt: Int
}
Notes:
- The property names match the key names of the items in the property list.
Codableis a typealias forDecodable & Encodable. So if you only want to read the property list file you can just adoptDecodable.- A property list can contain dictionaries, arrays, strings, numbers, dates, binary data and boolean values.
To read/decode the property list file create a PropertyListDecoder object and then use its decode method:
let settingsURL: URL = ... // location of plist file
var settings: MySettings?
if let data = try? Data(contentsOf: settingsURL) {
let decoder = PropertyListDecoder()
settings = try? decoder.decode(MySettings.self, from: data)
}
If you want to handle the errors use a do…catch block:
do {
let data = try Data(contentsOf: settingsURL)
let decoder = PropertyListDecoder()
settings = try decoder.decode(MySettings.self, from: data)
} catch {
// Handle error
print(error)
}
If instead of a dictionary the property list file had an array at the top level:
typealias Settings = [MySettings]
var settings: Settings?
...
settings = try decoder.decode(Settings.self, from: data)
To rename properties if you don’t want to use the key names from the property list file add a CodingKeys enum to the type. For example, to use id instead of someInt:
struct MySettings: Codable {
var someFlag: Bool
var someString: String
var id: Int
private enum CodingKeys: String, CodingKey {
case someFlag
case someString
case id = "someInt"
}
}
As simple as that. For a more complex example using a dictionary of dictionaries see the ScaledFont project from last week.
Writing A Property List
Writing or encoding a property list is just as easy. Create a PropertyListEncoder, set the output format and then use its encode method:
let someSettings = MySettings(someFlag: true, someString: "Apple", someInt: 42)
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
do {
let data = try encoder.encode(someSettings)
try data.write(to: settingsURL)
} catch {
// Handle error
print(error)
}
Note:
- The
outputFormatcan be.binary,.openStepor.xml.
Objective-C
If you are using Objective-C to read and write property lists you should be aware that Apple will in a future release also deprecate these methods on NSDictionary and the similar NSArray methods:
+ dictionaryWithContentsOfFile:
+ dictionaryWithContentsOfURL:
- initWithContentsOfFile:
- initWithContentsOfURL:
- writeToFile:atomically:
- writeToURL:atomically:(BOOL)atomically
Foundation adds new methods to iOS 11 that include an error parameter:
+ dictionaryWithContentsOfURL:error:
- dictionaryWithContentsOfURL:error:
- writeToURL:error:
So to read the property list with iOS 11:
if (@available(iOS 11.0, *)) {
NSError *error = nil;
settings = [NSDictionary dictionaryWithContentsOfURL:settingsURL error:&error];
} else {
// fallback to old method
settings = [NSDictionary dictionaryWithContentsOfURL:settingsURL];
}
To write the property list with iOS 11:
if (@available(iOS 11.0, *)) {
NSError *error = nil;
[settings writeToURL:settingsURL error:&error];
// Handle error
} else {
// fallback to old method
BOOL success = [settings writeToURL:saveURL atomically:YES];
// Handle error
}