在 obj-c 中传递块的问题是什么?

What is this problem passing blocks in obj-c?

Xcode11.4.1,IOS13.3.1

背景:为了获得一个图形化的、程式化的应用程序并 运行ning,我广泛地使用了 UIAlertController,我知道有一天,随着图形设计和艺术作品的完成,我会用自定义 class。那一天已经(部分)到来,UIAlertController 的一些(但不是全部)实例可以迁移到自定义 class。为了简化转换,我构建了自定义 class 以具有相同的 footprint/function 调用但具有不同样式的 ENUM。目前,自定义 class 是 UIAlertController 的子class,并决定是显示股票警报还是自定义警报。最终,当所有自定义警报图形都可用时,我将删除子 classing,自定义 class 将独立。

除了我 运行 遇到传递 UIAlertAction 处理程序并因 EXC_BAD_ACCESS.

而崩溃的情况外,这一切都按预期工作。

首先,我将块定义如下:

typedef void(^buttonActionHandler)();

然后我子classed UIAlertAction 并添加了一个方便的变量来访问处理程序(因为处理程序似乎隐藏在 UIALertAction 的深处并且通常无法访问)。

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

我为 actionWithTitle 添加了覆盖,这是我看到我不理解的行为的地方。

+(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 

据我所知,action.actionHandlertestHandler 的定义相同。那么,为什么前者工作而后者崩溃呢?这是问题的最简单形式,但我实际上发现它试图在代码的后面传递处理程序。

所以问题是:

  1. 我在 wrong/different 和 testHandler 对比 action.actionHandler 做什么?

Rob 的评论是提示。我忘记将参数传递给处理程序,它是一个 UIAction 因此以下命令正常工作:

 testHandler(action);

我只能假设 action.actionHandler() 在没有参数的情况下工作,因为即使处理程序的参数为 nil,Apple 仍然会选择父 action

考虑到 handler 带有参数,而 buttonActionHandler(按照惯例可能应该称为 ButtonActionHandler)没有参数,我感到很惊讶。

因此,您应该定义 ButtonActionHandler typedef 以包含参数:

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