开发者

Stored Block Closure as IBAction

开发者 https://www.devze.com 2023-01-16 10:06 出处:网络
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 Interf开发者_StackOv

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 Interf开发者_StackOverflow社区ace 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.


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.


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消