Thunk
Thunk

Reputation: 4177

What is this problem passing blocks in obj-c?

Xcode 11.4.1, IOS 13.3.1

Background: to get a graphical, stylized app up and running, I used UIAlertController extensively, knowing that someday, as the graphical design and artwork finalized, I would be replacing it with a custom class. That day has (partially) arrived, and migration to the custom class is available for some--but not all--of the instances of UIAlertController. In an effort to simplify the transition, I've built the custom class to have the same footprint/function calls but with a different style ENUM. Currently the custom class is subclassed from UIAlertController and makes decisions on whether to display the stock alert or the custom alert. Eventually, when all the custom alert graphics are available, I will remove the subclassing and the custom class will stand alone.

This is all working pretty much as intended, except I've run into a case of passing the UIAlertAction handler that crashes with EXC_BAD_ACCESS.

To start with, I defined the block as follows:

typedef void(^buttonActionHandler)();

Then I subclassed UIAlertAction and added a convenience variable to access the handler (since the handler seems to be hidden deep inside UIALertAction and not normally accessible).

@interface GoodAlertAction : UIAlertAction
{
}
    @property buttonActionHandler actionHandler;
@end 

I added an override for actionWithTitle and this is where I see behavior I don't understand.

+(instancetype)actionWithTitle:(NSString *)title style:(GoodAlertActionStyleTypes)style handler:(void (^)(UIAlertAction * _Nonnull))handler
{

    if (style == UIAlertActionStyleDefault || style == UIAlertActionStyleCancel || style == UIAlertActionStyleDestructive)
    {
        //non-migrated alert; use standard UIAlertAction
        return (GoodAlertAction *) [UIAlertAction actionWithTitle:title style:(UIAlertActionStyle) style handler:handler]
    }

    //migrated alert; use custom class
    GoodAlertAction action = [GoodAlertAction new];

    action.actionHandler = handler;
    action.actionHandler();     <--Successfully runs the handler. 

    buttonActionHandler testHandler = handler;
    testHandler();              <--Crashes with EXC_BAD_ACCESS 

As far as I can see, action.actionHandler and testHandler are defined the same. So, why does the former work and the latter crashes? That's the simplest form of the problem, but I actually uncovered it trying to pass the handler around later in the code.

So the question is:

  1. What am I doing wrong/different with testHandler vs action.actionHandler?

Upvotes: 0

Views: 113

Answers (2)

Rob
Rob

Reputation: 437632

I’m surprised that either works given that handler takes a parameter, whereas buttonActionHandler (which probably should be called ButtonActionHandler as a matter of convention) doesn’t.

So, you should define the ButtonActionHandler typedef to include the parameter:

typedef void(^ButtonActionHandler)(UIAlertAction * _Nonnull action);
typedef UIAlertActionStyle GoodAlertActionStyle;

@interface GoodAlertAction: UIAlertAction
@property (copy) ButtonActionHandler actionHandler;
@end

@implementation GoodAlertAction

+ (instancetype)actionWithTitle:(NSString *)title style:(GoodAlertActionStyle)style handler:(ButtonActionHandler)handler
{
    if (style == UIAlertActionStyleDefault || style == UIAlertActionStyleCancel || style == UIAlertActionStyleDestructive)
    {
        //non-migrated alert; use standard UIAlertAction
        return (GoodAlertAction *) [UIAlertAction actionWithTitle:title style:(UIAlertActionStyle) style handler:handler];
    }

    //migrated alert; use custom class
    GoodAlertAction *action = [GoodAlertAction new];

    action.actionHandler = handler;
    action.actionHandler(action);     // added parameter

    ButtonActionHandler testHandler = handler;
    testHandler(action);              // added parameter

    return action;
}

@end

Upvotes: 2

Thunk
Thunk

Reputation: 4177

Rob's comment was the tip. I had forgotten to pass the argument to the handler, which is a UIAction Thus the following command worked properly:

 testHandler(action);

I can only assume that action.actionHandler() works without the argument because Apple still picks up the parent action even when the handler's argument is nil.

Upvotes: 1

Related Questions