Hazel powered mogenerator

May 01, 2012

Having recently started using the excellent mogenerator by Jonathan ‘Wolf’ Rentzsch for a project, I wanted a way to have it automatically run whenever I edit the .xcdatamodeld file. The solution to this used to be the Xmo’d tool that is part of the mogenerator project. Unfortunately, Xcode 4 isn’t supported at the moment, so I needed an alternative.

A common approach seems to be the use of a Run Script Build Phase in Xcode. But I’d rather automatically run mogenerator to create and/or update my model files immediately without having to do a build first. I turned to Noodlesoft’s fantastic Hazel, a utility that watches any folder you tell it to and applies custom conditional rules when something changes in that folder.

I added a new set of rules to point to my project’s Sources/Models folder where the .xcdatamodeld is located. I then created a rule with the following conditions (best illustrated with a screenshot):

Hazel rule conditions

The Run shell script action runs the following bash script to perform the actual invocation of mogenerator:

filename=$(basename $1)
outputdir=$(dirname $1)

/usr/local/bin/mogenerator --model $1/${filename%.*}.xcdatamodel --output-dir $outputdir --template-var arc=true

For convenience, I have posted the above Hazel rules file (which includes the shell script) here for download.

Using this setup, Hazel now watches the folder in my Xcode project that contains my Core Data model file. Whenever any changes happen within that folder, Hazel runs the rule I created above and looks for any .xcdatamodeld files that have been modified since the last time Hazel took a look at them. If it finds any modified files, it executes the shell script on them. This in turn causes mogenerator to do its magic.

I find this to work nicely for my needs, though it does have a few limitations. The first is that you have to manually add a duplicate set of the above Hazel rule for every Xcode project with which you want this to happen. The second is that it doesn’t automatically add the files to your Xcode project, but until Xmo’d works with Xcode 4 this will do at least half the job.

Using blocks as a flexible callback mechanism

September 13, 2009

Among the many numerous developer improvements in Snow Leopard is the addition of blocks to the Objective-C language (now at version 2.1). While working on some networking code using NSURLConnection I discovered that blocks provide a beautiful and flexible mechanism for implementing callbacks.

As a quick example, let’s say you want to initiate two GET requests to different websites. Perhaps the first is to post some form data and the other is just to download the website content. No matter the particular specifics, the idea is that for different types of NSURLConnection requests you will want to respond differently upon completion. For the first you might want to update the UI to show that the form data was posted. For the second you may want to display the site in a web view. To accomplish this you would traditionally need to keep an array around to hold not only the NSURLConnection instances themselves, but some kind of associated identifier for each one. Otherwise, when your delegate methods fire you will have no idea which action initiated the request in the first place — and thusly you wouldn’t necessarily know what to do next after the connection is finished loading.

The following is a bit of untested pseudo-code (a rough approximation if you will) of one of the traditional methods of handling this:

- (void)performMyFirstRequest
{
  NSURLRequest *request = [NSURLRequest requestWithURL:myFirstURL];
  NSURLConnection *connection = [NSURLConnection connectionWithRequest:request 
                                         delegate:self];
 
  [_connections addObject:[NSDictionary dictionaryWithObject:connection, 
       @"NSURLConnection", @"myFirstRequestIdentifier", @"identifier", nil]];
}
 
- (void)performMySecondRequest
{
  NSURLRequest *request = [NSURLRequest requestWithURL:mySecondURL];
  NSURLConnection *connection = [NSURLConnection connectionWithRequest:request 
                                         delegate:self];  
  [_connections addObject:[NSDictionary dictionaryWithObject:connection, 
       @"NSURLConnection", @"someOtherIdentifierHere", @"identifier", nil]];
}
 
- (void)connection:(NSURLConnection *)connection 
           didReceiveResponse:(NSURLResponse *)response
{
  NSDictionary *connectionInfo = [self _infoForConnection:connection];
  NSString *identifier = [connectionInfo objectForKey:@"identifier"];
 
  if ([identifier isEqualToString:@"myFirstRequestIdentifier"])
    NSLog( @"The first request was successful!" ); // do something else
  else if ([identifier isEqualToString:@"someOtherIdentifierHere"])
    NSLog( @"The second request was successful!" );
 
  // Remember to remove the connectionInfo dictionary from our _connections 
  // ivar once the connection is finished loading
}
 
- (NSDictionary *)_infoForConnection:(NSURLConnection *)connection
{
  for (NSDictionary *connectionInfo in _connections)
  {
    if ([connectionInfo objectForKey:@"NSURLConnection"] == connection)
      return connectionInfo;
  }
 
  return nil;
}

In short, you will have to have a bunch of conditional if-else statements in order to properly respond to each different connection’s completion. The alternative approach is to write a URLConnectionManager class based on blocks:

// URLConnectionManager.h
 
@interface URLConnectionManager : NSObject 
 
- (void)performRequest:(NSURLRequest *)request 
         responseBlock:(void (^)(NSURLResponse *response))responseBlock;
 
@end
 
// URLConnectionManager.m
 
- (void)performRequest:(NSURLRequest *)request 
         responseBlock:(void (^)(NSURLResponse *response))responseBlock
{
  NSURLConnection *connection = [NSURLConnection connectionWithRequest:request 
                                          delegate:self];
  [connection associateValue:(id)responseBlock withKey:@"responseBlock"];
}
 
- (void)connection:(NSURLConnection *)connection 
           didReceiveResponse:(NSURLResponse *)response
{
  void (^responseBlock)(NSURLResponse *) = [connection associatedValueForKey:
                                                   @"responseBlock"];
  responseBlock( response );
}

This code also takes advantage of associated objects, another new language feature that allows us to avoid having to keep an array of connection info dictionaries around to store the associated responseBlock reference. (I am using an elegant category wrapper of the C API written by Andy Matuschak.)

Using this approach, the two connection requests can be implemented as follows:

// The first request
NSURLRequest *request = [NSURLRequest requestWithURL:myFirstURL];
 
[connectionManager performRequest:request 
                    responseBlock:^(NSURLResponse *response) {
  NSLog( @"Received a response from myFirstURL: %ld %@", 
                [(NSHTTPURLResponse *)response statusCode], response );
  // Perform whatever other specific magic you need for this response!
}];
 
// The second request
NSURLRequest *request = [NSURLRequest requestWithURL:mySecondURL];
 
[connectionManager performRequest:request 
                    responseBlock:^(NSURLResponse *response) {
  NSLog( @"Received a response from mySecondURL: %ld %@",
                [(NSHTTPURLResponse *)response statusCode], response );
  // Perform whatever other specific magic you need for this response!
}];

Note that there are no more if-else statements to deal with and even though the requests are being performed asynchronously, you can write a piece of code to deal with the response in the same spot that you make the initial connection invocation. This results in a much cleaner and more readable codebase.

Browse and/or download the full Xcode sample project on GitHub

Why support matters

April 08, 2009

I’ve been a happy paid user of CSSEdit 2 for well over two years now. So when MacRabbit released Espresso it seemed only logical for me to try it out. I used it for about a week until I deemed it fit for purchase. I sauntered on over to the MacRabbit online store and discovered (as pretty much expected) that there was a discount purchase option for current CSSEdit 2 owners (at 49.95€ rather than the full 59.95€). To qualify one has to enter a valid CSSEdit 2 license code, so I dutifully looked mine up in my e-mail archives, copied it, and pasted it into the appropriate text field.

But instead of allowing me to complete the order I was greeted by a message stating that my license code “is not a valid CSSEdit 2 code.” Of course, I knew this claim to be false since this is the same code that works just fine in my still functioning copy of CSSEdit. Just to be sure though I headed on over to the MacRabbit support page where I hoped the license code lookup form would offer some kind of resolution. I entered my e-mail address (which I double-checked in my mail archives to be the one I originally used to register CSSEdit) to have my code sent to me. That didn’t quite work:

“Couldn’t find anything with this e-mail address! Are you sure it is correct?”

The next step was to shoot off an e-mail to the support people:

Subject: Espresso discount for CSSEdit 2 owners
Date: March 24, 2009 22:43:36 EDT

Hi,

I am trying to purchase Espresso via the CSSEdit 2 discount option. The form, however, is not accepting my license code:

Name: (redacted) Code: (redacted)

It claims this is not a valid CSSEdit 2 license code. Furthermore, I don’t appear to be in your lost code lookup database either.

Thanks.

Indeed, I sent this on March 24th. Somewhere around three weeks later I was left wondering if MacRabbit even has any support people. But just to be sure, I sent a follow-up on April 6th.

Subject: Re: Espresso discount for CSSEdit 2 owners
Date: April 6, 2009 18:17:59 EDT

A reply would be most appreciated as there are only 2 more days left in the trial period.

Since I have fit Espresso nicely into my workflow for several projects and its trial period is not of infinite duration, I had to make a choice. Either I could buy it at the full price of 59.95€ and be done with it, or I could partake of the MacHeist 3 bundle to get a license for considerably less than even the discount option I was initially trying to use. I wasn’t originally looking to get the MacHeist bundle since I would rather just directly pay the authors of the one application I actually need. Quality software development is hard work and developing a good application can take an inordinate amount of time, so I don’t mind rewarding such effort by paying a fair price.

Alas, quality software is a package deal. If I am going to pay a quality price I expect a certain basic level of support. It is rather difficult to justify paying full price for a piece of software when the support part of it is not being handled very well (or, seemingly, not being handled at all). Therefore, I went with the MacHeist bundle. The outcome is quite favorable for me as I can continue using Espresso to get work done. I can’t say it is nearly as favorable for MacRabbit’s cashflow.

It is certainly not my intent to bludgeon MacRabbit with this. They make fine software. But they do need to tighten up ship a bit, for their own sake.

Switching to Google Reader

March 31, 2009

For several years now I have been using Cyndicate as my RSS feed reader. It is an excellent piece of software, but I have come to realize that I really don’t use any of the features that make the application so unique among its competition. I don’t use any filters, I don’t use ratings, and though I was taking advantage of its persistent architecture by filing interesting articles into various folders for future reference, it is a very rare occasion that I ever go into these archives to re-read anything I put there.

The most important thing I have been wanting for some time is something Cyndicate doesn’t offer right now. And that is the ability to synchronize across multiple devices. In particular I want to be able to check my feeds on my iPhone or MacBook Pro when I am away from my desktop machine. Sometimes I’d like to skim or even read read posts while standing in line.

In short, I have decided to switch to Google Reader for all of my subscriptions. Not content with using the web interface, however, I am trying out EventBox on the Mac side of this; for the iPhone it is Byline. Neither of these applications is really a terribly impressive fit and I will definitely miss Cyndicate’s persistence and overall polish. But for now this looks like a trade-off I am willing to deal with.