无法在Swift类中调用抛出并从Obj-C返回非void的方法

Pau*_*w11 5 exception objective-c swift

我希望在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
}
Run Code Online (Sandbox Code Playgroud)

这很好,来自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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

是否真的不可能使用具有在Objective-C上下文中抛出的非void返回的Swift函数?

nhg*_*rif 6

正如我的评论中所指出的,我不清楚为什么这不起作用.该文档没有给出任何建议,这不应该工作,但它也没有明确说明它应该.我阅读文档说它应该有效.

话虽如此,我们可以将它包装在一个可以从Objective-C调用的方法中,方法是将返回类型更改为结果Void并使用inout参数:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,根据你的评论,如果我们返回一个可以桥接到Objective-C类(显然不包括Bool)的值,编译器会很高兴,所以我们可以这样包装它:

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

在解决这个问题时,很明显为什么只有当我们返回一个可以映射到Objective-C类的值时,这才有效.

编译器不允许您从标有@objc和的方法返回一个可选项throws.为什么?因为虽然在Swift方面,我们使用try语义来调用方法,但Objective-C中的方法完全不同. nil用于表示失败.

所以尝试在Swift中创建一个带有签名的方法:

@objc func doSomething(value: Int) throws -> NSNumber?
Run Code Online (Sandbox Code Playgroud)

生成此警告:

抛出方法不能标记为@objc,因为它返回一个可选类型'NSNumber?'的值; 'nil'表示没有Objective-C


最后,我仍然非常建议您将Swift方法编写为返回签名Bool并编写返回的包装器方法,NSNumber以便我们仍然可以使用最准确类型的Swift方法.

此外,如果你注意到,Bool 可以愉快地自动装箱NSNumber.我的包装器方法被写为返回类型NSNumber,但我正在包装的方法返回类型Bool,但Swift非常乐意Bool从应该返回的方法返回NSNumber.问题很可能是默认情况下,如果未指定,Swift将Bool转换为Objective-C BOOL,这是一个非类.

如果你的返回类型是,Objective-C将无法区分三种可能的情况BOOL.它只有两个可能的BOOL方法返回:YESNO.随着Bool映射到NSNumber,它可以返回@YES,@NOnil.

如果是我的话,NSNumber对我来说不够严格.我可能会被鼓励为此编写自己的包装类.

@objc class BooleanObject: NSObject, BooleanType {
    let boolValue: Bool

    init(_ boolValue: Bool) {
        self.boolValue = boolValue
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,该BooleanType协议意味着Swift仍然非常乐意使用这种类型的变量,就好像它们是常规变量一样Bool.

let b = BooleanObject(true)

if b {
    // b's boolValue property is true
}
Run Code Online (Sandbox Code Playgroud)