Saving Data in iOS

May 31 2022 · Swift 5.5, iOS 15, Xcode 13

Part 2: JSON

14. JSON Encoding

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 13. Saving On Device Next episode: 15. Challenge: Encoding JSON Arrays

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 14. JSON Encoding

This course was originally recorded in April 2020. It has been reviewed and all content and materials updated as of November 2021.

Transcript: 14. JSON Encoding

I’m 50% done with the whole “Forgetful Felipe” thing. I’m already remembering things thanks to the app loading stored data from the Document directory, but unfortunately it’s not saving any new things I don’t want to forget about.

Using the knowledge you acquired in part 1, saving data to files, you will now start storing a single task to disk.

Open TaskStore.swift and add a new method declaration that you will use to save a single task.

private func saveJSONPrioritizedTask() {
  ...
}

The process is very similar to decoding data. You will first encode your Swift type to Swift Data, acquire the URL for where to store your data, and then save the file, with your info, to your device’s storage. Encodable automatically handles writting it to the file in the correct format, so you’ll be able to see how thing are being stored to the Document directory in a plain JSON file, just like the ones you’ve seen in the project so far.

Just as when loading, when you used JSONDecoder, you have JSONEncoder available to you. Add a JSONEncoder at the beginning of the method.

let encoder = JSONEncoder()

Very similar so far. As you did with the load method, you will have a do-catch statement as encoding, as well as writing the file to your device’s storage, can throw an exception.

do {
  
} catch let error {
  print(error)
}

Just as when loading, you print to the console, any errors that may take place during the process. As mentioned before, the first step is to encode your Swift type to Data using the encode method on JSONEncoder.

let taskData = try encoder.encode(prioritizedTasks.first?.tasks.last)

The only parameter it takes is a Swift object that conforms to the Encodable protocol. When you added the Codable protocol conformance to a few of your models, you adheres to both Encodable and Decodable. For the object to encode you are just getting a single task out of a PrioritizedTask object in your array.

As always, start small and expand from that. If writing a single Task succeds then you can proceed to write an array of PrioritizedTasks.

Note how the call is marked with try as encoding can throw an exception. For now you’ll store just a single Task in order to verify that things are working correctly, and to do so you have to specify the URL of the file where your JSON will be saved.

let taskJSONURL = URL(fileURLWithPath: "Task",
                      relativeTo: FileManager.documentsDirectoryURL).appendingPathExtension("json")

This should look very similar to you, you are getting a URL to a file named Task.json, and storing it within the Document directory. The final step in encoding is to write your encoded object to the JSON file.

try taskData.write(to: taskJSONURL, options: .atomicWrite)

Once again the call is marked with try as it can throw an exception, and, similar to what you did in playgrounds, you are asking your encoded Data to be written to a file at the specified URL. Once again the only option in this method is .atomicWrite, which is a fancy way of asking that the data be saved to a separate file first, and once that success it gets exchanged for the final file, the one you specified in the URL.

That ensures that, should something crash or go wrong, the original Task.json file doesn’t get damaged or corrupted in the process.

And that pretty much does it as far as writing a Task object to a file in your Document directory, but where does it make sense to call this method?

A good place to do this would be in the didSet of the prioritizedTasks property. Think about it, every time you modify your prioritized tasks is likely when you want to save your changes to a file. This will ensure that all changes made by a user are immediately persisted and not lost. Implement didSet for prioritizedTasks.

@Published var prioritizedTasks: [PrioritizedTasks] = [] {
  didSet {
    saveJSONPrioritizedTask()
  }
}

Build and run your app in the iOS simulator, be sure to have the Document directory open in Finder for your app, ensure that it’s empty, and add a new task in the app.

Oh no! What’s going on? Well, your array of prioritizedTasks is empty, but it needs to contain at least one PrioritizedTask per priority available. To fix that, for now, copy over the PrioritizedTasks.json file to your app’s Document directory, we’ll fix things so that file isn’t required in the next video.

Run your app one more time, and try to add a task now. Excellent! Your task for added.

Open the app’s Document directory and look for a file named Task.json. Select it and press the space bar to preview it. You can see that a single task is now getting correctly saved to the file, hurray!

One final thing worth mentioning is what to do when your Swift Types have Date or Data objects in them. Fortunately Encodable and Decodable can take care of it for you.

Data and Date get automatically serialized to JSON. Data as an encoded String and Date as a floating point. If you want to change the strategy to use when encoding or decoding these, you can use the dataEncodingStrategy or dateEncodingStrategy properties of JSONEncoder, and the dataDecodingStrategy and dateDecodingStrategy properties of JSONDecoder.

One of those, when encoding dates, is the .formatted case that lets you specify a specific date formatter to use.

While you don’t need to worry about either of these in this course, you might, at some point, need to save or load JSON data that uses a specific format for dates. For data the convention is to use a base-64 encoded string, but should you need to customize that too just know that it’s something that can be done.

In preparation for saving and loading your tasks to and from the same file, take the tasksJSONURL out of the loadJSONPrioritizedTasks method and move it out to the TaskStore class.

let tasksJSONURL = URL(fileURLWithPath: "PrioritizedTasks",
                       relativeTo: FileManager.documentsDirectoryURL).appendingPathExtension("json")

Everything should continue to work as it was, except that you now have that URL available to use when you save the array of prioritized tasks.

Are you up for a challenge? It’s time to save all of the prioritized tasks to a file, not just one. No more forgotten sunglasses or wallets at last!