无法从Obj-C调用具有抛出错误和非void返回类型的Swift类中的方法

7
我希望在Swift中有一个函数可以返回一个Bool,但如果出现异常,则也可以throw
例如:
 func doSomething(value: Int) throws -> Bool {
    if (value > 0) {
        return true
    } else if (value == 0) {
        throw NSError(domain: "SwiftClass", code: 0, userInfo: nil)
    }
    return false
}

这个函数在Swift中可以正常工作,但是如果我试图从Objective-C中使用这个函数,编译器无法找到该方法。我知道throws需要将Objective-C函数签名更改为doSomething:x error:&error,并且如果我将返回类型更改为Void,它就可以工作了。
func doSomething(value: Int) throws -> Void {
    if (value == 0) {
         throw NSError(domain: "SwiftClass", code: 0, userInfo: nil)
    } else if (value < 0) {
        throw NSError(domain: "SwiftClass", code: -1, userInfo: nil)
    }
}

但这有不同的语义。在第一个示例中,只需要处理异常(或非零NSError)如果有问题。使用此代码,我必须捕获异常(或检查错误)并确定它是否是真正的问题还是有效的“false”情况。

在Objective-C上下文中是否真的无法使用具有非void返回并引发异常的Swift函数?


嗯,在第一个示例中的throw后面加上return可以吗?在Swift中,如果一个方法抛出异常,它不会返回任何东西。在Objective-C中,设置错误参数并不能让你避免返回有效值。 - nhgrif
我已经做了那个(并更新了问题以反映更改)。没有区别。 - Paulw11
该宏影响 Objective-C 方法对 Swift 的可见性。这是相反的:使 Swift 方法对 Objective-C 可见。如果 doSomething 方法具有非 void 返回,则该方法简单地不会出现在 -Swift.h 文件中。 - Paulw11
我刚刚查看了这些文档,感觉很奇怪。首先,它们表明如果Swift方法返回void,Objective-C方法将返回BOOL(并添加错误参数)(这对我来说只有那么多意义)。但是没有明确说明当有非void返回时我们是否应该期望它能正常工作。 - nhgrif
如果没有明确指定它不适用于非 void 返回,我不知道我们应该如何假设它不适用,除非尝试并发现它不适用。 - nhgrif
啊哈!在函数声明中添加@objc给了我更多信息——一个错误——“抛出方法不能被标记为@objc,因为它返回类型为'Bool'; 返回'Void'或桥接到Objective-C类的类型”。将返回类型更改为NSNumber让我可以从Objective-C调用该函数,这很奇怪,因为我认为Swift会自动将Bools打包成NSNumber。如果该方法不抛出异常,则可以愉快地将布尔值返回给Objective-C。 - Paulw11
1个回答

8
如我所述,我不清楚为什么这个方法不能工作。虽然文档没有给出任何建议表明这个方法不能工作,但也没有明确说明它应该工作。我的理解是它应该能够工作。
话虽如此,我们可以将其包装在一个方法中,通过将返回类型更改为Void并使用inout参数来使其可从Objective-C调用:
func doSomething(value: Int) throws -> Bool {
    if (value > 0) {
        return true
    } else if (value < 0) {
        return false
    } else {
        throw NSError(domain: "SwiftClass", code: 0, userInfo: nil)
    }
}

func doSomething(value: Int, inout result: Bool) throws {
    do {
        result = try doSomething(value)
    } catch let error {
        // If need be, assign some default value to result.
        throw error
    }
}

根据您的评论,作为另一种选择,似乎编译器希望我们返回一个可以连接到Objective-C类的值(显然不包括Bool),因此我们可以将其包装如下:

@objc func doSomething(value: Int) throws -> NSNumber {
    do {
        return try doSomething(value)
    } catch let error {
        throw error
    }
}

在尝试使用此方法时,很明显为什么只有返回可以映射到Objective-C类的值才能起作用。

编译器不允许您从标记有@objcthrows的方法中返回可选项。为什么?因为在Swift端,我们使用try语义调用该方法,而在Objective-C中,这种方法完全不同。使用nil表示失败。

因此,在Swift中创建具有签名的方法是不可行的:

@objc func doSomething(value: Int) throws -> NSNumber?

生成以下警告:
抛出的方法不能标记为@objc,因为它返回一个可选类型'NSNumber?'的值;'nil'表示Objective-C失败。
但最终我仍然强烈建议您将Swift方法编写为具有返回Bool的签名,并编写一个返回NSNumber的包装器方法,以便我们仍然拥有最准确的类型的Swift方法。
此外,如果您注意到,Bool可以愉快地自动包装成NSNumber。我的包装器方法被编写为返回类型NSNumber,但是我要包装的方法返回类型Bool,但是Swift完全可以从应该返回NSNumber的方法中返回Bool。问题很可能是默认情况下,如果未指定,Swift的Bool会转换为Objective-C的BOOL,这是一个非类。
如果您的返回类型是BOOL,那么Objective-C将无法区分三种可能的情况。从BOOL方法只有两种可能的返回:YESNO。将Bool映射到NSNumber后,它可以返回@YES@NOnil
对于我来说,NSNumber并不太严格,我可能会被鼓励为此编写自己的包装类。
@objc class BooleanObject: NSObject, BooleanType {
    let boolValue: Bool

    init(_ boolValue: Bool) {
        self.boolValue = boolValue
    }
}

请注意,BooleanType 协议意味着 Swift 可以像处理普通的 Bool 类型一样使用此类型的变量,而不会出现问题。
let b = BooleanObject(true)

if b {
    // b's boolValue property is true
}

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