views:

143

answers:

3

I am trying to reduce ceremony and out of academic curiosity I want to know how to do the following without the IBAction method defined in the .m file to use a closure whenever an Interface Builder wired action occurs such as a button press. You could say that I want to imply the cancelButtonPress method below instead of having to define it. A UIViewController subclass or some magic stored in a category would be quite acceptable.

@interface MyViewController : UIViewController
{
    void(^doOnCancel)(void);
}

@property (nonatomic, copy) void(^doOnCancel)(void);

- (IBAction)cancelButtonPress:(id)sender;//I want this gone!

@end

I tried changing void to IBAction in the property and variable with no luck.

Edit: Alternative patterns that also reduce repetition in using closures for actions would also be useful.

The bounty is for a good pattern that will allow for closures to be arbitrarily used to service actions defined in IB in a way that could be used to reduce ceremony. The "can't do it" comments so far might or might not be correct.

+1  A: 

Without resorting to something like "Put the code that would normally be in cancelButtonPress: in forwardInvocation:," you can't. Interface Builder actions send messages. Calling a block cannot be the direct result of an action message.

It is possible to extend a control so that it calls a block instead of sending a normal action method to your controller, but that would require much more code, and it wouldn't magically make Interface Builder support it.

Chuck
+3  A: 

Here you go, a hack to allow any block-typed property to be used as an action: http://gist.github.com/589740

It is, as you say, of academic curiosity. I really don’t recommend using it for reals.

Also, it doesn’t strictly live up to your requirement, since the action is declared in an unimplemented category. This is purely so that Interface Builder can find it automatically by scanning headers. You can delete that and add the action manually in IB’s inspector, but that seems like a loss to me since keeping it in sync properly is harder that way.

Ahruman
This is really cool, and I'll try refactoring a controller/xib with it this evening to see what happens. It certainly meets the spirit of the bounty.
Peter DeWeese
Don’t miss the fork that does it better. :-)
Ahruman
+3  A: 

Ahruman's approach is good, but it's probably relying too much on the runtime. Other than performance issues, Swizzling and using initialize is more susceptible to bugs if someone else wants to play with the runtime at the same time. I suggest using C macros.

Define these macros at the top of your class (or anywhere, really):

typedef void(^BlockAction)(id sender);
#define BlockActionProperty(ACTION)     @property (copy) BlockAction ACTION;\
                                        - (IBAction) ACTION:(id)sender;

#define BlockActionSynthesize(ACTION)   @synthesize ACTION;\
                                        - (IBAction) ACTION:(id)sender {\
                                            if (ACTION) ACTION(sender);\
                                        }

Now to create a new action, all you need to do is replace the @property ... in the header with the following (for example):

BlockActionProperty(testAction);

and "synthesize" it in implementation with:

BlockActionSynthesize(testAction);

At anytime if you want to override this and use the normal action method, all you need to do is synthesize and implement the action as usual.

This is faster (and in my view cleaner) than doing it at runtime, and since the IBAction is defined Interface Builder can "see" its definition.

Mo
Interesting and simple. This would definitely reduce the apparent ceremony, and I'll try it out. As for the performance, I'm amazed at how much Rails figures out and does behind the scenes while still rendering a web page fast enough for most applications. I'll see if there is a noticeable difference using Ahruman's approach.
Peter DeWeese
Well yeah, I agree that even on mobile devices there wouldn't be much of performance hit - unless it's a big loop which is unlikely for Actions. I guess what I mean was more of an "academic" note than a practical issue ;-)
Mo