Saturday, February 19, 2011

NSBundle vs. UINib performance

Amongst many wonders in iOS 4.0 you will find a class named UINib. It's purpose - to optimise the loading time of nibs. It has been rumored to be much faster than NSBundle. In this post I set out to compare the loading times for nib files using good ol' NSBundle (or to be more precise the methods provided by the "UINibLoadingAdditions" NSBundle category) and the younger UINib.


I've created a simple test case in which a table view displays cells that consist of 2 labels. The cell has been designed in Interface Builder and saved in it's very own nib file. Once the table view asks for a cell we load the nib file, instantiate the object graph and voilĂ ! We end up with a beautiful table view displaying 11 cells. This also means that my table view will need to create 11 instances of my cell. I better find the most optimal way to load that nib!


I start out by using the NSBundle loading mechanism. In my - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method I load the nib, measure the time it took to load, find the table view cell and set some values for its 2 labels.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ExampleCell"];

if(cell == nil)
{
NSDate *startDate = [NSDate date];
NSArray *bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"ExampleCell" owner:nil options:nil];
printf("%f\n", [[NSDate date] timeIntervalSinceDate:startDate]);

for(id obj in bundleObjects)
{
if([obj isKindOfClass:[UITableViewCell class]])
cell = (UITableViewCell*)obj;
}
}

[(UILabel*)[cell viewWithTag:1] setText:[NSString stringWithFormat:@"%d", [indexPath section]]];
[(UILabel*)[cell viewWithTag:2] setText:[NSString stringWithFormat:@"%d", [indexPath row]]];

return cell;
}


I ran the code 3 times on an iPhone 4. On average the time it took to load each of the 11 cells in milliseconds was:

0.016110333
0.006223667
0.004825
0.004784333
0.005055667
0.004709333
0.004510333
0.004458667
0.004389
0.004378333
0.004712667

Notice how the first loading takes a considerably larger amount of time than all the next ones. I haven't found any information mentioning NSBundle doing any sort of caching so I'm presuming the reason why every subsequent call takes less time is due to the underlying file system methods it uses.



Next, I replaced NSBundle with UINib like so:


if(cell == nil)
{
NSDate *startDate = [NSDate date];
if(!nib_)
nib_ = [UINib nibWithNibName:@"ExampleCell" bundle:nil];

NSArray *bundleObjects = [nib_ instantiateWithOwner:nil options:nil];

printf("%f\n", [[NSDate date] timeIntervalSinceDate:startDate]);

for(id obj in bundleObjects)
{
if([obj isKindOfClass:[UITableViewCell class]])
cell = (UITableViewCell*)obj;
}
}


Notice how the UINib has the loading and instantiation methods seperated. This provides an immense performance boost, because we only have to load the nib file from disk once and with each subsequent call to the table views delegate method only instantiate a new object graph using the cached nib data.

Same as before, I ran this 3 times and here is what I got:

0.015249333
0.002955
0.002447
0.002064667
0.002092667
0.001903
0.001851667
0.001981
0.001852667
0.001873333
0.001941667

Again, the first loading time is much higher than the subsequent ones.



So how do the 2 compare?



We can see that both start out at a similar point. With each subsequent call both UINib and NSBundle loading times remain at their respective levels. It's at this point where it becomes obvious that UINib beats NSBundle hands down. The fact that UINib keeps nib data in memory proves to be it's winning point if you're instantiating the same nib more than once.

1 comment:

  1. But the NIB data would be in memory anyway (in the kernel's filesystem/unified cache, to be precise). The performance gained can't be in the small overhead of application<->kernel switches, can it?

    ReplyDelete