Storing App Data Locally
Reading and Writing to a JSON File in Your iOS Application
** NOTE : DO NOT USE THIS METHOD TO STORE SENSITIVE INFORMATION **
Introduction
Data plays a HUGE part in how well an application functions. From fetching the latest information on a server to storing all your favorite cat photos, modern iOS apps seem to be effortlessly managing data behind the scene. Personally, as a new developer, it seems overwhelming trying to figure out how to make the jump from data manually written into the code to dynamic databases with web connectivity. But we all have to start from somewhere.
In this case, locally storing application data in a JSON file whenever the user interacts with our app but before we get to the code let’s answer what I think are the two biggest questions.
What is JSON?
In short, JSON stands for Java Script Object Notion and it’s a common, light-weight, format used to transfer data in, but not limited to, web applications. JSON focuses on two structures: objects and arrays, which are present in a majority of modern programming languages. But the best part is that it’s human readable and computationally simple to parse.
For additional information on JSON check out: JSON.org, JSON — Introduction W3, Working with JSON — Mozilla
Why JSON for iOS development?
In my opinion the greatest advantage of using JSON is the convenience. The simple fact that a whole database doesn’t have to exist just to save a couple of basic data types fills me with glee. The back-end of an application is made simple with just as few as one JSON file. Using JSON could be thought of a stepping stone into incorporating persistent, changing, data into iOS applications. Which means that, new developers don’t have to dive into the deep-end of Core Data, CloudKit, or other databases immediately after their first app. Additionally, JSON is used in a multitude of Application Programming Interfaces (APIs) to send and receive data from established databases and website services (think weather, stocks, emails, etc.). So, a new developer would encounter JSON at some point in their development adventures and learning it early makes the task a bit less daunting.
For additional information: Using JSON — Apple Developer
JSON and Swift
The reason this article exists is because in prior versions of swift working with JSON was a nightmare for new and established developers alike. It’s such an issue that Hacking with Swift wrote an extension to decode JSON. Similarly, GitHub users developed SwiftyJSON as a “better way to deal with JSON data in Swift”. In short, it seems like an unnecessary hurdle but there is a silver lining: Apple has improved the overall interaction between Swift and JSON in Swift5 which I stumbled upon due to a Stack Overflow answer from user Rob.
Thanks to Rob, this article exists to help new developers, like myself, get the information that they need to work with JSON on Swift5 and SwiftUI without feeling overwhelmed.
Code
The app that my code is derived from is the WeSplit tutorial form Hacking with Swift (GitHub)but I wanted to extend its capabilities. I wanted a way for the user to see past totals per-person and was interested in having that information available after the app was closed. Additionally, I’d like to note that this article will consist of the JSON code and not touch on the SwiftUi views.
My first attempt was to make a JSON file in the app’s bundle directory, which contains all the executable code and resources needed for the operation of the app. This isn’t the best idea since files there are read-only and if code attempts to write to the file, the system creates a temp file that gets deleted once the user quits the app.
So where do you put the files?
For the purpose for this app, the JSON file is stored in the app’s document directory. This directory houses user content and is accessible to the user. Since this app is only being simulated or deployed on my personal devices, I’d like to make sure that the JSON file is being updated with the correct amount. Again: DO NOT USE THIS METHOD TO STORE SENSITIVE INFORMATION. Especially in this case, where the user has access to the saved data.
For more information: Bundle Programming Guide, iOS Storage Best Practices, File System Programming Guide
Writing
Alright, now that we know where we are saving the data let’s go through the code to write data to a file. Again, my example is an implied version of the one given on Stack Overflow from user Rob so feel free to view his example for objects.
Let’s start with the basic: The function writeData takes in an array of doubles and return nothing (Void). Inside the function, we implement Error Handling in Swift which is the DO-TRY-CATCH block of code. Essentially, if the code after the TRY encounters an error then the CATCH statement will run. In this case, the catch statement simply prints out “error writing data”. Otherwise, it continues on as normal.
Specifically looking at the DO block, the code generates a URL path to a file stored in fileURL. The two important modifier here are the .documentDirectory and the .appendingPathComponent(“pastData.json”). The first modifier specifies the location of the file (e.g. the Document Directory) this can be changed depending on the desired location (see iOS Storage Best Practices, File System Programming Guide). The latter represents the name of the file; In my case, the file name is pastData.json. Another key point, is that the variable fileURL has to be in the DO-TRY-CATCH block since it can throw an error. Ensure that the directory location and the filename match in both the reading and writing functions. Otherwise, you’re going to have a difficult time trying to read in one location and write in another.
Lastly, the TRY portion, attempts to encode the input into JSON and then attempts to write to the file. If successful the debugging console should not contain the error message.
Reading
Unlike the the write function, the read function has no inputs but it does return an array of doubles. Similarly to writeData, readData uses the DO-TRY-CATCH block of code with a slight difference in the error code so that we can pinpoint if wither or both of these functions fail.
Starting from the top of the DO block, the code generated a URL path to the directory and file we specified. In this case the .documentDirectory and the pastData.json file. A new constant is then created to hold the information that the program attempts to read from the JSON file. Then the variable is converted from JSON information to data which can then be used in the application. One thing to note, for this application is that [Double].self should be changed to the appropriate data type. For example, if the data was a custom type of Book then then the line should be Book.self. If there are no errors the readData function returns the information in an array of doubles. On the other hand, an empty array is returned if a read error is encountered.
Closing Thoughts & Next Steps
Hopefully, this has been a decent introduction to storing data locally in an app. I realize that the approach here might not be the best or most efficient method but it’s a starting point and it’s one that helps to introduce a file format that new developers will encounter. As always, Apple’s Developer Documentation on JSONEncoder and JSONDecoder are fantastic resources to go more in-depth on encoding and decoding JSON data.
As for Next steps, it really depends on what you would like to learn but here are some options:
- Apple — CoreData: CoreData is used to save your app’s data for offline use, cache temp data, and to add ‘undo’ to a single device.
- Apple — CloudKit: CloudKit works in conjunction with CoreDate to store data in iCloud and sync it across all devices.
- Docker: Docker enables packing your app in containers such that it can be run on any type of infrastructure regardless of provider