Search code examples
iosobjective-cobjective-c-blocksobjective-c-category

Extending UIRefreshControl with block in a category


I really like the way blocks work and thought it would be nice to add them in a few place like setting the action for UIRefreshControl.

So I created a category to UIRefreshControl

@interface UIRefreshControl (Blocks)

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

- (id)initWitActionBlock:(void (^)())actionBlock;

@end

@implementation UIRefreshControl (Blocks)

- (id)initWitActionBlock: (void (^)())actionBlock {
    self = [super init];
    if (self) {
        self.actionBlock = [actionBlock copy];
        [self addTarget:self action:@selector(fireActionBlock) forControlEvents:UIControlEventValueChanged];
    }
    return self;
}

- (void)fireActionBlock {
        self.actionBlock();
}

@end

Which is crashing : reason: '-[UIRefreshControl setActionBlock:]: unrecognized selector sent to instance

But I really don't know blocks that much and also I don't really see the difference between this category and a subclass doing the same thing.

I think I don't fully understand what's happening with properties, so my questions are what should I do ? And if it's possible, is this okay ? Or maybe I shouldn't be doing this ever ?

EDIT : *The solution with associated reference thanks @Martin R!

static char const * const ActionBlockKey = "ActionBlockKey";

@interface UIRefreshControl (Blocks)

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

- (id)initWitActionBlock:(void (^)())actionBlock;

@end

@implementation UIRefreshControl (Blocks)
@dynamic actionBlock;

- (id)initWitActionBlock: (void (^)())actionBlock {
    self = [super init];
    if (self) {
        self.actionBlock = [actionBlock copy];
        [self addTarget:self action:@selector(fireActionBlock) forControlEvents:UIControlEventValueChanged];
    }
    return self;
}

- (void)fireActionBlock {
        self.actionBlock();
}

- (id)actionBlock{
    return objc_getAssociatedObject(self, ActionBlockKey);
}

- (void)setActionBlock:(void (^)())actionBlock{
    objc_setAssociatedObject(self, ActionBlockKey, actionBlock,  OBJC_ASSOCIATION_COPY_NONATOMIC);
}

@end

Solution

  • The problem is unrelated to blocks.

    The compiler does not synthesize properties defined in a class category, because that would require a corresponding instance variable, and you cannot add instance variables in a class category.

    Actually you should get a warning like

    property 'actionBlock' requires method 'actionBlock' to be defined - use @dynamic or provide a method implementation in this category
    

    I would recommend to create a subclass instead.