• invisible

Browsing ‘App Development’


App Store Analysis Part 2: Apps

June 16th, 2010 by

The exact same plot as I made for app developers can be made for the apps themselves. Check it out below, and Shotgun Free at #32. Most of these apps shouldn’t be surprising, though I do find it crazy that Facebook has so many more ratings than the next highest, some 2.5x the #2. Wow.


App Store Analysis Part 1: App Developers

May 31st, 2010 by

Having made my claim that ratings are a good indicator of downloads I’m going to look at the top app developers. As of a week or so ago 39,175 developers have at least 1 application on the store. I’ve compiled the Top 100 app developers and included them in a plot below. Check out Inedible Software at #68. There are two things I find interesting about this data:

1) The top app developers are gigantic. Gameloft has just about 2.3% of all ratings, which means they probably have near 2.3% of all downloads. Digital Chocolate is almost as large, with about 2% of all ratings. We ourselves have 0.23%, which from Apple’s stated total number of app downloads is just about spot on.

2) The top 100 app developers have 40% of all ratings! Looking down a bit further, the top 170 control 50%, which means that the bottom 39,105 developers have as many downloads as the top 170. Tough market.


App Store Analysis Part 0: Ratings vs. Downloads

May 27th, 2010 by

I’ve been working hard the past couple of weeks to scrape a whole bunch of data from the iTunes App Store. Before I ask what I think are some interesting questions, however, I’d like to state why I think the number of ratings for an app is a very good indicator of number of downloads. Three reasons:

1) You can’t rate an app if you haven’t downloaded it. Some percentage of those who download an app are going to rate it, and so it stands to reason that the more downloads an app has, the more ratings its going to have.

2) We’ve found for our own applications that the number of ratings goes up quite linearly with the number of downloads. The rate isn’t the same from app to app, but its the same to about an order of 2.

3) I made a nifty plot of the number of ratings for the top 3000 entertainment apps in the order of popularity. The ordering by popularity is given by Apple and I have no idea how its calculated, but it definitely weights number of downloads very very heavily but also takes into considerations things like price and the current Top 100 standing.

Ratings by Popularity

The reason why this claim is important is that Apple is exceedingly stingy with download numbers. It’s even a pain for us to find our own! To analyze the market, however, all we need are relative download numbers. And if ratings are a good indicator of downloads, then relative rating numbers should make us good to go.


Bazooka Released!

May 14th, 2010 by

Inedible Software is pleased to announce the release of its first iPad application Bazooka. Bazooka is a virtual rocket launcher, complete with smoke and fire particle effects and high quality sound. Users hoist Bazooka up to their shoulder and pull back to fire off a blast. Due to an initial rejection by Apple for containing “minimal user functionality,” users also have the gratuitous ability to choose between several background images, including a bunny rabbit and outer space.

Bazooka is currently available for $0.99 on the iTunes App Store, exclusively for the iPad.

Check out Bazooka on the iTunes App Store!

[download press release here]


Speeding up Tables with Section Headers

January 22nd, 2010 by

I’ve been working recently on a table with section headers that had really slow scrolling speeds (around 30 FPS). After a lot of digging, I found the problem in a strange place, so I thought I would share in hopes that someone else can save some time.

The gist of the problem is that when you override tableView:viewForHeaderInSection: it ends up being called constantly when you scroll. This in turn calls tableView:titleForHeaderInSection, and if that’s slow, you’re going to see serious lag.

What can unexpectedly make that call slow, however, is the standard:
[[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:section];

What dramatically speeds it up, however, is declaring your own NSArray *sectionHeaders and setting it in your init as so:
sectionTitles = [[[UILocalizedIndexedCollation currentCollation] sectionTitles] copy];

I found that referencing this instead of the indexed collation dramatically sped up my scrolling speed, and now I’m whisking along at an acceptable speed (60 FPS).


Integrating the iPod in OS 3.0 (2)

September 10th, 2009 by

I have a few more tidbits that might help anyone else trying to integrate the iPod in OS 3.0.

After a lot of experimentation, I’ve determined that there are vastly different times that certain data takes to load into memory. You can use this to your advantage. One example, building off my last post, is that a MPMediaItem’s persistent ID (pID) loads much more quickly than it’s other properties. What this means is that, if you’re forced to load other properties from memory, you can cache the information about each item in a NSMutableDictionary with the keys being pIDs. You can then save this out to disk on applicationWillTerminate, load it in on the next run, grab an item’s pID, check the dictionary, and get a huge load-time decrease if you’ve already seen it.

Another trick that has helped me out a lot is creating an MPMediaItemCollectionWrapper class. I init one of these classes with an MPMediaItemCollection and then, in a thread-safe way, lazy-load absolutely everything. This is made easy by having every necessary property be a read only property with an overridden getter. Here’s an example:

/* Album Title */
- (NSString *) albumTitle {
  @synchronized(self) {
    if(albumTitle == nil) {
      [self.lock lock];
      NSDictionary *appInfo = [self.cache objectForKey:self.pID];
      [self.lock unlock];
    }
    if(appInfo == nil) {
      loadedFromDisk = YES;

      albumTitle = [[self.representativeItem valueForProperty: MPMediaItemPropertyAlbumTitle] retain];
      artist = [[self.representativeItem valueForProperty: MPMediaItemPropertyArtist] retain];

      [self.lock lock];
      [self.cache setObject:[NSDictionary dictionaryWithObjectsAndKeys: albumTitle, @"Title", artist, @"Artist", nil] forKey:self.persistentID];
      [self.lock unlock];
    } else
      albumTitle = [appInfo objectForKey:@"Title"];

  }

  return albumTitle;
  }
}

All of the wrappers share the same lock and cache, so nothing has to unnecessarily access disk. Additionally, I’ve discovered that once you access one of the non-pID properties the others come for free, so I cache both the artists and the album title at the same time. The getter for the artist looks very much the same, so the cache gets created from whichever loads first.

The other huge benefit to having a wrapper is the ability to have that albumTitle selector for sorting purposes. Apple now gives us a UILocalizedIndexCollation class to help us create a table index, but the only way to use it is if the object being sorted has a single selector from which it can be sorted.

I don’t, however, use albumTitle. I instead use this:

/* Sortable Album Title */
- (NSString *) sortableAlbumTitle {
    @synchronized(self) {
    if(sortableAlbumTitle == nil)
        sortableAlbumTitle = [[self stringByRemovingLeadingTheFromString:self.albumTitle] retain];
    return sortableAlbumTitle;
    }
}

Pass that along to your collation and you can easily index all of your MPMediaItemCollections in your table in the same way Apple does (without the leading “The”).

That’s all for now. I hope this ends up helping someone else who ends up trying to recreate swaths of the iPod app in their own application.


Integrating the iPod in OS 3.0

September 1st, 2009 by

One of the OS 3.0 upgrades we were psyched about was iPod integration. I’ve been playing around with it a lot for several of our upcoming projects, and I have some comments that might help people doing the same.

When you use the iPod app, every tab looks amazing and scrolls smoothly. When you first setup your own music browser, however, you’ll see serious lagging and bad scrolling speeds in your UITableView.

The reason for this is that, even though you might have an MPMediaItemCollection, you don’t actually have the MPMediaItems in memory. So when you query a bunch for MPMediaItemPropertyArtist, for example, you’re going to see serious lagging and it loads that data into memory.

Apple, I’m guessing, prefetches and caches everything, so if you want to see smooth scrolling you should probably do the same. Once you load an MPMediaItem into memory once it stays there and every subsequent request to it is lightening fast.

I today came up with an easy prefetch that rests somewhere between hacky and elegant. I put something like this off the main thread after launch.


MPMediaItem *rItem;
NSString *pID, *title, *artist;

for(int i = 0; i < [query.collections count]; i++) {
  rItem = [[query.collections objectAtIndex:i] representativeItem];
  pID = [rItem valueForProperty:MPMediaItemPropertyPersistentID];
  title = [rItem valueForProperty:MPMediaItemPropertyAlbumTitle];
  artist = [rItem valueForProperty:MPMediaItemPropertyArtist];
}

Note that you're not actually storing anything, just calling the item up into memory to query it. What's nice about this solution is that, if your prefetch gets to an item first, your table's request for that information will go lightening fast and thus provide smooth scrolling. If, however, your user gets there first, then the table will lag a little (which is unavoidable) but when the prefetch gets there it won't waste time pulling it into memory again.


NSNotification Troubles

July 18th, 2009 by

We’re big fans of loose coupling and so make use of NSNotifications.

In Shotgun Free / Pro, for example, the achievements class listens for a notification to know when the shotgun has cocked, notes the time, listens for a notification to know when the shotgun has fired, notes the time, and then calculates the fire time as the difference between those two times.

This method was really simple to implement because the achievements class can exist essentially on its own, just listening and reacting without talking to any other classes. Sounds good, right?

Wrong. Unfortunately there are no guarantees on when those notifications are delivered. Everything works fine 99.9% of the time, but then once in a thousand fires the cocking notification won’t be delivered until just before the fire notification, giving you an absurdly fast fire time that sits around on your high score board forever. Oops.


WiFi Edge Case

April 25th, 2009 by

James and I discovered a strange edge case a few days ago that I figured I’d blog about. The long story short is that the iPhone will auto-join hotspots with pay portals, such as at Starbucks, Kinkos, or the airport. The problem is that the iPhone thinks the network connection is active, but it can never get valid data back from the server.

One effect of this you may have noticed is that when you go to get your email in the airport, the spinner just goes indefinitely. This has gotten me a few times, and I have to remember to turn WiFi off.

More importantly, if your program doesn’t handle this effectively, bad things can happen. For an example, with the MobClix ad library you temporarily see the login screen from the ad bar and then after twenty seconds the app crashes. Whoops.

In any case, be careful.


UIScrollView Paging Hack

April 5th, 2009 by

The paging property in UIScrollViews is pretty handy: for those unfamiliar, it’s the great snapping feature when leafing through images in your photo collection, for example. One unfortunate limitation is that the UIScrollView, as provided, only lets you snap on multiples of the frame size. We wanted to lay out smaller previews of images so you could see the previous and next images as well, something like this:
scroll1.png
It’s easy enough to lay out the images, but if you turn paging on then you end up snapping on every other item. The solution we use is to move our content within the scrollview so that it lines up properly when snapped, like so:
scroll2.png
This method is very simple to handle in the code: just put all the subviews into a container, set yourself as the scrollview delegate, and use the scrollViewDidScroll: callback method to set the CGAffineTransformTranslation of your container view to half the current content offset of the scrollview.

- (void) scrollViewDidScroll:(UIScrollView *)scrollView {
  CGFloat offset = scrollView.contentOffset.x/2;
  CGAffineTransform transform = CGAffineTransformMakeTranslation(offset, 0);
  [contentView setTransform:transform];
}


The view no longer scrolls one-to-one with your finger, but flicking works quite well.