06 Apr 15
14:24

NSKeyedArchiver performance and custom class alternative

NSKeyedArchiver is a common choice for saving data of medium complexity, alongside plist serialization. More complex and you might use CoreData, less and you might use NSUserDefaults. I use NSKeyedArchiver in my Voicer app to record the metadata for sound. For the metadata I tried to design the format so that it would be very responsive, by only keeping index markers to audio samples in external files. However, I noticed for long sounds that were over an hour, it could take around 10 seconds to load just the metadata.

I thought about it longer, and decided that the reason for this must be the key-based lookup. So, I went ahead and removed all the keys (this is generally safe to do as long as you encode and decode in the same order). But, performance did not increase. I then tried using NSArchiver, which is listed in the mac os x docs as deprecated, and is not even mentioned in iOS docs. It is much faster. The only reference I found to it was an older StackOverflow post from 2012. However, using it is problematic, because presumably, it counts as private API which could be rejected on app submission.

It's convenient!

It’s convenient!

I put it down for a long time, but recently came back to it – I fixed up and renamed the Cocotron NSArchiver class to NSKeylessArchiver so that it could exist along side the private one. The results are okay, but could be improved.

Writing directly to a file would be faster for my case, but I’m sticking with the initWithCoder method for now, because it is less work, but also for maintenance – the encode/decode functions are nice, and it’s easy to have backwards compatibility with NSKeyedArchiver by checking a file extension to determine the right Unarchiver to use. The NSKeylessArchiver also does some fancy but mostly-cheap stuff, such as string and object references – if you use the same string twice, it will only occur in the archiver once, and used as a reference the second time.

Here is the github repo and the readme:

How to use

Add NSKeylessArchiver and NSKeylessUnarchiver’s .h and .m files to your project. If you have an arc project, you will need to go into the "Build Phases" tab of your project settings and add the ‘-fno-objc-arc’ flag to both NSKeylessArchiver.m and NSKeylessUnarchiver.m.

Then, just #import "NSKeylessArchiver.h" and use it like an NSCoder. See the performance test project below for a sample.

When to use

If you have a moderate to large use case for NSKeyedArchiver that does not rely on keys (e.g. uses only -[NSCoder encodeObject:] and -[NSCoder encodeObjectOfObjcType: at:] as opposed to -[NSCoder encodeObject: forKey:]), then you may be able to use this. The keyed based encoding and decoding is much slower, because it allows for random access. The non-keyed method allows a faster linear, forward-only read and write because it requires the order of encoding and decoding to be fixed. Mac OS X has a deprecated NSArchiver/NSUnarchiver, and iOS has this, but is not a public API.

How do I get backward-compatibility without keys? Forward-compatibility?

A frequent cited advantage of NSKeyedArchiver is that the keys provide easy backward-compatibility. It certainly helps, but of course, backward compatibility can exist without keys.
If you store a version number, you can still add and remove new objects/data, and check the version number on load to determine whether or not to decode or not. If this is not clear I will expand on it later.

Forward-compatibility via keys is much harder, but in the ‘save user data’ case, you may not need it – the only way that you will need to load a file from a future version is for cloud-based methods.
If you do need it, it is possible to implement schema for forward-compatibility with NSKeylessArchiver, by using keys implicitly in an NSDictionary (which you should be able to use with NSKeylessArchiver!). The other options are probably too messy, and likely remnicent of file formats that specify a byte length of each component and subcomponent, in which case you should just write your own binary file.

Performance

First of, if fast-as-possible performance is your goal, you might consider something else, such as directly writing to a binary file. This is a trade off for having the convenience of having a drop-in replacement for compliant uses of NSKeyedArchiver.

For a test case, I compared NSKeyedArchiver, NSKeylessArchiver, and NSArchiver with a simple root object with 20000 ints. Please feel free modify the test repo and this class to improve performance and correctness. This was the result of running a release build on an iPhone 5S, with 10 runs per class:

encoding (min/max/avg secs) decoding (min/max/avg secs)
NSKeyedArchiver 0.2048/0.2453/0.2165 6.8919/6.9238/6.9037
NSKeylessArchiver 0.0407/0.0506/0.0451 0.0253/0.0330/0.0287
NSArchiver 0.0094/0.0114/0.0102 0.0019/0.0025/0.0020

As you can see the performance for this unrealistic use case shows NSKeylessArchiver doing much better than NSKeyedArchiver, but worse than NSArchiver. NSArchiver is a better choice, but apps that use it may not pass iOS app review due to the private API status. If you know otherwise, let me know.

In practical use, I have seen about a 2-5x speed increase for a use case that had ~20,000 ints/floats/objects being encoded/decoded.
It seems logical that the gains will be larger for encoding schemes that have a larger ratio of encoding calls to actual data being encoded.

For reference:

03 Apr 15
22:49

The Magical Bookstore

One of the joys in life is walking into a bookstore, after being away from bookstores for a good amount of time. I can’t quite say why it is so nice, but I have always felt this way. Recently, I came across an interesting book that made me think about this puzzle. The only thing I can remember about the book is its title. I don’t even remember seeing the book – perhaps I saw it as an online recommendation or reference at the bottom of an article. But it has stuck with me, in a nagging and profound sort of subtle that has me thinking there really is something magic there.

Cow Books in Naka-Meguro, Tokyo

Cow Books in Naka-Meguro, Tokyo


The title of the book is 「なぜ本屋さんに行くとアイデアが生まれるなのか?」(Why is it when you go to a bookstore, ideas are born?). I don’t plan on reading it, at least not for a while. I sort of like the mystic that it provides the bookstore, and the unanswered prompt it gives me for understanding the true values of my own consumerism. That is to say, while there is certainly some connection between books and new ideas, the author clearly asks about bookstores and not other places where you can get free books, such as a friend’s house or a public library.

This makes me recall my first two-year stay in Tokyo, just after I had graduated from college. I integrated where I wanted to, but at the end of the day the nuanced stresses of culture shock led me to foster what I believe is a queer consumerism designed to help keep me grounded in a somewhat foreign land. I would go to Jimbocho, which is the famous book’s quarter and visit many shops on a regular basis. I would search for first editions of classic literature, such as Joyce or Hemmingway. I was a student working a part-time job to fund my entire stay, so I would usually just look at the dusty old tomes through glass and protective plastic, but sometimes I would buy one at a premium rate, this being Japan. Somehow it meant something to me that the books were priced so highly. I think I felt like the money was a token of respect that I could offer to dead authors – that the price valued the book more than normal.

Christopher Alexander’s best known book is still a rare find at bookstores (amazon link)

If I were to visit Japan and go back to the same bookstores today, I would be able to buy many more books than I could have before. This buying power somehow reduces the experience, and my 25-year old self wouldn’t have liked that. Although I don’t have the desire to buy many first editions nowadays, I can still understand what I was doing then. Price labels and and implicit value (positioning and presentation) are such an important part of walking into a bookstore. This is just one part of why bookstores are special places.

Last week, I walked into Pegasus Books on Solano Ave in Albany (California). I was elated to find the architect Christopher Alexander’s The Nature of Order and The Process of Life. His books are always very expensive, even used, perhaps because they are rarer. It is sort of my automatic action to search for Alexander’s books every time I enter a used bookstore. I plan to write more about his Pattern Language, and A Synthesis of Form later. But for now, I will just mention with amazon links that these books were very influential architecture books from the 70s that mesh well into programming practice, and have played a role in influencing the Gang of Four’s Design Patterns.

10 Jan 15
19:06

Xcode: Semi-stale libraries and the simulator

Edit: (2/8/2014) As usual, I’m an idiot – this problem was caused by having explicit linker library search path flags (e.g. -LTo/My/Old/AdMobDir) that took precedence over the new libs I added. It didn’t matter that I removed the old ones from the Xcode and git, because they were (oddly) specifying an external directory that had […]

Debug the rest of this issue »

05 Jan 15
22:30

2014 projects

my barebones background app

my background noise app

It’s been a while since I posted an actual project I’m working on. Since I started working full time for Apportable, I’ll admit there is less time to work on projects, but that’s really no excuse. In addition, as the new year merits such reflection, I’ve been thinking about why I didn’t ship anything last year.

Then I realized, I *technically* shiped something. I shipped my children’s note teaching app (that was already on iOS) on google play using the Apportable framework. Apportable lets you compile your objective-C iOS project to android and run natively. I thought it was lies and insane when I first heard about it; now I can confirm that it is simply black magic. Anyway, I’m usually working on other company’s apps, and improving the Apportable platform where it is is not perfect, so this was a nice way to hit two birds with one stone.

I also started on a binaural beats app, and got a quick prototype, but it didn’t seem as useful or original enough to publish without specific controls, so I’m still working on that. Instead, I put my efforts into a ‘background noise’ app. I started on this because I wanted to be able to drown out the sounds of the 580 and BART that my midcentury apartment building windows are not rude enough to prevent passage into my bedroom. It’s made me remember how much I love the sample synthesis for-loop in an audio callback. (I want to post more on various synthesis techniques in the future). So far the app only has three background noises – rain, waves, and crossfaded white noise. To stay productive, I might just release it as such and develop further as my interest stays with it. To the right is a screenshot of the minimal gl/coregraphics based rendering to accompany the background noise. I’ll try and do a live coding screencast just to get some of it on the screen again.

With the full time job, I can work on all those financially-worthless-but-fun ideas that are musing around in my head that were out of the question when I was doing the solo contract-dev shop. Just to be clear, I am posting this so that I will feel embarrassed if I don’t take advantage of that freedom when I come back and check on this blog.

18 Aug 14
16:50

Cisco AnyConnect VPN problems on android:

I’ve been using cisco VPN on android. It works pretty well, but sometimes, I would run into an issue where DNS wouldn’t resolve while connected (e.g. google.com fails to load in chrome and says DNS host lookup fail). This would happen and leave me out of luck for a week for that device. The internet, […]

Debug the rest of this issue »