当作为Objective-C块调用时,Swift闭包会崩溃。

12

在我的项目中,我既有 Objective-C 代码又有 Swift 代码。我有一些包含块的属性对象,用于清理一些 UITableView 配置。在 Objective-C 中使用它是可以的,但在 Swift 中会崩溃。

我已经尽可能地将问题简化以便重现。

// in Objective-C
@interface MyClass : NSObject
@property (copy, nonatomic) NSString* (^block)();
- (NSString *)callTheBlock;
@end

@implementation MyClass
- (NSString *)callTheBlock {
    if (self.block) {
        return self.block();
    } else {
        return @"There is no spoon";
    }
}
@end

// In Swift
// I actually have this in my AppDelegate to run when the app starts, but that shouldn't matter

class AppDelegate: UIResponder, UIApplicationDelegate {
    var myClass: MyClass?

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
    self.myClass = MyClass()
    self.myClass?.block = { () -> String in 
        NSLog("In Closure")
        var string = "String From Closure" // <-- "po string" correctly prints the string
        return string // <-- This is where it crashes
    }

    let output = self.myClass?.callTheBlock()
    NSLog("\(output)")
...

}

我最后得到了“EXC_BAD_ACCESS”错误,所以我猜想这可能与某些不应该被释放的东西有关,但是我无法找出我的错误在哪里。
堆栈跟踪非常简洁:
* thread #1: tid = 0x4a8b6a, 0xbff2d9c8, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0xbff2d9c8)
  * frame #0: 0xbff2d9c8
    frame #1: 0xbff2d9e0

然而,如果我将该块更改为需要一个字符串作为参数:
// MyClass 
@property (copy, nonatomic) NSString* (^block)(NSString *string);

// Using it in Swift
self.myClass?.block = { (inString: String!) -> String in 
    NSLog("In Closure")
    var string = "String From Closure"
    return string
}

我得到了一个更有意义的堆栈跟踪:
* thread #1: tid = 0x4a9a38, 0x01b07e63 libobjc.A.dylib`objc_release + 19, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x40000013)
    frame #0: 0x01b07e63 libobjc.A.dylib`objc_release + 19
  * frame #1: 0x0005e442 MyApp`MyApp.AppDelegate.(inString=Some) -> (ObjectiveC.UIApplication, didFinishLaunchingWithOptions : Swift.Optional<ObjectiveC.NSDictionary>) -> Swift.Bool).(closure #1) + 594 at AppDelegate.swift:22
    frame #2: 0x0005e4ad MyApp`reabstraction thunk helper from @callee_owned (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) to @callee_owned (@in Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@out Swift.ImplicitlyUnwrappedOptional<Swift.String>) + 77 at AppDelegate.swift:19
    frame #3: 0x0005cbaa MyApp`partial apply forwarder for reabstraction thunk helper from @callee_owned (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) to @callee_owned (@in Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@out Swift.ImplicitlyUnwrappedOptional<Swift.String>) + 90 at AppDelegate.swift:0
    frame #4: 0x0005e5e7 MyApp`reabstraction thunk helper from @callee_owned (@in Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@out Swift.ImplicitlyUnwrappedOptional<Swift.String>) to @callee_owned (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) + 71 at AppDelegate.swift:19
    frame #5: 0x0005ce64 MyApp`partial apply forwarder for reabstraction thunk helper from @callee_owned (@in Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@out Swift.ImplicitlyUnwrappedOptional<Swift.String>) to @callee_owned (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) + 132 at AppDelegate.swift:0
    frame #6: 0x0005e7ee MyApp`reabstraction thunk helper from @callee_owned (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) -> (@owned Swift.ImplicitlyUnwrappedOptional<Swift.String>) to @callee_unowned @objc_block (@unowned Swift.ImplicitlyUnwrappedOptional<ObjectiveC.NSString>) -> (@autoreleased Swift.ImplicitlyUnwrappedOptional<ObjectiveC.NSString>) + 478 at AppDelegate.swift:19
    frame #7: 0x0008fa3f MyApp`-[MyClass callTheBlock](self=0x7beadc50, _cmd=0x000edee6) + 143 at MyClass.m:38
    frame #8: 0x0005bcf8 MyApp`MyApp.AppDelegate.application (application=0x7c160390, launchOptions=None, self=0x7c162d50)(ObjectiveC.UIApplication, didFinishLaunchingWithOptions : Swift.Optional<ObjectiveC.NSDictionary>) -> Swift.Bool + 1544 at AppDelegate.swift:24
    frame #9: 0x0005cf55 MyApp`@objc MyApp.AppDelegate.application (MyApp.AppDelegate)(ObjectiveC.UIApplication, didFinishLaunchingWithOptions : Swift.Optional<ObjectiveC.NSDictionary>) -> Swift.Bool + 101 at AppDelegate.swift:0
    frame #10: 0x0090d14f UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 309
    frame #11: 0x0090daa1 UIKit`-[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1810
    frame #12: 0x00912667 UIKit`-[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 824
    frame #13: 0x00926f92 UIKit`-[UIApplication handleEvent:withNewEvent:] + 3517
    frame #14: 0x00927555 UIKit`-[UIApplication sendEvent:] + 85
    frame #15: 0x00914250 UIKit`_UIApplicationHandleEvent + 683
    frame #16: 0x03b9df02 GraphicsServices`_PurpleEventCallback + 776
    frame #17: 0x03b9da0d GraphicsServices`PurpleEventCallback + 46
    frame #18: 0x01cf6ca5 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 53
    frame #19: 0x01cf69db CoreFoundation`__CFRunLoopDoSource1 + 523
    frame #20: 0x01d2168c CoreFoundation`__CFRunLoopRun + 2156
    frame #21: 0x01d209d3 CoreFoundation`CFRunLoopRunSpecific + 467
    frame #22: 0x01d207eb CoreFoundation`CFRunLoopRunInMode + 123
    frame #23: 0x00911d9c UIKit`-[UIApplication _run] + 840
    frame #24: 0x00913f9b UIKit`UIApplicationMain + 1225
    frame #25: 0x0005dbae MyApp`top_level_code + 78 at AppDelegate.swift:12
    frame #26: 0x0005dbeb MyApp`main + 43 at AppDelegate.swift:0
    frame #27: 0x024e3701 libdyld.dylib`start + 1

编辑:我最初认为这与异步调用有关,但是像这样直接调用它后,它是可重现的,因此我修复了代码和堆栈跟踪以反映这一点。

1个回答

14

通过弹出式文档的帮助,我理解了它!在处理Objective-C时,大多数东西都需要使用!。我非常确定这是因为Objective-C没有像Swift中的可选项(Optionals)概念。

与其将我的闭包定义为:

... self.myClass?.block = { (inString: String!) -> String in ...

将其更改为以下内容即可使其正常工作:

... self.myClass?.block = { (inString: String!) -> String! in ...

这是有道理的,因为这是弹出文档中推荐的。


3
最好使用"?"而不是"!"。这样,Swift编译器可以帮助您编写不会尝试访问nil的代码,而不是在运行时发现问题。 - Mike Pollard
1
我同意,只写Swift代码会更好。对于inString,是的,那可能是个好主意。但是对于返回值,实际上需要一个!。?实际上是编译错误。根据Quick Help,MyClass块的自动生成签名为var block: ((String!) -> String!)! { get set } - cjwirth
我们刚刚等了半天才弄清楚问题所在,原来是这个:/ 谢谢啊! - Michał Hernas
尽可能避免使用!,确保正确地解包值。!只会导致崩溃。并不是Xcode弹出窗口中推荐的所有内容都应该被视为最佳解决方案。 - RyanG
是的,绝对没错——这是一般情况下非常好的建议。请记住,这是2年半前Swift还很年轻的时候。特别是在ObjC-Swift互操作性发挥作用时,有很多隐式解包可选项,因为这就是语言的方式。 - cjwirth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接