How to Persist Data in iOS Apps Using Property Lists and the Correct Architecture for Handling Persistent Storage

Many iOS developers seem to go for very complex storage solutions when iOS offers very quick and easy ones that are much better for many tasks.

I myself made such mistake in the past. When the iOS SDK first came out I decided to make a little game for the iPhone. It was a little card game and as such it had to store the values for each card as well as information about the deck and cards the player collected. And I used the worst solution possible.

At the time I was coming from Mac development, where I used Core Data for data storage. But there was no Core Data in earlier versions of iOS. Unfortunately, when I Googled for solutions I was misinformed that the only way to store my app’s data was with SQLite.

Looking back, that was overkill. Because I was using a database management system (DBMS) – designed to handle huge amount of data and relationships – just for storing static data plus a small amount of data that did change. Such a simple usage didn’t justify the very complex work involved in setting up and using SQLite.

On top of that, SQLite has a C interface, so I had to use C functions to perform queries on the database every time I needed to create a card object in my app. Even though Swift did not yet exist, using C in Objective-C was already quite annoying and many of the benefits of Objective-C were lost. To give you an idea: all the database queries needed to be written into strings, there was a lot of management of pointers and the API was not very intuitive to use at times. Nowadays, with Swift, the gap is even more evident. And calling C functions or managing pointers in Swift is even more painful.

By the way, I also made the mistake of letting the model class for a card access the database directly, which was even a singleton! So two errors here: I used an overblown and complicated solution when simpler would do, and my architecture left a lot to be desired.

We all live and learn – luckily you can avoid the pain I went through by following the advice here. As I explained in my previous article on the different storage solutions in iOS, there are much simpler options available. One in particular is perfect for both providing static data to an app and to store small quantities of structured data: property lists.

In this article I am going to cover how to use property lists to store data in your apps as well as choosing the correct architecture for handling data storage. The app I will build is a simple list of quotes and its code is based on the architectural concepts I explained in my previous article on UITableView. If you don’t know how to use the MVC pattern to separate data sources for table views I advise you to read that article first.

Property lists – what kind of data do they store?

Property lists are a simple, yet versatile, way to store hierarchical data in iOS. That’s data structured in a tree of relationships. And I am always surprised by how few developers know about property lists and use them. In many cases, property lists are the simpler alternative to the overly complex solutions many developers use. Property lists are often a better storage solution than Core Data or SQLite in iOS. For guidance on matching solutions to each use, you can read my article on the available storage solutions in iOS.

With hierarchical data, every object in the tree can have one child or more. But there are no links that

  • go back to ancestors on the same branch
  • skip children objects to go to a descendant node
  • go across branches.

Because all of these would make the structure a graph, which is a much more complex structure.

But actually hierarchical data is very common in our world and especially in software development. For example, companies are usually organized in a tree structure, with the CEO at the top, then managers that report in to the CEO and employees that report in to managers (yes, I am oversimplifying, but you get the idea). In software development hierarchical data is quite common too: HTML, and more generally XML, and JSON are very common examples of hierarchical structures.

As you see there are many cases where property lists come in handy. So let’s have a look at how they are structured.

First of all, property lists only allow simple types: booleans, numbers, strings, dates and raw byte data. No other types are allowed in a property list, although it is always possible to convert any type to a simple type representation.

A property list also allows two data structures:

  • Dictionaries – which represent complex objects,
  • Arrays – for collections of values.

How to create a property list in Xcode

Let’s see a practical example. In the app we’re building we want to show a list of different quotes. Each quote will have an author and the actual text of the quote. We can store these in a property list file in our Xcode project. First of all, we need to create the plist file. Select File -> New -> File… from the Xcode menu and select Property List in the Resource section.

property-list-creation

Xcode has an editor that simplifies the creation and editing of plist files. This also allows non-technical people on your team to access and edit these file – instead of the developer having to change the app’s data.

property-list-file

In our plist file, the root is an array, since it needs to contain a list of quotes. Each quote in this array is represented by a dictionary. These dictionaries contain two keys of String type: one for the author and one for the quote text. Now that we have the data for our app, we will write the code, starting from the model.

The model takes care of data and its conversion

First of all we need a model type for our quotes.

struct Quote {
	let author: String
	let text: String
}

Nothing really complicated here. But this is where most developers would stop. Our data will come from our plist file, in the form of dictionaries, which means we have to convert it to our Quote struct. Most developers would go on putting this code in a view controller. And this is one reason why view controllers often become bloated, making them very hard to understand, change or test and causing a lot of hard to find bugs.. Data conversion belongs to the model layer of the MVC pattern, so we can put it in an extension of our Quote struct.

Beware that since Swift 4 you can, and should, decode and encode property lists using the new Codable protocols. The following approach is now obsolete. Please refer to my other article until I get a chance to update this one.

A property list dictionary has keys of type String and values of type AnyObject

extension Quote {
	init(dictionary: [String: AnyObject]) {
		author = dictionary["author"] as! String
		text = dictionary["text"] as! String
	}
}

We could have put this initializer in the Quote struct itself, but it’s good to put it in a separate extension for two reasons:

  • First of all it shows that this functionality is related to data conversion and not to the structure itself. We could even put the extension in a separate Swift file.
  • Second, putting this initializer in an extension preserves the synthesised memberwise initializer of the struct, in case we need to use it later. The moment you put a custom initializer in a struct, the memberwise initializer will be gone and you will have to rewrite it by hand.

For simplicity’s sake, I am assuming that the values will always be present, so I am force unwrapping the optionals.

This is a good approach but I am still not satisfied. Because we still have two problems:

  • Too much repetition – there is a lot of repetition when fetching the values from the dictionary, since we have to force unwrap the optionals and cast the returned value from AnyObject to String. This might not be very evident here because we only have two lines, but it would if our struct had more fields or we had more model structs.
  • Crashing risk from using string literals – the keys in the dictionary are string literals, which means two things. First, if I need to use them somewhere else, I will need to repeat them. So if I decide to change their name in the future I have to go back and find all of them and replace them with the new values. Second, there is no compiler check. So I could misspell them and the app would still compile fine – until I ran the app. Then it would crash.

So let’s look at a better solution. First of all, we define a typealias for plist dictionaries, to make it clearer around our code that we these are not simple dictionaries, but have a specific signature:

typealias PListDictionary = [String: AnyObject]

This dictionary has keys of type String, but, as I said, using any string might lead to crashes. So we can make the keys conform to a PlistKey protocol, which defines them as property list keys and not as any string.

protocol PlistKey: RawRepresentable {}

The PlistKey protocol inherits from the RawRepresentable protocol. In this way we can use types that conform to this protocol, like enums, as keys in our dictionary.

Property lists have also another constraint. Values in property list dictionaries have type AnyObject, but we know that the types in a property list are actually limited, so we would like the compiler to stop us when we are trying to put an unsupported value in a dictionary. To do this we can define a new PlistValue protocol and then make the types we want to put in a PlistDictionary conform to it.

protocol PlistValue {}

extension Bool: PlistValue {}
extension String: PlistValue {}
extension Int: PlistValue {}
extension Date: PlistValue {}
extension Data: PlistValue {}
extension Dictionary: PlistValue {}
extension Array: PlistValue {}

Now we can write an extension for our PlistDictionary providing a method to fetch a value given a PlistKey. I would like to define this extension on the PlistDictionary type itself, but unfortunately Swift does not allow this. So we will have to extend Dictionary itself.

extension Dictionary where Value: AnyObject {
	func value<V: PlistValue, K: PlistKey>(forKey key: K) -> V where K.RawValue == String {
		return self[key.rawValue as! Key] as! V
	}
}

The getValue(forKey:) method takes care of the unwrapping and the type casting. This is a generic method, so it will work with any of the types stored in our dictionaries.

Now we can define the keys as an enumeration with String raw type.

extension Quote {
	private enum Keys: String, PlistKey {
		case author
		case text
	}
}

This enumeration conforms to the PlistKey protocol and provides a string value, which is simply its raw value. Finally, we write an initializer for the Quote struct that takes a PlistDictionary and uses the getValue(forKey:) method to retrieve the values:

extension Quote {
	private enum Keys: String, PlistKey {
		case author
		case text
	}
	
	init(plist: PListDictionary) {
		author = plist.value(forKey: Keys.author)
		text = plist.value(forKey: Keys.text)
	}
}

As you can see, we completely eliminated string literals and now everything is checked by the compiler. What’s more, we don’t even have to specify the type for the generic method. The compiler infers it from the type of the properties of the Quote struct.

We ended with more code than the first solution of course, but this groundwork will pay off in the future. When our app grows every key will be defined in a single place and the compiler will make sure we don’t make mistakes.

Reading data and populating the user interface

Now that we have the model layer laid out, including the conversion of data from a property list dictionary to a Quote struct, we can read the actual data from the file and populate our app’s user interface. We can do all this in the view controller, right?

Wrong. This is another place where code that belongs somewhere else gets placed inside view controllers. In the MVC pattern, the role of a view controller is only to mediate between the model and the views. Accessing data on the disk is a task that belongs to model controllers instead.

So, first thing we need to create such a model controller, which I called DataController. Data from a plist file can be read through the initializers of NSArray or NSDictionary, depending on the type of the root object in the plist file. In this case we will use the init(contentsOf:) initializer of the NSDictionary class.

class DataController {
	let quotes: [Quote]
	
	init() {
		let fileUrl = Bundle.main.url(forResource: "Quotes", withExtension: "plist")!
		let quotesPlists = NSArray(contentsOf: fileUrl) as! [PListDictionary]
		quotes = quotesPlists.map(Quote.init)
	}
}

In this example the code for this controller is very short. All it does is to read the data from the plist file when it gets initialized and save it in a quotes property other external objects can access. This controller is actually a class instead of a struct. This is because this controller is actually keeping the internal state of the app. In a bigger app this controller would probably be passed around to other view controllers. You can read more about this in my article on how view controllers communicate with each other.

We can finally move to the view controller, where we can read the data and pass it to the data source of the table view.

class ViewController: UIViewController {
	@IBOutlet weak var tableView: UITableView!
	
	let dataSource: QuotesDataSource
	let dataController = DataController()
	
	required init?(coder aDecoder: NSCoder) {
		self.dataSource = QuotesDataSource(quotes: dataController.quotes)
		super.init(coder: aDecoder)
	}
}

Again, the code and the architecture behind the whole app are explained in more detail in my previous article. For completeness I am going to include the rest of the code here. You can find a full explanation of such code in the linked article.

class QuotesDataSource: NSObject {
	let quotes: [Quote]
	
	init(quotes: [Quote]) {
		self.quotes = quotes
	}
}

extension QuotesDataSource: UITableViewDataSource {
	
	func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
		return quotes.count
	}
	
	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
		let cell = tableView.dequeueReusableCell(withIdentifier: QuoteCell.identifier) as! QuoteCell
		let quote = quotes[(indexPath as NSIndexPath).row]
		cell.author = quote.author
		cell.quoteText = quote.text
		return cell
	}
}
class QuoteCell: UITableViewCell {
	@IBOutlet weak var authorLabel: UILabel!
	@IBOutlet weak var quoteTextLabel: UILabel!
	
	static let identifier = "QuoteCell"
	
	var author: String? {
		didSet {
			authorLabel.text = author
		}
	}
	
	var quoteText: String? {
		didSet {
			quoteTextLabel.text = quoteText
		}
	}
}

Further developments

In this example we only read static data from a plist file located in the app’s bundle, but property lists can be used for much more.

  • You can write a property list to a file using the write(to:atomically:) method on either the NSArray or NSDictionary classes. This offers a very easy and convenient to persist and read data generated by the app or the user.
  • You can write these files in different iOS directories, depending what you are using them for (saving user data, app data, or for a cache of data you downloaded from the network). All you need to do is to pass the appropriate URL to the init(contentsOf:) or write(to:atomically:) methods. You can retrieve these URLs through the methods of the FileManager class in the Foundation framework. Using different directories allows you to specify the lifetime and access of your data. In this way you can decide which data is exposed to the user or whether data needs to be persisted or can be deleted when the operating system needs more disk space.
  • NSUserDefaults also uses property lists to store data. All the considerations in this article would still apply, including creating model structs for the data you would store in the user defaults (something many developers ignore). User defaults are the right solution for storing your app’s preferences and settings.

Putting Property List loading in the right architectural context

Although this is just a small example app, it is built on the fundamental architectural concepts at the base of any iOS app. The app uses the MVC pattern, separation between model controllers and view controllers, view controller communication and many other fundamental concepts. You can find all of these concepts in my free guide and iOS development series.

[referral-form title=”Storing data is only one part of making professional iOS apps. Get my free course that covers the other important parts” button=”Send me the FREE course”]

2 thoughts on “How to Persist Data in iOS Apps Using Property Lists and the Correct Architecture for Handling Persistent Storage”

  1. Adding a new fiels on the plist got –

    Could not cast value of type ‘Swift.Optional‘ (0x10606bc18) to ‘Swift.String’ (0x106038fd8)

    Swift 5 Xcode 12

    Reply

Leave a Comment