Today i have been assigned another JIRA issue by my product manager. This task is simply to mock the app data or to maintain a backup of the app data like json retrieved from API. I just started to dig into the project code because i was newbie to this project and i found that our data models is written like Structs not Classes. The story started from here because that’s made me to write this post.
I’m working now on Apple TV App (YAY), i’ve never imagined that i’ll develop in tv OS, However things happens. the project i’m working on is a entertainment application like most Apple TV apps it display Video content so the home screen contain some recommendations video assets from our backend and it’s displayed as Tiles in home screen, once the user clicks on any tiles he will be redirected to asset details screen that contain details about the video clicked[he can play video, browse cast&crew, Add to Wishlist, etc]
One of the functionality that our client requested is to be able to make the app working even the backend/server is down. So we have to persist these video assets data(recommendations,…) somewhere even if the user close that app and open it again.
First of all i thought for multiple solutions for persisting these data:
1- It make sense to use NSUserDefaults to save recommendations. As NSUserdefaults is a plists which is a NSDictionaries that can includes array, strings, numbers, and even NSDictionaries. 2- I just thought to use a local json file that contain a backup json data the same data comming from backend.
Finally i decided to use both solutions for persisting these data, So the application flow was as follows:
This was the sudo code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
The beauty of this way is that in any case we have a mock data so our app can work any time our server is down. even if this is the first time the user open the app and the server down, don’t worry the app will load the data from the local json file. Also we will have a updated mocked data because every time server is responding we save the new data in NSUserdefault so it’s updated data.
This too much information and out of scope now, sorry just thought i’ve to share it with you!
Let’s get back to the point, So we have to store an array of recommendations objects in NSUserDefaults so we can retrieve them any time.
The way we all know to make this happens was as follows:
We use NSKeyArchiver the archiving functionality to achieve that.And by the way our data persistence were not large to use Core Data or SQLite.
Archiving functionality has few parts. we have to conform to NSCoding protocol that requires to implement 2 methods encodeWithCoder and * initWithCoder* which is where we specify how to encode and decode data.
Then we implement NSCoder protocol methods in our Recommendation class as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
This way serialize our Recommendation object and is convert into NSData, making it easy to be persisted.
To store the encoded object as NSData we use NSKeyedArchiver archivedDataWithRootObject method as follows:
To decode the our recommendation model we have to search in NSUserdefaults for encoded object with the key “recommnedations_data” then decode and return it.
1 2 3 4 5 6 7 8
Issues with this technique:
NSCoding only works with objects inheriting from NSObject.
Currently in our Apple TV application we use Structs for the model layer so every model is basically a Struct. Actually our cannot conform to NSCoding protocol because it’s not inheriting from NSObject.
As we have a lot of models in our app and we are taking advantages of the features of Structs, It doesn’t make sense to convert/rewrite our models (Recommendation) to objects rather than structs.
How we solved the problem of persisting Structs in Swift? Since we can’t archive and unarchive them like classes inherits from NSObjects.
Let’s spill out the the solution we can do:
Recommendation model this is one of the models that we want to persist. this model is already implemented in the project as a Struct:
1 2 3 4 5 6 7 8 9 10 11 12 13
Now we need a way to persist our recommendation model, we have to convert our Struct to another object that can be persisted like NSDictionary.
The idea is we can convert our Struct into NSDictionary object and then we can persist this NSDictionary into NSUserdefaults easily. and then we can extract this NSDictioanry from NSUserDefaults and brings it back into Recommendation instance.
we did that using a Protocol we used a protocol to make it more generic for all models of the project. this protocol contain two methods, one method return an NSDictionary from Recommendation instance by going through the Recommendation instance and set key-value pairs for each of the variables and returns an NSDictionary.
another method that takes NSDictioanry as parameter and return an instance of Recommendation model.
1 2 3 4
Now we can extend each Struct with protocol
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Note: You may face a problem in your implementation is that you have another struct instance inside you Struct, No worries you have to follow the same approach all your persisted Structs must conform to PropertyListReadable protocol and then you can propertyListRepresentation to get an NSDictionary for this Struct or return this Struct back from NSDictionary.
Dealing with NSUerDefaults:
1- Extracting Structs from NSUerDefaults:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
2- Saving Structs in NSUerDefaults:
1 2 3 4 5 6 7 8 9 10 11 12
P.S. I’ve created a Playground that contains a working example.
Happy Coding :)