First feature I want to implement is local storage through NSUserDefaults, why? Because it must be simple to do such a small feature giving the opportunity to implement unit tests all along.
Here comes Quick
When it gets to test frameworks I rather use the ones that are behaviour driven, they are:
- [X] Easier to use
- [X] Human friendly
- [X] Helps us to understand how the code works
So after a quick search I found Quick… Yeah I know, bad joke.
Onward we go and so I used cocoapods to add Quick as instructed by it. Right after
pod install finished testing continues to be easy:
- Create a new swift file in your test
- Then add Quick, Nimble and your module imports
- Code your tests
- Be happy, at least until they break…
Pro tip: In order to the tests work I had to change the model making the things public
Going further with NSUserDefaults
As an exercise and to understand things better, each TodoModel is going to be able to save itself into NSUserDefaults. In the end we can remove this feature, since only saving multiple todos is interesting for the app.
Following is my test written specification and right after it the Swift code.
When a TodoModel is using local storage I want it to:
- save to user defaults
- load from user defaults
- delete from user defaults
- optionally returns the Todo
- avoid failing when deleting “nothing”
In order to save/load/delete the Todo must have some identifier, for that it receives an NSUUID property.
Now, to continue there is something that maybe one is not aware of, storing custom objects into NSUserDefaults demands that TodoModel inherits from NSObject and conform to NSCoding protocol.
Conforming to NSCoding means to implement
encodeWithCoder(aCoder: NSCoder) and
init(coder aDecoder: NSCoder) with each property being encoded as it type like this
aCoder.encodeObject(uuid, forKey: "uuid"). Still don’t get it? Check this excerpt from Apple’s doc
“The NSCoding protocol declares the two methods that a class must implement so that instances of that class can be encoded and decoded. This capability provides the basis for archiving (where objects and other structures are stored on disk) and distribution (where objects are copied to different address spaces).”
Yet I didn’t said why it’s NSUserDefaults demands such details, the reason is that it accepts only a given set of classes and to store something custom one must use NSData which demands a class to be encoded using NSCoding to be transformed into NSData, so that is why.
Finally it is all set for using NSUserDefaults
Replicating to a TodosModel
Each Todo saving itself would be chaotic, yet a TodosModel that represents a list of todos is perfect to represent this app’s data.
The extra here is the filter operation, which as refactored from ViewController to here. Also there is
Saving the todos
Finally the only thing missing right now is to call save throughout where it is needed. I added the following lines with the correct logic into TodosViewController
- As iOS features are the same for Swift, often is needed to be done the same strange paths, the best example is the travel made to put a simple class into NSUserDefaults.
- Swift has some quit restrictive control over class variables, check more here
- Test with Quick is quick and pleasant
- Knowing iOS and ObjC prior to change to Swift really matters