[MUSIC] So far we have persisted some simple settings and values using NSUserDefaults. We have also used NSURLSession to download data from the internet. But what if want to create an archive of one of our more complex objects like instances of the feed and feed item classes. This video will introduce the NSKeyedArchiver class and NSCoding protocol which let you save instances of your own custom classes to a file and read them back later. Once we have updated the feed and feed item classes to comply with the NSCoding protocol, we can save the feed to a file. So that it doesn't have to be redownloaded each time the app starts up. Okay. Welcome back. So far we have loaded and saved data to the NSURL or NSUserDefaults. We've started using NSURLSession to download data. Now we're going to learn how to save data to a file. So the motivation for this is that every time this app become active either when it starts or when it switches back from another app. It reloads that feed, downloads it and opens up that feed and recreates it. Why don't we sort of minimize our bandwidth usage by saving that feed to a file and then loading it from the file, unless it's kind of old. So this is kind of like a cache of that feed. Now you can't just write that feed to a file really, because it's a custom object that we created. So the file system doesn't really understand how to store it. That's okay, as long as we go into that feed object and make it comply with a protocol called NSCoding, then we can actually code that file into a format that we can write to the system. And by using NSCoding, we can actually save the kind of connections between the feed and the feed item and all of its properties. So that we can recreate that without actually having to download the feed JSON file and recreate it. It can just be sort of woken up again from file. So let's go into feed and start making it comply with that in NSCoding protocol. Okay, so I'm in the feed class. The first thing I have to do is make the feed a subclass of NSObject. That's going to be required just to make it work with NSCoding. And then we're going to give it the NSCoding protocol here. So this is a subject, this is now a subclass of NSObject, and it's going to comply with the NSCoding protocol. Now that we're a sub object, a subclass, we're going to have to call the NSObject initializer in our designated initializer. So just add that here in our designated initializer just to make sure that that gets called. And now we have to add two methods to comply with NSCoding. The first one encodes the object. So when we're saving the feed object to a file, it's going to encode itself, it's going to tell the system how to code its different properties and put it to sleep basically. And then we have to create another intializer that can decode the feed from a file, and that's going to wake it up basically and populate its properties from information that was contained in the file. So let's just put in those two methods here, let's see here. There we go, okay, so we got two new methods here. The first one is encoding and the second one is the new initializer. It's another convenience initializer. This time, this initializer gets a parameter called a decoder, or NSCoder. And you can use this decoder to decode the various properties that we've saved in our object. So when we get this back, we'll have some keys that we can look up and get our various properties out of that. If we don't get the properties we're expecting or if there's something wrong with it, we can return nil. Otherwise, we call our designated initializer with the items that we've recovered using the decoder. And we have an encoder as well, so this gets called when this object is getting put to sleep in a file. And within this function, we can encode any of the properties that we want to save, and you'll notice that the keys match up between the encoder and the decoder. So you'll kind of understand that these kind of functions kind of go together. So you can also understand that you can encode whatever you want. If there're properties of your object that you can compute later that you don't really need to encode, you can leave them out of it. If you kind of add properties later as well, nothings going to happen if you're loading them with different kinds of code. They can decode or encode objects. I guess whatever they want to encode or decode just based on the keys that you're giving it. So it's pretty flexible that way. You'll also notice that we're getting this error here, and that's because we're encoding our items. So normally it's okay to encode any kind of property list compatible object. So URLs, or any object that complies with the NSCoder protocol as well. So our source URL looks okay, right? It's a NSURL. And if I go and look at the NSURL documentation. Let's see if it's going to load. It's kind of slow. Okay, let's open that up. So if I actually look at the NSURL documentation. Let's see what it conforms to. Okay, it conforms to NSSecureCoding which is a little bit more of a secure coding, it says sub. It's the same as the NSCoding protocol, so as long as it subscribes to NSSecureCoding or NSCoding then we can use it in the feed encoder. But our array of feed items, that doesn't subscribe or doesn't comply with the NSCoding protocol, so we're going to get a complaint about that. And actually furthermore, the feed item is not even an object, it's actually a struct, and you can't use structs with NSCoding. So we're going to have to go into the feed item, change it into an NSObject, and also make it compile with NSCoding. And then at that point everything that we're trying to write to the file is going to be complying with NSCoding. So you can see it's kind of like a recursive approach here. So first the feed gets coded, then it calls the encoding methods on for the sort of sub properties and each of those are going to code themselves as well. The URL can code itself out of the box but we're going to have to fix the feed item to work with that, so let's switch over to feed item. And it was already a struct so let's change that into a class. And that's also going to be an NSObject, and that's going to be NSCoding as well. All right. Okay, so we've done that. Now we need to actually add an initializer for it. Since it's not a struct anymore, it doesn't get a free initializer. So let's create a quick initializer here. Okay, just get those two things here. Okay, so we have an initializer, and now we have to actually add those same two methods as well. So let's do here to FeedItemCoding. Okay, here we go. So it's the same pattern, right? So we are encoding our properties. You can choose a key just to destroy that property. The key can be like, any string. So, just have to make sure you use the same key to store in decoded as well. I'm using string literals here, but often you might want to actually set up a constant within your object, it'll make the chance of maybe having a typo between the two, and you may be wondering why you're not getting your properties decoded. And then again, we're checking to make sure we have the values that we're expecting, and calling the designated initializer. So that makes the FeedItem also compliant with the NSCoding as well. You remember that there was another protocol called NSSecureCoding. I'm just going to touch on that very briefly, because it's important to remember that if you are storing and retrieving data online from sources that you don't trust. There are different kinds of attacks that people can do if they send you data that you're not really expecting. So NSSecureCoding, it has a little bit of a method that guarantees that tou're getting objects that you're expecting. So there is a chance when you're using NSKeyedArchiver that you'll get objects that you're not expecting and it's going to instantiate objects that is, that you're not kind of expecting to get. And that it could have unexpected effects on your app. So if you're transferring data that's using NSCoding online then make sure you comply with NSSecureCoding instead of NSCoding. We're just using NSCoding for now because I found there was some difficulty of getting that to work with Swift currently. So just in the future, that may be a bit easier. But just keep that in mind that if you're using the NSKeyedArchiver and NSCoding that you shouldn't really transfer those things back and forth. You should keep them only within trusted sources. Okay so for now, so we've got these feed items and feed, and everything's complying with the NSCoding protocol. And we should be able to build it, nothing's going to really change. You just added these methods, but the next step is to actually write these and then load them back. Okay, so we've given the feed and feed item classes the capability of being stored in a file or serialized. Now, let's actually do that. Let's actually store them and read them. So to do that, we're going to add some more methods to our app delegate class. So I'm back in the app delegate and we are going to add, let's start with some methods to save and load these objects. So let's insert those. Here we go. Okay. So I've added three functions here. So the first one is just a function that returns the file path. In order to store a file, it's not like the NSUserDefaults. It doesn't have a kind of default kind of, the NSUserDefaults are all stored in the same file provided by the system. We don't have to worry about where it goes. When it comes to storing random stuff like feeds or feed items, whatever we want to store, we have to give it a file name and a location just like you would on a desktop computer. We can do that by using the NSFileManager to get a directory that we can store stuff in. So you don't give it a direct path because applications are sandboxed. You don't know exactly where these files are going to go in the system. So you can't give it an absolute folder. You actually use an NSFileManager to ask for a certain type of directory, and then you can store the feed file in that directory. So we can use the default file manager. Again, it's like a singleton object. We just get the default manager. And then it asks for a URL to a directory. You actually give it the type of directory that you want. So, if you wanted to store a user document. You would store it in the document directory. This is an enum, which has several different options. Now, if you want to store stuff in the document directory, that's generally reserved for types of things that users create like, a note or a document, or a movie, or something. Some kind of media. Some kind of thing that the user has created that maybe they want to have backed up when the iPhone or iPod or iPad gets backed up. But, for our item, it's actually kind of we can recreate it whenever we want. Right? We don't really need this to get backed up, its going to be kind of a cache. So we're going to just change that to cache's director. So you see that there are different options here. If I go in here maybe I can see some more, some code completion here. There are all kinds of different locations. Some of them apply only to the Mac. Some of them apply also to the iPhone, but really often you'll be using either maybe caches or documents. So let's say caches. And then you give it a domain. So the user domain mask is just tells you that you're going to store it in user files. On the Mac, you might also have a system domain or a network domain but you're always going to really use the user domain mask on iOS device. Then we get a URL to that cache's directory, but we need to give it a file name, so we're going to append a file name to it by using the URL by appending PathComponent. And also when you get these URLs back, you actually get an array. So we're just going to take the first item out of the array and append a file back. It's really supposed to give you a bunch of URLs, maybe that might match this kind of request, but on the iOS device you're only ever going to get one. So we'll just take the first one, and give it a file name. I'm just using plist here. You don't have to really worry about what kind of extension you give to the file. It's really an implementation detail provided by the system. But if you wanted to actually go in and open that file manually on your Mac and take a look at it inside. You can kind of load it as a plist file because that's pretty much the format that it's stored in. Okay and then this function returns the path. The reason I packaged this up as a function is that we're going to need this in different places so it's just a good idea to kind of have a general encapsulation of that into a function. Okay, now this is how you save it. So if you want to save a feed, we have this function called saveFeed and it takes the feed object, and it's going to use this NSKeyedArchiver class. And that has like a class function called archiveRootObject. So it's takes a root object just one object, so it could be any kind of object that complies with that NSCoding protocol. And it will write it to a file. And you give the file a path here. And it gives you back a boolean that says whether it succeeds or fails, and then we're going to return that success so that we can just make sure that the file is saved properly. And then if you want to read that file back, we have a read feed function here, and it's going to get that same file path and this time, it's going to use an NSKeyedUnarchiver object. So, this is kind of matching object for the archiver, it's the unarchiver and it has an unarchived object. And you just give it a path and what you get back is an unarchived object. And then we're just going to return that, we're going to try to cast that as a feed, and call the completion with that. The reason we're using a kind of a completion thing here is that maybe you want to put that on to another thread at some point in the future. Maybe it's really slow. Maybe it takes a long time. Remember like NSUser defaults. We're only storing really small amounts of data in that. So it's not super slow. But, with this, we could potentially be storing hundreds of items. Maybe hundred of objects or thousands of objects. So you may want to actually put this on a separate thread. It could take much longer because it is a definitely reading and writing from local storage. But for now, we're just going to really use it on the main thread, it's not super slow anyway. Just keep that in mind. And once you get up to thousands of objects, there are other, maybe better, ways to store stuff like that. You might want to look at SQLite to store things in a relational database. And then there's also Core Data which is kind of a much more flexible version of sort of object graph storage. And that's provided by the iOS SDK. Just as you remember, just to kind of intro Core Data just a little bit, is that we had to write all this coding and decoding stuff to build this object graph. Core Data actually kind of automates some of this boiler plate for you among other things. It also automates things like validation and searching, and it does a lot more than what the Keyed Archiver can do. But the Keyed Archiver's good when you just have very simple stuff, like maybe storing a cache file like we're doing. Okay, so we have the basic methods to save and read it. Let's add a method to, you know, we're going to have to save it when it's successfully downloaded. So let's go in here and add a few lines of code just to save the file if it was successfully downloaded, so let's just add that few lines of code here. Okay, so what's going to happen is it's going to check to see that this is not nil, and if it is, then it's going to try saving it. And then it gets back a Boolean and if it succeeds, actually, we're going to use that standardUserDefaults. And this time we're going to write a date again. Remember at the very beginning, we were saving that date from the button. We're going to kind of use the same method here. We're going to save a date, but this time we're actually going to save the time that it was last updated. When this file starts to get old, maybe it starts to get hours or days old. You probably want to force the feed to update again from the remote source. So this will tell us when we actually saved that file. And it's going to store it in the user defaults. And then we're just going to print out a little message, just to confirm that we actually loaded it remotely. And then it continues on and completes that. So that's just the quick change to the updateFeed method, and we're going to add one more method here. And that's going to basically, either, it's going to decide whether to load it locally or load it remotely. So I'll just do that. Okay, so this is kind of a big, complex, nested function. Let's see what's going on here, make sure we've got matching braces here. Okay, so this method will basically decide whether or not to load the feed remotely or locally. So it's going to read back that time that it was last updated. Then it's going to compare it to the current date. If it finds that it has been updated at some point in the past, and if there's a difference of less that 20 seconds, it's not going to update remotely unless it has to. If it's more than 20 seconds, then it's going to update remotely. It's going to skip loading the file that was saved to disk. And that feed is getting really old, so it's going to skip that, and it's going to definitely force it to load the remote feed again. So if we get to this point, it should update. That file's really old. It's going to call that downloader again. If not, then what it's going to do is call our local readFeed thing, that's the one that does the unarchiving. And it's going to get this callback, and it's going to check as well to see, remember that source URL property that we had on the feed object? It's going to just make sure that that's matching the one we're requesting. because remember the user can switch feeds. So what if they switch from kittens to pugs or to pizza and the one that we actually have stored is the kittens instead? It's just going to make sure that those are the same. If not, it's going to again, go back and try to update it. But if passes all these test, if it's not too old, if it's exactly the data we're looking for, then it's actually going to load the saved one and then it's going to call our completion to update the view controller. So that's kind of a little bit of complicated logic just to make sure it has the most recent up to date feed, it's not got the wrong feed and it's loading stored feeds if it can. Anyway, that's basically NSKeyedArchiver. You can see how you can get a reference to a directory, you can create a file name, a path and then you can archive that object. And as long as this object complies with the NSCoding protocol, then you can save it to a file and then get it back by unarchiving it. Okay, so there's just one more thing we need to do before we start testing this. And that is to go back up and modify our applicationDidBecomeActive method a little bit. Now that we've wrapped the update feed with this loadOrUpdateFeed method, we're going to have to change that method name, so I'm just going to copy that and just replace that. It's the same signature, so I'll just change the name. And that's it. So, now, we should be able to run the app and see it loading in, either from saved or from remote, so just open that up and I'm just running it. Okay, so it's going to run and so it's loaded the remote feed to start. Let's switch over to the app here. Let's sort of close it, let's open it up again, now it's loading the saved feed. So if you open it up again, it's still opening the save feed. So for about 20 seconds, it should open the saved feed and then it will eventually try the remote feed again. 20 seconds isn't very long but it's good for checking it to make sure it works. You may actually, in a real app, maybe want to set it for longer. So now it's going back to the saved feed. Okay, so it's loading that save feed. It's timing out, then it's loading the remote feed again. If we were to go into the settings and change it, it would force it to the remote feed. Okay, so that is NSKeyedArchiver. And we're saving stuff to a file, we're loading it and loading it back. And then using that to kind of supplement our URL loading system. Okay, thanks for staying with me through this. And we're going to go on to some more stuff in the next video. Thanks.