Weak Referencing Dictionary
Kolin Krewinkel, the talented developer behind the App.net client Stream, shared an interesting article on NSMapTable tonight. In the article, Charles Parnot subjects NSMapTable to a battery of tests to see if it performs as promised. The results were surprisingly disappointing.
For those not familiar, NSMapTable is best described as a mutable dictionary with weak referencing capabilities. Typically, you use a map table when you need a collection of objects in which each object persists as long at least one other object in your app has a strong reference to the it.1 For example, if you were writing an App.net client, you might want to store a local cache of user objects in a map table. As long as at least one post or profile view controller keeps a strong reference to a given user, the map table will keep a reference to the user, too. In short, it’s a way to keep things around as long as you need them, without having to manually keep track of when you no longer need them.
The problems that Parnot uncovered are related to what happens after all other objects have released an object that is a member of a map table. The expected behavior is that both keys and values for these objects will be released. In practice, the results are unpredictable. Often, only half of such objects actually get removed from the collection. The rest are just hanging around and may never get purged in a non-garbage-collected environment like iOS.
On a whim last year, I tried writing an alternative to NSMapTable that would perform the same function, but that would be able to run on iOS 5 (NSMapTable is only available on iOS 6 or later). I put the result up on Github. It’s a funny little thing. It was written before my morning coffee, so I don’t recommend using it in a production app unless you absolutely need to. Nonetheless it worked as expected in my limited tests. Here’s how it was put together:
JTSWeakReferencingDictionary
The technique begins by adding an object via a category method on NSMutableDictionary, jts_setWeakReferencedObject:forKey: You add the object you want to be weakly referenced with this method instead of using the standard method.2.
This method does not add the object to the dictionary directly, but instead wraps the object in an NSValue using valueWithNonretainedObject. Later on, this plays a crucial role.
Just before adding the NSValue from step 2 to itself, the dictionary sets a custom property on the object passed into step 1 (the object we want to weakly reference). This is accomplished via associated objects, a form of voodoo too supernatural to cover here. The custom property is a strong reference to an instance of JTSDeallocNotifier, a subclass of NSObject that notifies a delegate whenever it is about to be deallocated. The mutable dictionary sets itself as the delegate of the dealloc notifier.
So at this point, each object passed into step 1 is given a JTSDeallocNotifier and wrapped in a weak-referencing NSValue. The NSValue is then added to the mutable dictionary.
As long as at least one other object in your app retains the weak-referenced object, it will persist via the NSValue as a member of the dictionary.
When all strong references to the object are broken, the NSValue’s weak reference will be zeroed, and ARC will begin the process of deallocating the object and cleaning up the object’s own references. During this process, the object’s JTSDeallocNotifier is also deallocated since no other object retains it.
Inside of JTSDeallocNotifier’s dealloc implementation, it notifies its delegate that it is about to be deallocated.
The mutable dictionary receives the message from the dealloc notifier. The dealloc notifier has a key that is a copy of the key passed in step 1 when the original caller added the weak-referenced object to the dictionary. Using this key, the dictionary knows which NSValue it should remove from itself.
The dictionary removes the NSValue for the key from step 8.
At this point, ARC finishes its deallocation steps, and the weak-referenced object, its dealloc notifier, and the NSValue are all released.
I ran brief tests of this technique with collections of 10 to 20 objects, using GCD and blocks to experiment with timing. Everything worked as expected.
The Indie Ocean
My finger-to-the-wind says that apps and services aiming for mainstream consumer appeal, which were already impossibly hard, are only going to get harder in the coming years. There’s too much noise, and attention is fleeting. Any company that hasn’t started by now has already lost.
But on the other hand, iOS is a rapidly-maturing operating system. It’s officially seven years old today. The latest round of iPads and iPhones have practically removed all hardware barriers limiting what software can do.
In other words, while the tides of attention-based, multi-million-dollar startups churn red with the blood of all the contenders, I think there’s a vast blue ocean waiting for independent developers to make prosumer and pro software. I look at an app like Editorial by Ole Zorn and think, “My god, that’s where the rest of us should be going.”
Mass market, VC-funded companies can’t ship those kinds of apps. Apple’s apps will never be all things to all customers, either. But many people are still going to need advanced tools to get through their day-to-day lives. Not just writers, but first grade teachers, speech-language pathologists, youth pastors, session guitarists, ICU nurses — everyone.
I think that success will come to folks who can figure out how to ship “maximum viable products” on day one, without venture funding and without going bankrupt in the process, apps that are carefully designed and built to solve real problems for real people.
Unread App Status Report
The Polar Vortex of 2014 has been a boon for Unread. Icy midwestern weather trapped me indoors and blurred the week into one, uninterrupted, very productive Work Day. I cleared the last must-have feature from the Version 1.0 todo list a few days ago. Only a few stepping stones remain between here and App Store submission. If you have any questions not answered below, feel free to ask me.
Fixes and Polishing
The private beta has been a relief. The testers have sent fantastic notes and bug reports. Most of their concerns are fixed and ready to ship. Fresh eyes are indispensable after you’ve spent six straight months staring at the same screens. The app is on feature lockdown from now on: no new features until after App Store submission. I’m obsessively polishing the smaller details. The best news I can give you is that the testers are really enjoying the app. I hope you will, too.
Feedly Integration
Technically, I’ve integrated Feedly into Unread already, but I only have access to the development sandbox. I’ve requested access to the production environment so that any Feedly user can sign in with Unread. If for some reason Unread is not approved for production access, then I will submit Version 1.0 without Feedly, trying again later. I requested access very recently, however, so I’m confident that Feedly integration will be in the 1.0. This process may end up pushing back the 1.0 several weeks. Unread also works with FeedWrangler (which I use personally) and Feedbin.
App Submission Collateral
I need to assemble the items required by the App Store, the usual bric-a-brac: screenshots, an app description, et cetera. Easy stuff.
Pricing Announcement
I’ve finally chosen a price: Unread will be available at an introductory price of $2.99, for a limited time. I know the tides have turned in favor of freemium pricing models, but those models work best for:
- Apps that connect to their own proprietary services. Unread connects to other companies’ services. There are no server-side features of my own that I could charge for.
- Games with consumable purchases. I could charge you $0.99 cents for every article you read or share, but I don’t think I could stomach the one-star reviews.
I considered using the free-with-pro-upgrade model we use in Riposte, but it doesn’t make sense for an RSS reader. Riposte’s biggest challenge is drawing customers to App.net, which has a much smaller network of users.1 RSS, while not as popular as it once was, is an established medium with plenty of existing users. Hopefully there’s a good number of them who aren’t afraid to spend a few bucks on a new app — especially one that tries very hard to be a fresh take on an old idea.
-
App.net also has the Developer Incentive Program, which is a nice bonus. ↩
Pull-To-Do-Action
Loren Brichter, inventor of the now ubiquitous pull-to-refresh control, interviewed in an article by Fast Company called “Why the Pull-to-Refresh Gesture Must Die”:
Brichter, however, feels that it’s high time his gesture evolves. “The fact that people still call it ‘pull-to-refresh’ bothers me—using it just for refreshing is limiting and makes it obsolete,” he says. “I like the idea of ‘pull-to-do-action.’”
If you like the sound of that, you’re gonna love Unread.
Solving the App Store Discovery Problem with App Playlists and Good Taste
It’s common knowledge that there are too many apps on the App Store, with no reliable way of discovering the good ones. It’s like panning for gold: piled on every hidden gem is a heap of worthless lumps.
Most of the attempts to solve the problem lean on shoddy software algorithms. They’re blunt tools at best, and games for crooks at worst. From “What’s Hot” to the top free/paid/grossing sections, the algorithm-based lists of apps have spawned a cottage industry of companies like TapJoy and AppGratis, which promise to pump up downloads in exchange for thousands of dollars from desperate developers. There’s also the “Genius” recommendation engine, which isn’t vulnerable to app marketing tricks, but in my experience it has never produced a recommendation that matched the quality of a recommendation from a friend with good taste.
Taste is the missing ingredient in App Store discovery thus far. People with good taste, whether in apps or otherwise, nourish their taste by continually looking for things that stretch the boundaries of what they know and like. I don’t know if I have good taste, but I try to. When a friend first played Tom Waits for me, I was still listening to godawful Dave Matthews Band and Korn. I hated Tom Waits. Eventually I learned how to love him. Tom Waits’ work is music about music. When my mind finally snapped into that higher register, my taste expanded. I learned a new way of listening.
Your taste in apps can grow in the same way, but no algorithm can take you there. Only the good taste of a friend can help. This is why the App Store needs “app playlists,” a list of all the apps currently installed on the iOS devices of anyone with an iTunes account — one playlist per account.
Consider the failure that was Ping. Ping’s failure is a lesson to learn, not a fate to repeat. Ping tried to be too many things, worst of all a new social network. It missed the crucial element that could have made it powerful: taste. As my brother pointed out to me, Ping should have been nothing more than a way to snoop through my friends’ album collections, with a buy button next to each one, and a “Buy All“ button at the top. We improve our taste by absorbing the tastes of people whom we admire.
App playlists should be rigorously simple: just a list of apps. Not all the apps ever downloaded, but the apps that a given user currently has installed on their device. The assumption is that if somebody has an app on their device, they probably like it. App playlists should be given top-level priority via their own tab in the App Store. When selected, that tab should contain two main screens: one for recent activity, and one for a list of all the people you follow. When viewing a playlist, you should be able to buy each app individually, or buy them all with a single tap of a “Buy All” button at the top of the screen. The playlist view should look just like the current “Purchased” screen in the Updates tab.
App playlists would succeed where other attempts have failed because they would be effortless. Even the most esoteric high-quality app is downloaded by the developer’s friends and Twitter followers. It would take no extra work for these “promoting” users to share their new discoveries. Simply downloading an app and keeping it would suffice.
There are a number of ways Apple could build the social graph behind this. They could do it the same way they built Game Center, whose popularity is the strongest counterargument to any Ping-hating naysayers. Alternatively, Apple could require linking to a third-party account like App.net or Twitter — App.net is especially poised to back this kind of service, with its powerful, open-ended API. However Apple builds the social graph, it should only be used to keep track of following status. There should be no comments, likes, reposts, or anything of that nature. The problem of App Store discovery is not due to a lack of social media coverage or bad algorithms. The real problem is a failure to empower people to share their good taste.