【问题标题】:Swift closure in array becomes nil in Objective-c数组中的 Swift 闭包在 Objective-c 中变为 nil
【发布时间】:2017-06-21 19:20:49
【问题描述】:

我创建了一个 Objective-c 方法,它将通过 NSInvocation 调用一个方法:

typedef void (^ScriptingEmptyBlock)();
typedef void (^ScriptingErrorBlock)(NSError *error);
- (void)scripting_execute:(NSString *)operation withParams:(nullable NSArray *)args {

    SEL selector = [self scripting_selectorForOperation:operation];
    Class class = [self class];
    NSMethodSignature *signature = [class instanceMethodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

    [invocation setSelector:selector];
    [invocation setTarget:self];
    for (int idx = 0; idx < args.count; idx ++) {
        id arg = args[idx];
        [invocation setArgument:&arg atIndex:idx + 2];

    }

    ScriptingEmptyBlock success = args[1];

    // Breakpoint added on next line to test for nil
    success(); // this is nil and would crash!
    // (lldb) po args.count
    // 3

    // (lldb) po success
    // Printing description of success:
    // (ScriptingEmptyBlock) success = 0x0000000000000000

    // (lldb) po args[1]
    // (Function)

    //[invocation getArgument:&success atIndex:2]; // also tried this and got nil as well
    [invocation invoke];

}

该方法采用“操作”,通过覆盖子类中的scripting_selectorForOperation: 将其转换为选择器,然后执行调用。

所有这些都有效,除了当调用的方法具有块参数时它们为 nil,我添加了我用 cmets 描述的 nil 测试,当尝试从数组中读取闭包时,它将为 nil。

调用如下:

let successClosure: ScriptingEmptyBlock = {
                    print("Renamed product")
                }
let errorClosure: ScriptingErrorBlock = { error in
                    print("Failed to rename product: \(error)")
                }
let params:[Any] = [ "testName", successClosure, errorClosure]
object.scripting_execute (ScriptOperation.updateProductName.rawValue, withParams: params)

为什么闭包变为零了?

【问题讨论】:

    标签: ios objective-c memory-management swift3 objective-c-blocks


    【解决方案1】:

    success 不是 nil(实际上,NSArray 不能包含nils)。如果您将其打印为NSLog(@"%@", success);,它将显示(Function),而不是(null)。如果你像NSLog(@"%@", [success class]); 这样打印它的类,它会说_SwiftValue。基本上,它是一个桥接到 Objective-C 的 Swift 值。

    问题在于success 指向的对象不是Objective-C 块。它是一个 Swift 闭包,Swift 闭包与 Objective-C 块不同。尝试像使用 Objective-C 块一样调用它会导致未定义的行为。调试器中的po 打印错误可能是因为假设它是ScriptingEmptyBlock 类型(块类型)而打印它。如果您执行po (id) success,它将打印(Function)

    关于如何从 Swift 显式地将 Objective-C 块放入数组中,我想出的唯一方法是:

    let params:[Any] = [ "testName",
                         successClosure as (@convention(block) () -> Void)!,
                         errorClosure as (@convention(block) (NSError) -> Void)!]
    object.scripting_execute (ScriptOperation.updateProductName.rawValue,
                              withParams: params)
    

    我不确定为什么必须将函数类型放在! 中,但它似乎不起作用。也许其他人可以找到更好的方法。

    【讨论】:

      【解决方案2】:

      我必须承认我不完全理解为什么会发生这种情况,但据我所知,这与使用 NSInvocation 无关,即使我们只是将 Swift 闭包传递给 Objective-通过id 类型参数的C 函数。通过id 传递一个Objective-C 块工作得很好,不知道为什么:Swift 闭包应该与Objective-C 块兼容。如您所知,NSArray 的元素属于id 类型,因此任何Objective-C 对象都可以是数组元素。

      要解决在 Objective-C 中访问通过 id 传递的 Swift 闭包的问题,​​可以引入一个包装类。

      // In a header:
      @interface EmptyBlockWrapper : NSObject
      @property EmptyBlock _blk;
      @end
      
      // In an implementation file (just an empty implementation):
      @implementation EmptyBlockWrapper
      
      @end
      

      那么我们可以在 Swift 中使用包装器实例而不是块作为数组元素:

      let myBlock : EmptyBlock = {
          print("In Swift EmptyBlock...")
      }
      
      let myBlockWrapper = EmptyBlockWrapper()
      myBlockWrapper._blk = myBlock
      

      在 Objective-C 方法中,我们可以如下调用它,假设 argsNSArray *

      EmptyBlockWrapper * emptyBlockWrapper = args[1];
      emptyBlockWrapper._blk();
      

      希望这会有所帮助。当然,这只是一个简化的例子,给你一个想法;这可以做得更好。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多