High resolution timing in Cocoa, revisited

A while back I wrote about high resolution timing in Cocoa. It's been a while, and I've updated it a bit – adding ARC support, adding a call to time a block, and removing some stuff that wasn't needed. Hopefullly you find it useful.

// MachTimer.h
#include <mach/mach_time.h>

@interface MachTimer : NSObject

+ (instancetype) timer;
+ (NSTimeInterval) timeForBlock:(void (^)(void))block;

- (void) start;
- (NSTimeInterval) elapsedSeconds;

@end


// MachTimer.m
#import "MachTimer.h"

static mach_timebase_info_data_t timeBase;

@implementation MachTimer
{
    uint64_t timeZero;
}   

+ (void) initialize
{
    (void) mach_timebase_info( &timeBase );
}

- (instancetype) init
{
    self = [super init];
    if( self ) {
        [self start];
    }
    return self;
}

+ (instancetype) timer
{
#if( __has_feature( objc_arc ) )
    return [[[self class] alloc] init];
#else
    return [[[[self class] alloc] init] autorelease];
#endif
}

+ (NSTimeInterval) timeForBlock:(void (^)(void))block
{
    MachTimer* aTimer = [self timer];

    [aTimer start];
    block();
    return [aTimer elapsedSeconds];
}

- (void) start
{
    timeZero = mach_absolute_time();
}

- (NSTimeInterval) elapsedSeconds
{
    return ((NSTimeInterval)(mach_absolute_time() - timeZero)) * ((NSTimeInterval)timeBase.numer) / ((NSTimeInterval)timeBase.denom) / 1000000000.0f;
}

@end

You might use it like this:

MachTimer* aTimer = [MachTimer timer];
[self doSomeLengthyOperation];
NSLog( @"Lengthy operation took %f seconds", [aTimer elapsedSeconds] );

Or, with the new block call:

NSTimeInterval theTime = [MachTimer timeForBlock:^{
    // Do some lengthy operation.
}];
NSLog( @"Lengthy operation took %f seconds", theTime );

Credit where credit is due

I originally found the basis for this code on the Apple message boards, here. I dunno who wrote that, but thanks, dude. If you're that guy, let me know, and I'll be happy to give you attribution. Since writing this, I've discovered Waffle Software has a similar thing, which has some additional functionality; check that out if you're interested.

If you want to follow me, I'm @zpasternack on Twitter and on app.net.

Relative date formatting like Mail.app

Mail.app on Mac OS has a cool way of showing you relative dates of messages. Messages received today show only the time (e.g., "12:55 PM"), whereas messages received prior to today show only the date (e.g., 3/13/14), and if the date was Yesterday, it shows that, rather than the date. I wanted this same functionality in one of my apps.

Googling around for a bit turned up this StackOverflow question, with which I was not happy. So I did it myself (and, of course, added my own answer to that question). Here's what I did.

NSDateFormatter (as of Mac OS 10.6 and iOS4) does relative date formatting (e.g., "Today", "Yesterday" for you, like so:

NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
[formatter setTimeStyle:NSDateFormatterNoStyle];
[formatter setDateStyle:NSDateFormatterShortStyle];
[formatter setDoesRelativeDateFormatting:YES];

NSString* dateString = [formatter stringFromDate:[NSDate date]];    
NSLog( @"date = %@", dateString );

Which outputs:

2014-03-15 15:26:37.683 TestApp[1293:303] date = Today

But that still leaves the issue of instead using the time if the date is today. First, we need to be able to tell if a given date is today. For this I implemented a method isToday, as a category on NSDate. (Side note: I freaking love categories!)

@implementation NSDate (IsToday)

- (BOOL) isToday
{
    NSCalendar* calendar = [NSCalendar currentCalendar];

    // Components representing the day of our date.
    NSDateComponents* dateComp = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
                                             fromDate:self];
    NSDate* date = [calendar dateFromComponents:dateComp];

    // Components representing today.
    NSDateComponents* todayComp = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
                                              fromDate:[NSDate date]];
    NSDate* todayDate = [calendar dateFromComponents:todayComp];

    // If the dates are equal, then our date is today.
    return [date isEqualToDate:todayDate];
}

@end

Then, I made two date formatters, one for the day, and one for the time, and decided which to use based on whether the date is today or not. Like so:

- (NSString*) postDateToString:(NSDate*)aDate
{
    static NSDateFormatter* todayFormatter = nil;
    if( todayFormatter == nil ) {
        todayFormatter = [[NSDateFormatter alloc] init];
        [todayFormatter setTimeStyle:NSDateFormatterShortStyle];
        [todayFormatter setDateStyle:NSDateFormatterNoStyle];
    }

    static NSDateFormatter* notTodayFormatter = nil;
    if( notTodayFormatter == nil ) {
        notTodayFormatter = [[NSDateFormatter alloc] init];
        [notTodayFormatter setTimeStyle:NSDateFormatterNoStyle];
        [notTodayFormatter setDateStyle:NSDateFormatterShortStyle];
        [notTodayFormatter setDoesRelativeDateFormatting:YES];
    }

    NSDateFormatter* formatter = notTodayFormatter;

    if( [aDate isToday] ) {
        formatter = todayFormatter;
    }

    return [formatter stringFromDate:aDate];
}

And there you have it.

If you want to follow me, I'm @zpasternack on Twitter and on app.net.

Some objects don’t support weak references

Filed under “you learn something new every day”.

You can’t make a weak reference to an NSTextView (nor NSViewController, NSWindow, NSWindowController, and a few others). This was news to me.

The following code will not compile:

@interface MyWindowController ()
@property( weak, nonatomic ) IBOutlet NSTextView* textEntryView;
@end

You’ll get an error like this:

Synthesis of a weak-unavailable property is disallowed because it requires synthesis of an ivar of the __weak object.

Well, that’s not very helpful. If you manually add the backing iVar (which you should almost never do, just because there’s really no benefit), you get a more useful error.

@interface MyWindowController ()
{
    __weak IBOutlet NSTextView* textEntryView;
}
@property( weak, nonatomic ) IBOutlet NSTextView* textEntryView;
@end

Now the error is:

Class is incompatible with __weak references

OK, that makes more sense. Except, wait… what? Classes are incompatible with weak references? After a bit of digging I found this in the Resource Programming Guide:

Note: In OS X, not all classes support weak references; these are NSATSTypesetter, NSColorSpace, NSFont, NSFontManager, NSFontPanel, NSImage, NSMenuView, NSParagraphStyle, NSSimpleHorizontalTypesetter, NSTableCellView, NSTextView, NSViewController, NSWindow, and NSWindowController, and all classes in the AV Foundation framework.

In cases where you cannot therefore specify weak, you should instead use assign:
@property (assign) IBOutlet NSTextView *textView;

The (always useful) LLVM ARC guide has a section on weak-unavailable types which explains why:

It is explicitly permitted for Objective-C classes to not support __weak references. It is undefined behavior to perform an operation with weak assignment semantics with a pointer to an Objective-C object whose class does not support __weak references.

Rationale: historically, it has been possible for a class to provide its own reference-count implementation by overriding retain, release, etc. However, weak references to an object require coordination with its class’s reference-count implementation because, among other things, weak loads and stores must be atomic with respect to the final release. Therefore, existing custom reference-count implementations will generally not support weak references without additional effort. This is unavoidable without breaking binary compatibility.

So there you go.

If you want to follow me, I’m @zpasternack on Twitter and on app.net.

UIAlertView with UITextField, Revisited

My first ever blog post here was on putting a text field on a UIAlertView. While that technique still works perfectly fine, there is now (since iOS 5), an official API for putting a text field on an alert view. All you need to do is set the UIAlertView's alertViewStyle to UIAlertViewStylePlainTextInput. You can then access the text field using textFieldAtIndex:.

- (void) promptForName 
{
    UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Congratulations!" 
                   message:@"You earned a top score! Enter your name:" 
                  delegate:self 
         cancelButtonTitle:nil 
         otherButtonTitles:@"OK", nil]; 
    alert.alertViewStyle = UIAlertViewStylePlainTextInput; alert.delegate = self; [alert show];
}

- (void) alertView:(UIAlertView*)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    UITextField* nameField = [alert textFieldAtIndex:0];
    NSLog( @"You entered %@", nameField.text );
}

Of course, this also works with ZPAlertView.

- (void) promptForName
{
    ZPAlertView* alert = [[ZPAlertView alloc] initWithTitle:@"Congratulations!"
                    message:@"You earned a top score! Enter your name:"
                   delegate:self
          cancelButtonTitle:nil
          otherButtonTitles:@"OK", nil];
    alert.alertViewStyle = UIAlertViewStylePlainTextInput;
    alert.didDismissBlock = ^(NSInteger buttonPressed) {
        UITextField* nameField = [alert textFieldAtIndex:0];
        NSLog( @"Your entered %@", nameField.text );
    };

    [alert show];
}

If you want to follow me, I'm @zpasternack on Twitter and on app.net.

Taking advantage of the iPhone 5’s larger screen

Because it comes up on StackOverflow, I dunno, maybe five times per day, I thought I’d make a quick post describing how to make your apps work well with the new iPhone 5 screen dimensions (while still remaining compatible with older iPhones). It’s really not that hard at all. TaskLog had iPhone 5 support when it launched. It took me about 5 minutes to make this work, and probably a few additional hours to make it work well. The first step is to include an iPhone 5 specific default launch image. This is what iOS uses to determine whether or not your app is ready for the larger screen size. Iphone5 launch image It should be 640 x 1136 pixels. If you drag it into the Launch Images in Xcode, it will be automatically named for you. If you just add it to the project yourself, make sure you name it correctly: “Default-568h@2x.png”. Once you do this, your app will be sized to fit the iPhone 5 screen. From there, the amount of work you need to do depends entirely on how your application is built. If you’ve set up all your views with proper resizing characteristics (either via Auto Layout or the old-school autoresizing masks), you might be done. Just make sure to test it out in the Simulator (choose Device->Hardware->iPhone 5 (Retina) ) and make sure all your views look nice. I can’t stress this enough: test every one of your views on both 3.5″ and 4″ devices (or the simulator, at the very least). Places where you might have gotchas: if you’re doing any kind of hardcoding of coordinates, there might be some tweaking you need to do. If you’re going to move or size things in code, based on coordinates, at least try to do so via relative, rather than absolute, coordinates.

Related StackOverflow questions:

If you want to follow me, I’m @zpasternack on Twitter and on app.net.

Working around broken hiutil in Mountain Lion

For VideoBuffet, I use VoodooPad to author my help file. I learned about doing this from this blog post, and it’s been working well for me. Until the last VideoBuffet update, when suddenly my help anchors stopped working.

As the article states, you basically make a Run Script build phase in Xcode which exports the VoodooPad doc as HTML. Then, your VoodooPad doc is setup to run Apple’s help indexer (hiutil) to create the index. What the article doesn’t tell you is how to open your help to a particular page.

To do this, in VoodooPad you make an alias to the page to which you want to link, and give it a name (in the title bar, click Info; then, under Aliases, click the + button and type the name). The screenshot below shows VideoBuffet’s Preferences page, and you can see I’ve made an alias called “preferences”.

Voodoopad example

Finally, in code (like, in the Help button in the Preferences dialog), you can do this:

NSString *locBookName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleHelpBookName"];
[[NSHelpManager sharedHelpManager] openHelpAnchor:@"preferences" inBook:locBookName];

…and the appropriate page will open up in Help Viewer. It works great, and I use this technique all over the place.

Back to the point. While testing VideoBuffet 1.0.3, I discovered that the help links were all broken. After a bit of hair-pulling I finally figured out that hiutil was giving me errors, and ending up not creating a proper index (it wasn’t getting as far as my anchors). After Googling it, to no avail, I posted a question on Stackoverflow, which, at the time of this writing still has no responses. If you know the answer, by all means, post it there. :)

I’ve since discovered that the version of hiutil that ships with Mountain Lion (version 1.3) was where this problem started showing up, and the version from Lion (1.2) works as it always did. So, my (terrible) workaround: use the version of hiutil which shipped in Lion.

Find yourself a Lion system, and copy /usr/bin/hiutil to the machine where you do your builds. I didn’t want to replace the newer hiutil, so I called the Lion copy hiutil_old, and changed the VoodooPad export script to use that one. There’s one other step: hiutil 1.2 depends on MPWXmlCore.framework, which no longer exists in Mountain Lion. So in order to use hiutil 1.2 on Mountain Lion, you need to copy MPWXmlCore.framework from a Lion system as well (it’s in /System/Library/PrivateFrameworks/).

Update: Some folks on the original stackoverflow question I posted note that if you specify a doctype of XHTML 1.0 and fixup any obvious markup errors, Mountain Lion’s hiutil works fine. After messing about with my export scripts, I can confirm that this does appear to work (though, I’m not sure VoodooPad is really doing 100% valid XHTML). In any case, with some changes I’ve made it work.

I’ve also since upgraded to VoodooPad 5, and there are some tweaks I had to make to ensure everything still works. Specifically, exporting aliases to anchors works differently now. I’ll post another entry showing my updated workflow for using VoodooPad to author help files, soon.

If you want to follow me, I’m @zpasternack on Twitter and on app.net.

Sparrow’s not a Canary

All the cool kids are talking about Sparrow’s acquisition by Google. People far smarter than I have chimed in about every aspect of the situation. But this caught my attention today: David Barnard, AppCubby:

…Sparrow is the proverbial canary in the coal mine. The age of selling software to users at a fixed, one-time price is coming to an end. It’s just not sustainable at the absurdly low prices users have come to expect. Sure, independent developers may scrap it out one app at a time, and some may even do quite well and be the exception to the rule, but I don’t think Sparrow would have sold-out if the team — and their investors — believed they could build a substantially profitable company on their own. The gold rush is well and truly over.

It’s true that the App Store has reset people’s expectations with regard to the value of software, and I do believe that the race to the bottom is ultimately detrimental to indie developers. It is pretty apparent, based on what we know, that the Sparrow team didn’t believe they could sustain a profitable business. But, how did we make the jump from “Sparrow could not sustain a profitable business building software” to “It’s not possible to sustain a profitable business building software”? I don’t see how Sparrow’s failure to build a sustainable business means that it’s impossible to do so. Remember how 60% of developers don’t break even? Sparrow had a 5 person development team, and was selling a $3 app. Read that again, because it’s kind of important. If you have a 5 person dev team, and you’re selling a $3 app, you probably plan on selling a whole lot of apps. Between Macs and iOS devices, there are over 200 million potential devices that can run Sparrow. And every single one of them ships with a very good email client. Are you seeing the problem yet? I believe Sparrow’s failure to build a sustainable business can be attributed to: 1. Overestimating the market for email client software. 2. Underestimating the value of their software to it’s actual target market.

I consider myself a nerd. I have a full-time job as a software developer, and I’ve got my own indie dev business going on the side. I have six (count ’em) active email accounts, and regularly send and receive many dozens of emails per day. But I don’t own Sparrow. Why? Apple’s Mail is good enough for me. Sparrow solves a problem I don’t have. I don’t think I’m alone. Mail is actually a pretty good app. I don’t think it even occurs to most people to go looking for a better email app, just as it doesn’t occur to most people to go looking for a better web browser.

The Twittersphere exploded in a shitstorm of angry sentiment from butt-hurt Sparrow users lamenting the ultimate demise of their software. Clearly, this was a very important piece of software for those that did buy it. I’m willing to bet that most of the people that paid $3 (or $10) for Sparrow would gladly have paid much more.

Brent Simmons knew this, years ago:

I’ve been joking for years that I’m going to write an email client and charge $500 for it — an email client that actually meets the needs of developers and professionals who rely on email, folks who type for a living.

I read somewhere that this may have been the genesis of the idea for Sparrow. I hope not, because if it was, they clearly didn’t read the next paragraph:

But I’m not going to, and I don’t know anybody who is. The economics of it make it kind of tough, given that Apple ships a good email client with OS X.

Sparrow was a premium product, sold at a commodity price. If BMW started selling their cars for $15,000, I don’t think it would surprise anyone when they lost buckets of money, and I don’t think anyone would declare that it’s not possible to sustain a profitable business selling cars.

Marco Arment nailed it:

Don’t blame Sparrow. Blame the terrible market for email clients.

Just because most apps are $0.99, doesn’t mean yours has to be. What is your software worth to your customers?

Seth Godin said,

“The problem with the race to the bottom is that you might win…”

If you want to follow me, I’m @zpasternack on Twitter and on app.net.

High resolution timing in Cocoa

For profiling app performance, it’s necessary to accurately time your code. The best way to do this is to use mach_absolute_time. In conjunction with mach_timebase_info, you can get extremely high resolution timing to ease performance benchmarking. I’ve wrapped this functionality up in a little class to make it super easy to use.

// MachTimer.h
#include <mach/mach_time.h>

@interface MachTimer : NSObject
{
    uint64_t timeZero;
}

+ (id) timer;

- (void) start;
- (uint64_t) elapsed;
- (float) elapsedSeconds;
@end

// MachTimer.m
#import "MachTimer.h"

static mach_timebase_info_data_t timeBase;

@implementation MachTimer

+ (void) initialize
{
    (void) mach_timebase_info( &timeBase );
}

+ (id) timer
{
#if( __has_feature( objc_arc ) )
    return [[[self class] alloc] init];
#else
    return [[[[self class] alloc] init] autorelease];
#endif
}

- (id) init
{
    if( (self = [super init]) ) {
        timeZero = mach_absolute_time();
    }
    return self;
}

- (void) start
{
    timeZero = mach_absolute_time();
}

- (uint64_t) elapsed
{
    return mach_absolute_time() - timeZero;
}

- (float) elapsedSeconds
{
    return ((float)(mach_absolute_time() - timeZero))
        * ((float)timeBase.numer) / ((float)timeBase.denom) / 1000000000.0f;
}

@end

You’d use it like this:

MachTimer* aTimer = [MachTimer timer];
[self performSomeOperation];
NSLog( @"performSomeOperation took %f seconds", [aTimer elapsedSeconds] );

I’ve used this code for iOS and Mac OS apps, and it works great.

If you want to follow me, I’m @zpasternack on Twitter and on app.net.

Retina Graphics in Mac OS X

I recently added Retina graphics to the Mac version of PuzzleTiles. I had Retina-ized TaskLog prior, and found that to be pretty trivial, as it’s not a very graphics-heavy app. PuzzleTiles has many hundreds of images, so it was a bit more of a challenge. The whole process is pretty straightforward, especially if you’ve done Retina graphics for an iOS app. The TL;DR is: * Make double-size images and suffix them with @2x * Use NSImage imageNamed: to load them * Use Quartz Debug to test your new graphics (if you don’t have a Retina MacBook Pro)

The code

The main thing is to use NSImage imageNamed:
It does all the @2x image-loading magic. One thing not immediately obvious to me is that you don’t specify the extension when doing so.

NSImage* myImage = [NSImage imageNamed:@"foo.png"];  // NO, this makes the magic not work.
NSImage* myImage = [NSImage imageNamed:@"foo"];      // YES, magic is a-comin'.

The art

Producing @2x artwork was, by far, the biggest task. Luckily we’d had the foresight to make all our source art with vector graphics (text, shapes, and effects layers) which are basically arbitrarily resizable. The first step was batch upsizing everything to 2x. I ended up making a separate set of source art for our @2x images, so that we could tweak them by hand. Some of the artwork we upsized with no other changes, but for some of them we wanted to tweak some things: line or shadow thickness, for example. Some others (the Wood tile set, for example) had bitmaps which we had to completely redo. My advice is this: if your source art isn’t vector-based, it might be time to bite the bullet and do that. If you’re like me and don’t have a graphic artist on staff to do all this stuff for you, I have a few pieces of advice to ease the Retina-izing process:

  • Get familiar with PhotoShop’s automation tools (File->Automate->Batch and File->Scripts->Image Processor). This saved me tons of time when converting, resizing, and exporting hundreds of images at once.
  • Get a tool for batch file renaming. At one point I decided to change my file naming convention (to make things more amenable to using imageNamed:), and hand renaming nearly a thousand files would have been a time-consuming, error-prone task. I ended up using Core-Renamer for this, and though the UI is a little bit wonky, it worked perfectly.

Testing

If you don’t have a Retina MacBook Pro, you can use any old Mac to test out your Retina graphics. Download and install Graphics Tools for Xcode (from within Xcode, choose Xcode->Open Developer Tool->More Developer Tools). Run Quartz Debug, choose Window->UI Resolution, and check “Enable HiDPI display modes”. Now, when you go to Displays in System Preferences, you’ll see a bunch of HiDPI modes. Pick one, run your app, and see how it looks.

References

The WWDC 2012 session “Introduction to High Resolution on OS X” is fantastic; I highly recommend checking that out.

If you want to follow me, I’m @zpasternack on Twitter and on app.net.