If you follow me on twitter you must have seen my most recent rant:
Argo vs JSON Parsing "by hand" with +1M objects. Argo -> 3.05s | "by hand" -> 0.089s. This is worrisome.— Rui Peres (@RuiAAPeres) November 15, 2015
And the memory footprint:
What worries me the most is the memory footprint. The red circle is from the one "by hand". pic.twitter.com/Phr2Nqq4AV— Rui Peres (@RuiAAPeres) November 15, 2015
The final test when I had everything in place:
In my particular case, Argo was suddenly the bottleneck of the entire app, taking almost 13s on an iPhone4S. Clearly, doing performance tests on a Simulator powered by a MacbookPro is meaningless. May that serve as a lesson.
This left me with a couple of options. Find a different library to handle this1, or do it myself. Since the measurements from the demo project gave me some promising results, I went with the latter.
The demo project, in case you haven't had a look, simply extracts the values from a dictionary, tries to cast it and finally creates the object. It looks like this:
I kept two main ideas from Argo:
The new protocol called
Mappable looks like this:
You can see that it's pretty similar to the Decodable protocol.
mapToJSON(), is used to go the other way around (as expected).
A real example:
With all this in place I ran my measurements and I was baffled. I only got an improvement of 200%. I was expecting at least something in the order of 10 times faster.
Pushing it a bit further
The following tips was what allowed me to push the performance from 2x to almost 11x:
- I was checking
let d : o as? [String: AnyObject]on the first line of every
AnyObject). Apparently you don't actually need to. You can assume your
[String: AnyObject]and use it as if it was one:
guard let s = o["key"] as? String. This was one of the biggest bottlenecks with this new approach.
- Remove every
forEach. They are fast, but nothing beats an
ifand good old
for var i = 0, condition; ++i. Yeah the code won't look as good, but performance in this case was more important.
- Don't use Result's
valueto get the underlying object. It's faster if you use
guard case .Success(let v) = result. Again, it's not by much, but every bit counts.