关闭

[Objective-C] Cocoa's Target-Action Pattern

406人阅读 评论(0) 收藏 举报

转自:http://justinvoss.com/2011/08/19/cocoa-target-action/


Despite being a thin wrapper on top of C, Objective-C has dynamic features that make it feel more like a peer to Ruby than to C++. In this short article, I’ll introduce one of those features and show you how to use it in your code to reduce coupling and increase reuse.

Selectors

A selector is essentially the name of an Objective-C method, realized as an object in code. It has the type SEL, which is a primitive (no memory management needed). The actual contents of the selector are opaque, but you can get one from a method name with the @selector() syntax.

SEL setToolbar = @selector(setToolbarItems:animated:);

You can also get a selector from an NSString. This is handy if you want to store the selector in a config file.

SEL setToolbar = NSSelectorFromString(@"setToolbarItems:animated:");

Using Selectors

Once you have the selector, there’s a few things you can do with it.

You can ask an object if it responds to that selector; this is like asking if the object implements this method. If you’ve ever done reflection in Java, prepare for a breath of fresh air!

if ([controller respondsToSelector:@selector(setToolbarItems:animated:)]) {
  /* it's possible to set toolbar items on this controller */
}

You can ask an object to perform that selector:

// direct
[controller viewDidLoad];

// indirect
SEL action = @selector(viewDidLoad);
[controller performSelector:action];

This is the same as calling the method directly, but because the selector could come from a variable, it’s possible to change the method at runtime.

For example, UIBarButtonItem uses a target and action to call your code when the button is tapped. You might have some code in your view controller like this:

- (void)viewDidLoad
{
  
  button = [[UIBarButtonItem alloc] initWithTitle:@"Done"
                                            style:UIBarButtonItemStyleBordered
                                           target:self
                                           action:@selector(doneButtonHit:)];
  // more code
}

- (void)doneButtonHit:(id)sender
{
  NSLog(@"The done button was tapped!");
}

When you run the app and tap on the button, you’ll see “The done button was tapped!” in the console. It’s as if the button has a line of code like [controller doneButtonHit:self], but obviously it doesn’t: the button is a generic object that you can use off-the-shelf. The secret sauce is performSelector!

This pattern, called “target-action”, is used throughout Cocoa, especially in user interface code. It allows the UI widgets to stay generic, while making it easy to integrate your custom controller without needing to subclass anything.

Implementing Target-Action

Let’s write a really simple object that implements the target-action pattern. We’ll call it a button, but we’ll skip writing any view-related code. For simplicity, let’s assume that when the user taps on the button, the button will receive the tapmessage.

Here’s our interface.

@interface JVButton : NSObject {
  id _target;
  SEL _action;
}

- (void)initWithTarget:(id)target action:(SEL)action;

- (void)tap;

@end

And here’s the implementation.

@implementation JVButton

- (void)initWithTarget:(id)target action:(SEL)action
{
  self = [super init];
  if (self) {
    _target = target;
    _action = action;
  }
  return self;
}

- (void)tap
{
  [_target performSelector:_action withObject:self];
}

@end

The init method is simple, just some vanilla setup. We store the target and action for later. Notice that we don’t retain the target.

In the tap method, we ask the target to perform the action. We also pass the button as an argument to the action. There’s a bunch of variations on the basicperformSelector: method to help with things like passing arguments or delaying before performing: check Apple’s documentation for all of them.

The controller wired up to this button might look like this:

- (void)viewDidLoad
{
  button = [[JVButton alloc] initWithTarget:self action:@selector(customButtonTapped:)];
}

- (void)customButtonTapped:(id)sender
{
  NSLog(@"The custom button was tapped!");
}

If you look back at the example with UIBarButtonItem, you’ll see that they’re almost identical! Mimicking Apple’s code is easier than it sounds, right? :)

When to Use Target-Action

The best time to reach for this pattern is when:

  • You need or want one object to stay generic (e.g., the button class) but be able to call into custom code.
  • The generic object has exactly one action to perform (e.g., being tapped).

If the generic object has more than one action to perform, or needs to collect information from your custom code, your problem is probably better solved with the delegate pattern or the data source pattern.

【yasi】模拟一下,完整代码如下


// ======= MyButton.h ======

#import <Foundation/Foundation.h>

@interface MyButton : NSObject

@property id target;

@property SEL action;

-(id) initWithTarget:(id)target withAction:(SEL)action;

-(void) tap;

@end

// ======= MyButton.m ======

#import "MyButton.h"

@implementation MyButton

-(id) initWithTarget:(id)target withAction:(SEL)action {
    _target = target;
    _action = action;
    return self;
}

-(void) tap {
    [_target performSelector:_action withObject:self];
}

@end

// ======= MyViewController.h ======

#import <Cocoa/Cocoa.h>
#import "MyButton.h"

@interface MyViewController : NSObject

@property MyButton* button;

-(id) init;

-(void) customButtonTapped:(id)sender;

-(void) tapButton;

@end

// ======= MyViewController.m ======

#import "MyViewController.h"

@implementation MyViewController

-(id) init {
    if (self = [super init]) {
        _button = [[MyButton alloc] initWithTarget:self withAction:@selector(customButtonTapped:)];
    }
    return self;
}

-(void) customButtonTapped:(id)sender {
    NSLog(@"CUSTOM BUTTON TAPPED");
}

-(void) tapButton {
    if (_button) {
        [_button tap];
    }
}

@end

// ======= main.m ======

#import <Foundation/Foundation.h>
#import "MyViewController.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MyViewController* view = [[MyViewController alloc] init];
        [view tapButton];
    }
    return 0;
}

运行结果:

> CUSTOM BUTTON TAPPED


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2728218次
    • 积分:23695
    • 等级:
    • 排名:第274名
    • 原创:158篇
    • 转载:596篇
    • 译文:0篇
    • 评论:202条
    最新评论