Advanced programming tips, tricks and hacks for Mac development in C/Objective-C and Cocoa.

Streaming MP3/AAC audio again

This week I present some further additions to AudioStreamer, a class I first presented in Streaming and playing an MP3 stream and revisited with bug fixes in Revisiting an old post: Streaming and playing an MP3 stream. This time, I'll add the two of the most requested features: seeking and HE-AAC audio support.

Introduction

The AudioFileStream and AudioQueue APIs on the Mac and iPhone are useful but tricky. They are much lower level than AVAudioPlayer and MPMoviePlayer (iPhone) or QTMovieView (Mac) but are not as low level as the AudioUnits and AUGraph.

As the only Apple-provided way of streaming audio to the iPhone without a fullscreen movie player, these APIs are important.

The full AudioStreamer class, along with projects for the Mac and iPhone, is available on github: AudioStreamer (you can also browse the source code repository).

HE-AAC

HE-AAC is actually very simple to use on the iPhone but I never took the time to add support for it to AudioStreamer before now because I had no personal need for it.

While it is simple to add, there is one complicating issue: the iPhone Simulator does not support HE-AAC. I'm sure this has confused a number of people who otherwise implemented HE-AAC correctly.

Enabling HE-AAC support for a file is as simple as sending the right AudioStreamBasicDescription to the AudioQueueNewOutput (which creates the playback queue).

err = AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallback, self, NULL, NULL, 0, &audioQueue);

If asbd.mFormatID == kAudioFormatMPEG4AAC_HE, then the AudioQueue will parse the required SBR data.

Of course, there's some added trickiness in knowing whether a given AAC file contains SBR data.

We can get this information from our AudioFileStream when it notifies us that the kAudioFileStreamProperty_FormatList is available. This property contains the list of formats that the stream may be interpreted as.

if (inPropertyID == kAudioFileStreamProperty_FormatList)
{
    Boolean outWriteable;
    UInt32 formatListSize;

    // Get the size of the format list
    err = AudioFileStreamGetPropertyInfo(inAudioFileStream,
        kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable);
    if (err) { // handle error }
    
    // Get the list of formats itself
    AudioFormatListItem *formatList = malloc(formatListSize);
    err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList);
    if (err) { // handle error }

    // Look through the list of formats to find HE-AAC if present
    for (int i = 0;
        i * sizeof(AudioFormatListItem) < formatListSize;
        i += sizeof(AudioFormatListItem))
    {
        AudioStreamBasicDescription pasbd = formatList[i].mASBD;
        if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE)
        {
            // We've found HE-AAC, remember this to tell the audio queue
            // when we construct it.
#if !TARGET_IPHONE_SIMULATOR
            asbd = pasbd;
#endif
            break;
        }                                
    }
    free(formatList);
}

By default, we get the AudioStreamBasicDescription from the AudioFileStream's kAudioFileStreamProperty_DataFormat. This additional code changes to a different AudioStreamBasicDescription in the case of HE-AAC. Since HE-AAC is the only file format that works in this manner, it is the only special case that appears here.

Seeking in an audio file downloaded via HTTP

Seeking requires three steps:

  1. Know the duration of the file to seek within
  2. Know where you can safely seek
  3. Perform the seek itself
Duration of the file

The first might seem easy but unfortunately, AudioFileStream does not give us the duration. We can get the nominal bitrate (only accurate for CBR data), byte counts, packet durations, packet counts and sample rates but none of these give the duration of the file unless you have an accurate bitrate, which we don't.

Instead, I've added a running average bitrate to AudioStreamer. Between BitRateEstimationMinPackets and BitRateEstimationMaxPackets (these constants are defined at the top of AudioStreamer.m), the bitrate is calculated from the packet sizes:

double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount;
return 8.0 * averagePacketByteSize / packetDuration;

Unfortunately, since this average changes over time in all VBR streams, this means that the estimated duration of the file may change by 10% or so for the first 20-30 seconds of playback. In a proper application, you may want to consider an appropriate way to present or conceal this uncertainty for the user.

Seek offset in bytes

The seek offset for a given time can be obtained from the file's duration and the bitrate.

However, it is not safe (particularly in an AAC) to seek to an arbitrary location. You must seek to a packet boundary.

If we want to seek to newSeekTime (a value in seconds), the initial guess at the byte offset for this time might look like this:

seekByteOffset = dataOffset + (newSeekTime / self.duration) * (fileLength - dataOffset);

In this calculation, dataOffset is the start of audio packets within the file, fileLength is the byte size of the whole file and self.duration is the duration in seconds (calculated from the average bitrate and fileLength.

Now we must round this to the nearest packet boundary.

double calculatedBitRate = [self calculatedBitRate];
if (packetDuration > 0 &&
    calculatedBitRate > 0)
{
    UInt32 ioFlags = 0;
    SInt64 packetAlignedByteOffset;
    SInt64 seekPacket = floor(newSeekTime / packetDuration);

    // Ask the file stream for the boundary for the appropriate packet
    err = AudioFileStreamSeek(
        audioFileStream, seekPacket, &packetAlignedByteOffset, &ioFlags);

    // Only use the boundary if it is NOT estimated
    // -- otherwise, stay with our first guess
    if (!err && !(ioFlags & kAudioFileStreamSeekFlag_OffsetIsEstimated))
    {
        seekTime -= ((seekByteOffset - dataOffset) - packetAlignedByteOffset) * 8.0
            / calculatedBitRate;
        seekByteOffset = packetAlignedByteOffset + dataOffset;
    }
}

Notice that we still need to add the dataOffset (the starting offset for audio packets in the stream) to the packetAlignedByteOffset to get a seek-safe byte offset. In my previous post, I mentioned that AudioFileStreamSeek wasn't working for me: it was because I wasn't adding this dataOffset (that you get from the AudioFileStream).

Perform the seek

With the rounding to a packet boundary, the seek is now done at a byte-level on the HTTP connection itself. By this, I mean I close the existing CFReadStreamRef and reopen a new one with a new CFHTTPMessageRef. With seekByteOffset set to a non-zero value, my code for starting the HTTP connection now enters the following conditional:

if (fileLength > 0 && seekByteOffset > 0)
{
    // Set the byte range in the HTTP header for the request
    CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"),
        (CFStringRef)[NSString stringWithFormat:
            @"bytes=%ld-%ld", seekByteOffset, fileLength]);

    // flag used to tell the AudioFileStream that I've skipped to a new
    // location when I next call AudioFileStreamParseBytes
    discontinuous = YES;
}

Obviously, integrating this required a little bit of massaging so that I could restart the HTTP connection without restarting the AudioFileStream or AudioQueue. I do stop the AudioQueue during seeking and restart it when the first new packet is ready to be queued.

Limitations

Different formats

I have tested this code with exactly 1 MP3 file, 1 AAC file and 1 HE-AAC file. There are thousands of variations of these formats which may still break the code. You will need to test your own files and ensure they work.

Testing/debugging note: If you get an error in AudioStreamer, set a breakpoint in failWithErrorCode: — all errors in AudioStreamer should go through this method so you should be able to look at the previous frame in the stack and see what line caused the error.
Progressive download

This class does not cache anything to disk. If you want progressive download instead of live streaming, you'd need to save the data from the CFReadStreamRef or better yet, download the file separately and open the still-downloading file as a CFReadStreamRef and feed it into AudioStreamer.

Multiple audio files at once

The iPhone can only decode 1 MP3 or AAC file at a time. That's a hardware limitation.

The Mac can decode as many as the CPU will allow. Go crazy.

Metadata

There is still no support for ID3 metdata in AudioStreamer, since AudioFileStream does not support it.

If you absolutely need metadata from the file, then the only possible way to get it is to save all the data downloaded up until the first packet and use a different library (like FFmpeg's libavformat) to parse the metadata. It's not an easy task — if at all possible, it would be better to get the metadata another way.

Conclusion

The full AudioStreamer class, along with projects for the Mac and iPhone, is available on github: AudioStreamer (you can also browse the source code repository).

While the code I've added this time is fairly brief, some of it is very hard to find elsewhere (I was unable to find anyone using AudioFileStreamSeek correctly anywhere).

My original goal with AudioStreamer was to handle live streams — MP3 sources without a beginning or an end — so seeking and HE-AAC files were not my priority. However, it appears that lots of people are using the AudioStreamer code as a starting point for their iPhone applications with fixed length files, so I've taken the time here to add the most requested features for these people.

None of these changes should affect files with indeterminate length which should continue to function as before.

Dynamic ivars: solving a fragile base class problem

In the "modern" Objective-C runtime (that's iPhone OS or 64-bit Mac OS X), you can dynamically add ivars (instance variables) to a class without declaring them first. This allows a solution to the common "fragile base class" problem involving ivar layouts. Dynamic ivars can also help data hiding and abstraction and can even create a confusing situation where a base class and a sub class have ivars with the same names that don't refer to the same underlying data.

Introduction

The post is about dynamic ivars in Objective-C. Dynamic ivars exist to solve a common type of fragile base class, specifically, the problem of ivar layouts.

A fragile base class problem is a situation where a minor change to the base class can break subclasses. The ivar layout problem is a fragile base class problem where you can't add an ivar to a base class without requiring all subclasses to be recompiled.

The fragile base class problem with ivar layouts occurs because accessing an ivar requires adding the ivar's offset within the object to the object's pointer. Since a subclass' ivars are always positioned after the base class' ivars, the offset to a subclass' ivars must always be the total size of the base class' ivar region plus their own relative offset. Since these offsets are traditionally fixed and unchangeable after compile-time, this means that you can't change the total size of a base class' ivar area without needing to recompile all subclasses to update their ivar offsets.

The reality is that in most object-oriented languages (including Objective-C before dynamic ivars), adding ivars to established classes while maintaining backwards binary compatibility is practically impossible.

Objective-C in conjunction with the Objective-C modern runtime is one of the few compiled language environments to address this problem.

Dynamic does not mean "at any time": by dynamic, I mean that the absolute ivar layout is not known at compile-time. Really, I'm talking about ivars that will appear dynamically from the perspective of subclasses (to the base class, they will appear like a normal ivar). While additional ivars can be added to a class at runtime, they can only be added before the class pair is registered (i.e. before there are any instances of the class). See the Apple documentation for class_addIvar for more.

The fragile base class problem of ivar layouts

Let's have a look at how the fragile base class problem of ivar layouts can cause problems by looking at an example.

Adding an ivar to a base class would break all subclasses

Consider an example where a writer of dynamic libraries (e.g. Apple writing the Mac OS X or iPhone OS Cocoa libraries) issues a version of a class that is designed to allow subclasses:

@interface LibraryBaseObject : NSObject
{
    NSString *baseObjectIVar;
}
@end

Users of that library class then make their own subclasses of the base class:

@interface UserSubObject : LibraryBaseObject
{
    NSString * userSubObjectIVar;
}
@end

This works fine until the writer of the library wants to add a feature to LibraryBaseObject that requires an additional ivar:

@interface LibraryBaseObject : NSObject
{
    NSString *baseObjectIVar;
    id newFeatureObject;
}
@end

Traditionally, this would break every existing subclass of LibraryBaseObject because no existing subclass would allocate enough memory to hold this new ivar and the offsets to userSubObjectIVar would be wrong because these offsets were compiled to include the old instance memory size of NSObject + LibraryBaseObject and will now offset by the wrong amount.

Yes, code can simply be recompiled with the new headers and all offsets would be corrected to the new values automatically, but until such a recompile, all existing programs that subclass the LibraryBaseObject will break.

Greg Parker's Hamster Emporium: [objc explain] has a good post titled Non-fragile ivars with more diagrams showing the ivar layout problem.
Previous workarounds for the fragile base class problem

The common workaround to the fragile base class problem of ivar layouts is to declare your class like this:

@interface LibraryBaseObject : NSObject
{
    id private;
}
@end

and then actually store all your data in the private class, which you can change in size without affecting the actual LibraryBaseObject since the private ivar will only ever be the size of a pointer.

However, this has three problems:

  • You must have had the foresight to include this private pointer from the beginning
  • It involves two dereferences (see the performance note below where I explain that the dereference is the slowest part of ivar access)
  • In this untyped scenario, it is unwieldy since all your code must cast this private ivar to its actual class before use. You can forward declare a @class and use that instead to eliminate this difficulty.

A fix requires that one of the compile-time values becomes dynamic

Previously, I said that we access an ivar by:

  1. Add the ivar's offset within the object to the object's pointer value
  2. Dereference (read or write from) the memory location referred to by the offset pointer value

This should not be new information to any programmer since Algol: it is the approach taken for accessing data in any struct, record or instance variable in most compiled languages.

The problem is that the offset of any subclass' ivars must include the total size of the base class ivar area and none of this can change at runtime.

Obviously, to fix the problem, something must be changeable at runtime. Objective-C's modern runtime fixes this by making the "size of the base class ivar area" a value that can be looked up at runtime.

The "modern" Objective-C runtime therefore requires that accessing an ivar follows this modified approach:

  1. Add the offset for the subclass' instance value area to the object's pointer value
  2. Add the offset from the subclass' instance area to the ivar
  3. Dereference (read or write from) the memory location referred to by the offset pointer value

Once this is done, the base class' ivar area can grow and the subclass' offsets will shift to accommodate this.

All ivars are dynamic in the modern runtime: Since this procedure is followed for all ivars, that means that all ivars in the modern Objective-C runtime are dynamic in that their absolute offsets are never known at compile-time.
Performance note

You may be concerned that the "modern" runtime makes a very common task (accessing ivars) slower.

Yes, adding the extra offset might slow down some code but for most, the reality is that the difference will be unmeasurably small.

Theoretically, if the extra offset needs to be fetched from the struct that describes the class' ivar layout, then the new approach could double time taken. However, the offset for the current subclass will normally already be stored in the rip register, which is designed for this type of doubly offset dereference and may reduce the impact of the extra offset to zero. Even if the extra cycle to add the offset is needed, it will be a maximum 20% speed impact, assuming the ivar is in the L1 cache (1 cyle impact versus 4 cycles just to fetch from L1 on newer Intel CPUs), less than 10% for L2 (where the fetch takes 10 cycles) and about 1% for main memory fetches.

In addition to this, compilers already eliminate unnecessary dereferencing, so multiple accesses to the same ivar will be optimized so that this additional offset only occurs once.

Synthesizing ivars

The next question is how do we take advantage of this to add additional ivars to an existing, established class? The reality is that since all ivars are dynamic in the modern runtime, you can simply add extra ivars to a base class and it won't cause a problem for existing subclasses.

But dynamic ivars also enable another way of creating ivars that involves even less disruption to existing classes since it doesn't require changing the header file at all — you can use a synthesized property without a matching ivar. This will create an additional ivar in the implementation that doesn't appear in the declaration that other classes see.

For example, if you start with the following class:

@interface MyIvarlessObject : NSObject
{
}
@end

You could add dynamic ivars by altering the declaration to:

@interface MyIvarlessObject : NSObject
{
}
@property (nonatomic, copy) NSString *myProperty;
@property (nonatomic, copy) NSString *anotherProperty;
@end

and add the following to the implementation:

@synthesize myProperty=myIvar; // a dynamic ivar named myIvar will be generated
@synthesize anotherProperty;   // a dynamic ivar named anotherProperty will be generated

With no matching ivars for either myIvar or anotherProperty, the @synthesize statement will generate a dynamic ivar for each and the @synthesize statement becomes the declaration of these ivars.

Since the @synthesize statement acts as a declaration, you can now reference myIvar or anotherProperty in the implementation as though they were regular declared ivars. For example, you could write:

- (id)init
{
    self = [super init];
    if (self)
    {
        myIvar = [[NSString alloc] initWithString:@"someString"];
        anotherProperty = [[NSString alloc] initWithString:@"someOtherString"];
    }
    return self;
}

If you wanted to hide the property declaration or avoid changing the header file at all, you can declare the properties in a private category in your implementation file instead of in your interface file:

@interface MyIvarlessObject ()
@property (nonatomic, copy) NSString *myProperty;
@property (nonatomic, copy) NSString *anotherProperty;
@end

This will need to appear above the implementation block for MyIvarlessObject.

Multiple ivars with the same name

Imagine the following base class:

@interface BaseObject : NSObject
{
}
@property (nonatomic, copy) NSString *propertyOne;
@end

@implementation BaseObject
@synthesize propertyOne=myIvar;
@end

and a subclass:

@interface SubObject : BaseObject
{
}
@property (nonatomic, copy) NSString *propertyTwo;
@end

@implementation SubObject
@synthesize propertyTwo=myIvar;
@end

This base class and the sub class both @synthesize an ivar with the name myIvar.

In a somewhat unusual quirk, the myIvar in BaseObject and the myIvar in SubObject are not the same value — they are actually different ivars, with different offsets within the instance memory area.

This quirk is required to properly address another fragile base class problem: if a @synthesize ivar could conflict with a subclass' ivar, then the base class' ivars would still be fragile. To support this idea, @synthesize ivars are always @private.

However, this does not mean that they are limited to the scope of the @implementation. You can access them in category implementations, provided that the category is below the @implementation that synthesizes the ivar and hence can see the implicit declaration.

It also means that if the BaseObject and SubObject were implemented in the same file, they would not compile. Why? Because if they were implemented in the same file, SubObject would see the implicit declaration of myIvar from its superclass BaseObject, attempt to use that instead of synthesizing its own and fail because the myIvar from BaseObject is @private.

Conclusion

You do not normally need to worry about ivar layout problems when adding ivars to your classes. It is only a concern when you are writing or updating a dynamic library and you want to maintain backwards binary compatibility.

Dynamic ivars are a feature of the "modern" runtime; if you are targetting 32-bit Mac OS X, they are not supported.

For other Cocoa platforms, there are numerous reasons why you may want to @synthesize ivars:

  • They are convenient (don't require an ivar declaration, just a property declaration).
  • They allow for greater information hiding from subclasses than @private declarations since they don't publicly declare anything.
  • You can add them to base classes without needing to recompile subclasses.

Dynamic ivars do require the cost of an extra pointer offset at runtime but you are paying this cost whether you use them or not (it is a required part of the iPhone OS and 64-bit Mac OS X Objective-C runtimes). In any case, you're unlikely to ever notice this cost.

Custom UI Bindings in Interface Builder

In my last post, I showed how you might redesign the interface to the iPhone's UITableView if you wanted to reimplement it on the Mac using Cocoa Bindings. This time, I'll show you how to make those bindings editable in Interface Builder so you can use the entire class with no code at all.

Introduction

I'm going to use integration with Interface Builder to construct a simple browser for the Address Book using the ColumnView class to select entries:

abbrowser.png

The column on the left is handled by the ColumnView class, a class which supports layout of rows in sections, with variable row heights, variable row classes and selection.

In fact, if the ABAddressBook and its children were KVO compliant, there would be no project-specific code in this program at all (just the generic ColumnView class, its children and default application template code).

You can download the project here: ColumnViewSample.zip (70kb)

Interface Builder integration

Last time, I focussed on designing a view for use with bindings in code and didn't look at integrating that effort with Interface Builder.

The redesign eliminated all 7 controller methods that would ordinarily be required to configure a UITableView.

However, the code still required a great big binding instruction:

[columnView
    bind:ColumnViewSectionArrayBinding
    toObject:[[AddressBookDataController sharedController] groupsController]
    withKeyPath:@"arrangedObjects"
    options:
        [NSDictionary dictionaryWithObjectsAndKeys:
            @"members", ColumnViewSectionContentKeyOption,
            kABGroupNameProperty, ColumnViewSectionHeaderDataKeyOption,
            @"Last", ColumnSectionRowDisplayKeyOption,
        nil]];

to hook everything together.

The purpose of Interface Builder integration is to eliminate even this statement; instead, the entire program can be set up using data alone.

Setting up an IBPlugin

The first required step is to setup an Interface Builder plugin for the ColumnView class.

I've previously written a post showing all the steps required to create an IBPlugin for a view class. These are the steps I followed.

The ColumnPlugin project is in a subfolder of the ColumnViewSample project folder. You must build and run ColumnPlugin in the Release configuration to install it in your ~/Library/Frameworks directory. After that, you will be able to use the ColumnView's bindings in Interface Builder.

Note: You will get an error — Unable to resolve plug-in dependency for "ColumnViewSampleWindow.xib" — unless you build the "Release" build of ColumnPlugin.xcodeproj before you build the ColumnViewSample project.

Exposing bindings

Of course, the only reason why building the ColumnPlugin project will allow you to use the ColumnView's bindings is because the ColumnView class implements methods which enable Interface Builder integration. Let's look at how that works.

The first required step is to expose the binding. This is very simple, just invoke the exposeBinding: method in the +intialize method for the class.

+ (void)initialize
{
    if (self == [ColumnView class])
    {
        [self exposeBinding:ColumnViewSectionsArrayBinding];
    }
}

The string ColumnViewSectionsArrayBinding is initialized as sectionsArray, so this binding affects the sectionsArray property on ColumnView.

If you select an object with a binding like this, it will appear in Interface Builder's bindings panel (select the object and press Command-4) at the bottom under the "Parameters" section.

Custom binding options

If you add no other support, this will be sufficient to support basic binding of an arbitrary object to this property.

However, I designed my class to expect a number of keys to be set at the same time as the binding. These include:

  • sectionContentKey —A key path (relative to each section object) where the rows array can be found (if not present, it is assumed that the section is the rows array).
  • sectionClassKey — A key path (relative to each section object) where the default class to use for all rows in the section can be found (if not present the default RowView class is used). This property is overridden by the rowClassKey.
  • rowClassKey — A key path (relative to each row object) where the class for the row can be found (if not present, it is assumed the section is the rows array).
  • rowDisplayKey — A key path (relative to each row object) where a separate object used for display is found (if not present, the row object is used directly for display).
  • headerDataKey — A key path (relative to each section object) where the object for the header is found (if not present, no header is shown for the section).
  • headerClassKey — A key path (relative to each section object) where the class for the header row is found (if not present, the default RowView class is used).
  • allSectionsSortKey — A key path (relative to each row object) by which every section should be sorted.
  • sectionRowSortKey — A key path (relative to each section object) where the key by which that section should be sorted can be found (this will override the allSectionsSortKey).

So we need to set up these properties as editable fields in the bindings editor.

The primary way to set up the options for a binding is through the optionDescriptionForBinding: method:

- (NSArray *)optionDescriptionsForBinding:(NSString *)binding
{
    if ([binding isEqualToString:ColumnViewSectionsArrayBinding])
    {
        NSArray *options =
            [NSArray arrayWithObjects:
                AttributeDescription(ColumnViewSectionContentKeyOption, sectionContentKey ? sectionContentKey : @"self"),
                AttributeDescription(ColumnSectionRowDisplayKeyOption, rowDisplayKey),
                AttributeDescription(ColumnSectionClassKeyOption, sectionClassKey),
                AttributeDescription(ColumnSectionRowClassKeyOption, rowClassKey),
                AttributeDescription(ColumnViewSectionHeaderDataKeyOption, sectionHeaderDataKey),
                AttributeDescription(ColumnViewSectionHeaderClassKeyOption, sectionHeaderClassKey),
                AttributeDescription(ColumnViewAllSectionsSortKeyOption, allSectionsSortKey),
                AttributeDescription(ColumnViewSectionRowSortKeyOption, sectionRowSortKey),
            nil];
        return options;
    }
    
    return [super optionDescriptionsForBinding:binding];
}

Here AttributeDescription is just a helper method to create an NSAttributeDescription object.

Fixing custom bindings so they work

Having made it this far with my design and Interface Builder integration I discovered that Interface Builder doesn't support custom bindings options.

If you try to use a bindings option that isn't on Apple's official list of Bindings Options, the option will get set on your class briefly and then immediately cleared.

There is a fix for this unfortunate behavior: get the values when they are initially set, save them to persistent attributes (which you need to inform Interface Builder about) and then ignore any attempt in Interface Builder to set your options to nil.

Informing Interface Builder you have persistent attributes is done in the CustomViewIntegration.m file of the ColumnPlugin:

- (void)ibPopulateKeyPaths:(NSMutableDictionary *)keyPaths {
    [super ibPopulateKeyPaths:keyPaths];
    
    [[keyPaths objectForKey:IBAttributeKeyPaths]
        addObjectsFromArray:
            [NSArray arrayWithObjects:
                ColumnViewSectionContentKeyOption,
                ColumnSectionRowDisplayKeyOption,
                ColumnSectionClassKeyOption,
                ColumnSectionRowClassKeyOption,
                ColumnViewSectionHeaderDataKeyOption,
                ColumnViewSectionHeaderClassKeyOption,
                ColumnViewAllSectionsSortKeyOption,
                ColumnViewSectionRowSortKeyOption,
            nil]];
}

This will make sure that Interface Builder tracks these values in Undo/Redo.

After this is done, you need to ensure the attributes are saved when the Interface Builder file is saved by implementing encodeWithCoder on the ColumnView itself:

- (void)encodeWithCoder:(NSCoder *)encoder
{
    [super encodeWithCoder:encoder];
    [encoder encodeObject:sectionContentKey forKey:ColumnViewSectionContentKeyOption];
    [encoder encodeObject:rowDisplayKey forKey:ColumnSectionRowDisplayKeyOption];
    // And so on the for the other 6 properties...

and load them correctly when the NIB file is loaded:

- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super initWithCoder:decoder];
    if (self != nil)
    {
        sectionContentKey = [[decoder decodeObjectForKey:ColumnViewSectionContentKeyOption] retain];
        rowDisplayKey = [[decoder decodeObjectForKey:ColumnSectionRowDisplayKeyOption] retain];
        // And so on the for the other 6 properties...

and finally, ensure that any attempt by Interface Builder to set them to nil is ignored:

- (void)bind:(NSString *)bindingName toObject:(id)observedObject
    withKeyPath:(NSString *)observedKeyPath options:(NSDictionary *)options
{
    if ([bindingName isEqualToString:ColumnViewSectionsArrayBinding])
    {
        // ...Other work associated with setting the binding goes here...
        
        if ([options objectForKey:ColumnViewSectionContentKeyOption])
        {
            self.sectionContentKey = [options objectForKey:ColumnViewSectionContentKeyOption];
            self.rowDisplayKey = [options objectForKey:ColumnSectionRowDisplayKeyOption];
            // ...and so on..
        }

        // ... and so on for the other bindings

What I have done is considered the ColumnViewSectionContentKeyOption option a mandatory inclusion — this will allow me to detect if Interface Builder is improperly setting my bindings options to nil or if the setting is valid. If there is no value for the ColumnViewSectionContentKeyOption key in the dictionary then the options are considered invalid and will be skipped — leaving them at their previous values and allowing us to save them in initWithCoder: when the file is saved.

Setting it all up in Interface Builder

Now that we have Interface Builder integration, hooking up the components in Interface Builder for our program is easy.

Provide the ColumnView with data

An NSArrayController with the Address Book Data Controller's groups array as its content is bound to the sectionArray of our ColumnView. ColumnView prefers that you use an NSArrayController for the top-level content (instead of directly binding an NSArray), since it can use the selection and sorting from the NSArrayController for the sections in the view.

The sectionArray binding has the "section Content Key", "row Display Key", "section Header Data Key" and "section Row Sort Key" values filled in, so the ColumnView will know where to find the rows for each section, the object used to display the row, the name for each section and the sort order for each section respectively.

Update the detail view when the selection changes

The ColumnView has a KVO-compliant selectedSectionArrayController property which returns the NSArrayController for the content in the selected section. Using the selection property of this NSArrayController, we can get the selected row's data.

To bind this selectedSectionArrayController property, we need to add an NSObjectController at the top level which will have our ColumnView connected to its content outlet.

By binding all of the text fields in the detail view through this controller, their values will all update based on the current selection. i.e. the "First Name" text field is bound to the NSObjectController, with the controller key selection and the model key selectedSectionArrayController.selection.First.

Fields will automatically show "No selection" and "(None)" when there is no selection or a given selection does not contain a value for the field.

Conclusion

You can download the project here: ColumnViewSample.zip (70kb)

The final ColumnViewSample project shows a Master-Detail-style interface, constructed with almost no code. This is due to classes which are designed to work with bindings. A code-less implementation only works when when your classes are fully KVC and KVO compliant as bindings cannot operate without these design patterns.

Implementing custom bindings options in Interface Builder for the various key-path options wasn't totally necessary — you can set basic string keys like these in a normal Interface Builder Attributes panel — but I wanted to keep these keys closely associated with the binding. As an added benefit, this approach allowed me to use Interface Builder's built-in interface for editing these fields, rather than requiring that I implement my own.

ColumnView remains a fairly basic replication of UITableView-like functionality for the Mac. There is no UINavigationController for drill-down hierarchies. It lacks the animation and edit modes of UITableView. It does not attempt to reuse RowViews for efficiency like UITableView does. Nor have I really tested it thoroughly — it was just an experiment on my part.

Despite these shortcomings, if you want section-based layout of views though and you're prepared to add other features as you need them, ColumnView may be useful as an alternative to NSCollectionView or NSTableView.

Designing a view with Bindings (UITableView on the Mac)

Bindings are one of the best ways of connecting your view and model on the Mac but due to the layers of abstraction involved — and the fact that they don't exist on other platforms — bindings can seem foreign and confusing. In this post, I'll show a complex view that is traditionally implemented without bindings (the iPhone's UITableView) and show how it might be redesigned for the Mac with bindings.

The aim

I'm going to show the design of ColumnView, a Mac NSView subclass which operates in a similar manner to the iPhone's UITableView.

columnview.png

Specifically, the ColumnView will lay out RowViews (a proper NSView subclass instead of cells like NSTableView arranges). The rows are grouped in sections (sections may have a header and may be independently sorted). The rows can have different heights and be different classes.

I'm not aiming to make this a fully-fledged UITableView recreation — it won't support significant interactivity, animation or editing modes, although all these things could be added to the design (it is designed to be a flexible foundation, even if all features aren't present).

The aim of this design is to show that the entire structure and configuration of the ColumnView can be done with one binding. The UITableViewDataSource/UITableViewDelegate, normally required for a UITableView, can all be replaced by this single binding.

I won't be looking at bindings in Interface Builder in this post. I realize that is how most people configure bindings but to keep the number of topics down, I'm going to limit discussion to bindings configured in code.

Background: controller heavy implementations

Traditionally, Model-View-Controller would work like this:

  1. Model — sends notifications when it changes but otherwise keeps to itself
  2. Controller — receives model notifications and actively changes views in response
  3. View — passive and simply shows information the controller shoves at it

This works well but the controller ends up being huge because it contains all the logic of responding to changes, configuring the view, plus all of the interactivity work that may be required by the views.

A number of larger views, including the iPhone's UITableView and the Mac's NSTableView (without bindings) operate on a delegate model which breaks the controller up slightly:

  1. Model — (as before) sends notifications when it changes but otherwise keeps to itself
  2. Controller — pushes new data to the delegate and notifies the view that it needs an update
  3. View — determines areas needing an update and asks the delegate how to display those areas
  4. View delegate — tells the view how to update based on data from the controller

A view delegate is really just a controller but since it is invoked by the view, it allows the view to control when data is loaded and changes are made (the view can control lazy load order).

Ultimately though, this second case can still involve a lot of code to configure the view and provide it with data. The potential advantage of breaking the controller into two pieces (the controller and the view delegate) is normally lost since both are frequently implemented in the same class.

Bindings: a reusable controller

The controller heavy nature of the above approaches can be burdensome as you need to rewrite the controller every single time — it is entirely specific to the mode and view that it joins.

Cocoa Bindings are a way of eliminating implementation specific controller code — the entire controller layer can be handled with reusable controllers and observations and configurations specified in data, not code.

Cocoa Bindings purpose in one sentence: If you can simplify your design so that all a controller needs to do is copy data from the model to the view, then the controller's role can be replaced by a simple, reusable "binding" object and all that remains is to configure that binding object with data.

The bindings structure is then:

  1. Model — (as before) sends notifications when it changes but otherwise keeps to itself
    • KVO interface/controller — the interface that the model exposes must be Key Value Observing compliant. This interface may be part of the Model, or it can be a lightweight wrapper around the model, or a generic controller like NSArrayController.
  2. Key Value Binding — ensures that data from the KVO controller is pushed to an object with exposed bindings. May pass the data through an NSValueTransformer before pushing to the exposed binding. A binding may also contain an options dictionary which tells the view how to handle data it receives.
  3. View — determines areas needing an update and asks the delegate how to display those areas
    • Exposed binding — any Key Value Coding compliant property on the view but may also have options and other metadata to support Interface Builder and configuration.

While I've included more layers in this description, most of the time, you'll only deal with two: the model and the view.

The "Key Value Binding" layer is entirely automatic — in fact, unless you're hacking or looking closely at the stack, you won't actually see the classes that handle this layer. It will "just happen".

The "KVO interface/controller" and "Exposed binding" may or may not be automatic. Many model classes will be KVO compatible already, although others may require a shallow layer to improve their compatibility.

The real work for bindings, is in designing views to be fully configurable through bindings alone.

Terminology: I've already mentioned KVC and KVO. These technologies are fundamental to how bindings work. If you don't have great familiarity with these terms, you can read the Apple programming guide to Key Value Coding and Key Value Observing. You could also read my earlier post on 5 key-value coding approaches in Cocoa but note that bindings require proper NSKeyValueCoding compatible KVC.

Implicit bindings support

In some situations, you might be able to connect a model and view with a binding with no cooperation from either the model or the view. This will be the case if your model is already KVO compliant and your view can be totally configured by piping this KVO data directly into KVC properties on the view.

For example, if your model has a text property that is only ever changed through its setText: method and you want to display that property in a text field, you can connect the two with:

[someTextField bind:@"stringValue" toObject:myModelObject withKeyPath:@"text" options:nil];

Bindings will do the rest.

As soon as the binding is established, the binding will set the stringValue property of the someTextField to the value of the text property from the myModelObject. Every time the text is set using the setText: method, an automatic Key Value Observing notification will be detected by the binding and it will keep the text field in sync.

Normally though, bindings are used in situations where the view explicitly supports them. For example, with NSTextField, you would normally use NSValueBinding instead of @"stringValue". Ultimately, the NSTextField will map NSValueBinding onto the same @"stringValue" property but the supported binding guarantees compliance (and also supports other options like implicit conversion from NSNumber to NSString).

Adapting a delegate/dataSource view for use with bindings

Most views require a redesign so that they can be completely configured through Key Value Coding compliant properties. The reason for this is that the view must be completely configurable through KVC compliant properties to work with bindings.

In adapting UITableView for the Mac, I'll need to make such a redesign since UITableView does not expose the KVC properties we would require.

Looking at the situation before bindings

To achieve the features I want, a UITableView would require its dataSource to implement the following methods:

- tableView:cellForRowAtIndexPath:
- numberOfSectionsInTableView:
- tableView:numberOfRowsInSection:
- tableView:titleForHeaderInSection:

and the delegate would need to implement:

- tableView:heightForRowAtIndexPath:
- tableView:viewForHeaderInSection:
- tableView:didSelectRowAtIndexPath:

None of these methods set KVC properties so they all need to go.

Recategorizing delegate and controller methods as properties

We need to rethink what the table displays as data that we can set.

Ultimately, a UITableView contains two tiers of data: the section and the row. For each of the UITableView dataSource and delegate methods, we can consider the returned value as either a property of a section, row (or the array containing them).

  • tableView:cellForRowAtIndexPath: — Row property
  • numberOfSectionsInTableView: — Property of the sections array
  • tableView:numberOfRowsInSection: — Property of the rows array within a section
  • tableView:titleForHeaderInSection: — Section property
  • tableView:heightForRowAtIndexPath: — Row property
  • tableView:viewForHeaderInSection: — Section property
  • tableView:didSelectRowAtIndexPath: — Section array and rows array controller properties

ColumnView, ColumnSection and RowView binding interfaces

Structure of data used for the bindings

The bindings design for the ColumnView then comes directly from this categorization.

At the top level we need to bind the ColumnView to an array of sections. Each section must have a row array property.

Each "section" is not an object of any specific class — it can be any object from your model. To make it work, the ColumnView must also know the key path from the section to its content. This key path can be set by using a binding option when a binding is established for the sectionsArray. It is this type of key path traversal that allows the view to traverse model data whose structure it doesn't necessarily know.

The only tricky point is the tableView:cellForRowAtIndexPath: method. While the "cellForRowAtIndexPath" could be considered a property, the rows and sections here a model objects and having "view" properties on model objects is a bad idea.

I've opted instead to use the row's class as the property instead of a fully realized view. The ColumnView will then construct the view from this class. Further, this class does not need to come from the model object — other means of setting the row's class will exist that are unrelated to the model object involved.

Class structure to follow the data structure

To break the implementation down into manageable components, the design will use three classes:

  • ColumnView — does all of the layout and manages the array of sections
  • ColumnSection — manages the array of rows in each section and stores header information
  • RowView — mostly a drawing class but does report the height for the row, given its data
ColumnView

The only binding for the ColumnView is the ColumnViewSectionArrayBinding which sets the sectionsArray property (ColumnViewSectionArrayBinding is a globally defined string with the value @"sectionsArray").

Along with this binding, the ColumnView has the following additional properties that can be set in the ColumnViewSectionArrayBinding options:

  • sectionContentKey —A key path (relative to the section object) where the rows array can be found (if not present, it is assumed that the section is the rows array).
  • sectionClassKey — A key path (relative to the section object) where the default class to use for all rows in the section can be found (if not present the default RowView class is used). This property is overridden by the rowClassKey.
  • rowClassKey — A key path (relative to the row object) where the class for the row can be found (if not present, it is assume the section is the rows array).
  • rowDisplayKey — A key path (relative to the row object) where a separate object used for display is found (if not present, the row object is used directly for display).
  • headerDataKey — A key path (relative to the section object) where the object for the header is found (if not present, no header is shown for the section).
  • headerClassKey — A key path (relative to the section object) where the class for the header row is found (if not present, the default RowView class is used).
  • allSectionsSortKey — A key path (relative to each row object) by which every section should be sorted.
  • sectionRowSortKey — A key path (relative to the section object) where the key by which that section should be sorted can be found (this will override the allSectionsSortKey).

While you can set these properties directly on the ColumnView, it is expected that they will be passed in the options dictionary and picked up in the implementation of bind:toObject:withKeyPath:options: on the ColumnView.

ColumnSection

The ColumnSection is a simple data class to manage data associated with a section. It looks like this:

@interface ColumnSection : NSObject
{
    NSArrayController *rowDataArrayController;
    NSString *rowClassKey;
    NSString *rowDisplayKey;
    
    Class defaultClass;
    
    id headerData;
    Class headerClass;
}

All of these properties are set by the ColumnView using the properties it extracts from the sectionsArray by following the key paths options from the binding.

The array of row data is set by binding the NSContentArrayBinding of the rowDataArrayController instead of setting it directly as an NSArray property. This is done so that the ColumnSection can sort the array independent of the original data and so that the selection and other controller features of the NSArrayController could be used if desired.

RowView

The RowView has no bindings of its own — it doesn't bindings since its data is passed to it by the ColumnView which gets it from the ColumnSection.

Following the path of setting the data

To see how these bindings work to change the table, I'll explain what happens at each step.

1. The sectionsArray property is set by the binding

Immediately after you establish a binding, the bindings will set the value of the underlying property to be the same as the model object to which it is bound.

- (void)setSectionsArray:(NSArray *)newSectionsArray;

When the sectionsArray is set by the ColumnViewSectionArrayBinding, the ColumnView's setSectionsArray: method is invoked where it constructs a ColumnSection object for each section.

2. The constructed array of ColumnSections is set as the columnSections array
- (void)setColumnSections:(NSArray *)newColumnSections;

The final step in the setter for sectionsArray sets the columnSections property of the ColumnView using the constructed array of ColumnSections objects.

This method observes the rowDataArrayController's arrangedObjects (in case a row in the section changes) and triggers a reload of the view.

If you wanted to bypass bindings for the sections (to use static, manually created sections), you could create and configure the ColumnSection objects yourself and set them using this method.

3. Reload the view

The ColumnView then accesses the properties on the ColumnSection objects to populate the table. Only rows and headers that are visible are constructed, positioned and displayed.

I show this code next time with the implementation of the ColumnView.

End result

The end result of a class designed this way is that configuring the ColumnView is done with a single statement. For the screenshot I showed at the top, the ColumnView is configured with data from the ABAddressBook with a single statement:

[columnView
    bind:ColumnViewSectionArrayBinding
    toObject:[[AddressBookDataController sharedController] groupsController]
    withKeyPath:@"arrangedObjects"
    options:
        [NSDictionary dictionaryWithObjectsAndKeys:
            @"members", ColumnViewSectionContentKeyOption,
            kABGroupNameProperty, ColumnViewSectionHeaderDataKeyOption,
            @"Last", ColumnSectionRowDisplayKeyOption,
        nil]];

This selects the arranged groups from the AddressBookDataController and uses them as sections. The title of each section is the group name, the content is the group's members and the object used for display is the lastname of the group member.

This one statement replaces the 6 dataSource and delegate method implementations that UITableView requires for the same functionality. If you create an IBPlugin for the view, you can configure all of this in Interface Builder and reduce the lines of code to zero.

Obviously, I'm going through a small controller here: the AddressBookDataController. This is because the ABAddressBook does not send KVO notifications when groups or their contents change and further, ABGroups are not KVC compliant for their members method. This controller is to address these problems, sort the groups by name and handle a situation where no groups exist. This work is not really bindings related and would be required in some form if the ColumnView were configured another way.

To be continued...

Matt, I can't help but notice you haven't shared the complete implementation of the ColumnView class.

Well spotted, voice in my head. I wanted to focus on bindings and how to redesign an interface to work with them. I've already run way past the time I was going to spend on this. In my next post, I'll share more about the implementation and let you see all the code.

Load from NIB or construct views in code: which is faster?

You don't have to look far to find people suggesting that constructing views in code will increase the speed of your program. Since interface performance is a serious concern in iPhone development, I thought I'd look at the performance speed in creating complex iPhone UITableViewCells in code versus loading them from a NIB file.

Introduction

The sample program this time is pretty simple: an iPhone application that can load a table containing 20 rows (sized so that they will all fit onscreen), each row containing 20 fully-configured UILabels, a backgroundView and a selectedBackgroundView.

All timing is done on the construct/load only. Adding cells to the table, configuring the cell for its row and drawing to the screen (all of which is identical in either load from NIB or construct in code cases) is not included as part of the timing results.

nibornot.png

The dark line across the right-hand side of each cell is just the word "placeholder" written 19 times on top of itself by the labels added to the cell.

You can download the sample project: NibOrNot.zip

Creating the cell in code

The construction of the cell in code looks like this:

cell = [[[UITableViewCell alloc]
	initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
cell.backgroundView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
cell.backgroundView.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];

cell.selectedBackgroundView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1.0];

UILabel *firstLabel =
	[[[UILabel alloc] initWithFrame:CGRectMake(5, 0, 60, 20)] autorelease];
firstLabel.tag = 1;
firstLabel.font = [UIFont boldSystemFontOfSize:14];
firstLabel.shadowOffset = CGSizeMake(1,1);
firstLabel.textColor = [UIColor colorWithRed:0.0 green:0.2 blue:0.5 alpha:1.0];
firstLabel.backgroundColor = [UIColor clearColor];
firstLabel.text = @"placeholder";
firstLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
firstLabel.adjustsFontSizeToFitWidth = YES;
firstLabel.minimumFontSize = 10;
firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
firstLabel.lineBreakMode = UILineBreakModeTailTruncation;
firstLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
firstLabel.highlightedTextColor = [UIColor clearColor];

[cell addSubview:firstLabel];

//
// Plus the construction of a further 19 labels...
//

In this case, the next 19 labels could all be created in a loop (since they're all identical) but I wanted to code to reflect a proper view where each subview is distinct and has many properties uniquely applied — so a basic loop is not necessarily appropriate.

Loading the NIB

There are many different ways to load a UITableViewCell (loading just the contentView, constructing a UITableViewCell subclass and having it load the NIB, etc). I've gone for the quick and easy approach: loading the UITableViewCell from the NIB in its entirety, passing my UITableViewController as the owner.

[[NSBundle mainBundle] loadNibNamed:@"Cell" owner:self options:nil];
cell = loadedCell;
loadedCell = nil;

It may be hard to see how the loadNibNamed:owner:options: method is connected to the other two lines. In this code, loadedCell is an IBOutlet on self. When the NIB is loaded, it sets the loadedCell value to be equal to the loaded cell (this is configured in the NIB file). After loading, I copy the loadedCell into a local variable named cell (which is later configured and returned) and set the instance variable on the class back to nil (because we no longer need this pointer after returning it).

Simulator results

The first 10 results from the simulator look like this:

Generated in codeLoaded from NIB
Generated cell in 0.00153798 secondsLoaded cell in 0.00184 seconds
Generated cell in 0.00138998 secondsLoaded cell in 0.00168097 seconds
Generated cell in 0.00138199 secondsLoaded cell in 0.00168198 seconds
Generated cell in 0.00139898 secondsLoaded cell in 0.001706 seconds
Generated cell in 0.00167602 secondsLoaded cell in 0.001697 seconds
Generated cell in 0.00235301 secondsLoaded cell in 0.00171804 seconds
Generated cell in 0.00137097 secondsLoaded cell in 0.002105 seconds
Generated cell in 0.00138301 secondsLoaded cell in 0.00173801 seconds
Generated cell in 0.00140399 secondsLoaded cell in 0.00171405 seconds
Generated cell in 0.00137198 secondsLoaded cell in 0.001692 seconds

Constructing in code is generally about 20% faster — although at 1 millisecond per construction in the simulator, it doesn't really matter.

Device results

The first 10 results running on my iPhone 3G look like this:

Generated in codeLoaded from NIB
Generated cell in 0.113011 secondsLoaded cell in 0.131085 seconds
Generated cell in 0.114312 secondsLoaded cell in 0.097244 seconds
Generated cell in 0.101614 secondsLoaded cell in 0.08413 seconds
Generated cell in 0.105022 secondsLoaded cell in 0.081331 seconds
Generated cell in 0.10087 secondsLoaded cell in 0.093407 seconds
Generated cell in 0.105968 secondsLoaded cell in 0.083472 seconds
Generated cell in 0.100045 secondsLoaded cell in 0.091788 seconds
Generated cell in 0.105458 secondsLoaded cell in 0.083763 seconds
Generated cell in 0.098836 secondsLoaded cell in 0.08714 seconds
Generated cell in 0.102028 secondsLoaded cell in 0.109811 seconds

The construction in code is about 15% faster on the first construction but by the third cell, loading from the NIB file is 17% faster.

Running some CPU sampling in Instruments, I was able to work out that adjustsFontSizeToFitWidth was the critical factor here: it appears that Interface Builder precalculates the correct size based on the placeholder string, whereas the constructed code is forced to do this at runtime, causing its performance to suffer.

Turning off adjustsFontSizeToFitWidth in both the generated code and the loaded code (replaced the label.adjustsFontSizeToFitWidth = YES with label.adjustsFontSizeToFitWidth = NO and switched Cell.xib to Cell2.xib), gave the following results:

Generated in codeLoaded from NIB
Generated cell in 0.085553 secondsLoaded cell in 0.095012 seconds
Generated cell in 0.077257 secondsLoaded cell in 0.087141 seconds
Generated cell in 0.084639 secondsLoaded cell in 0.082693 seconds
Generated cell in 0.079142 secondsLoaded cell in 0.098218 seconds
Generated cell in 0.078286 secondsLoaded cell in 0.082136 seconds
Generated cell in 0.087895 secondsLoaded cell in 0.087088 seconds
Generated cell in 0.0792 secondsLoaded cell in 0.082335 seconds
Generated cell in 0.084037 secondsLoaded cell in 0.082358 seconds
Generated cell in 0.076416 secondsLoaded cell in 0.08714 seconds
Generated cell in 0.078426 secondsLoaded cell in 0.084312 seconds

This now swings the performance advantage back to handwritten code by 7%.

Conclusion

You can download the sample project: NibOrNot.zip

Don't assume that NIB files are always slower than generating views in code — it is not always true. While in general, generating user interface views in code appears to be 5-10% faster than loading from a NIB, the reality is that this difference is small enough that it doesn't matter and there are certainly some views that load faster from a NIB than from code.

This doesn't mean that construction speed is unimportant on the iPhone; it clearly is. I was able to create a (slightly contrived) view that took nearly two seconds to load (unacceptably slow). However, saving 10% by moving from NIBs to creating views in code will never fix the problem (1.8 seconds is still too slow). In this case, flattening the view hierarchy (using a single custom view to draw what multiple subviews used to draw), or removing text fields entirely, is the best way to improve performance. This type of redesign can improve performance by a factor of 10 or more.

The conclusion to be drawn is that when choosing to use a NIB file or not, you should use whatever you're most comfortable with and whatever will keep your code maintenance costs low. Don't be concerned that one approach or the other will cause your UI performance to suffer.