我有以下Swift代码:
class ThingChecker {
static func checkThing() -> [String: [String]] {
return Thing.stringsDictionary()
}
}
其中Thing
是使用以下接口在Objective-C中实现的类:
@interface Thing : NSObject
+ (NSDictionary<NSString *, NSArray<NSString *> *> * _Nonnull)stringsDictionary;
@end
但是,当我运行我的应用并致电ThingChecker.checkThing()
时,我的应用程序崩溃并出现以下错误:
EXC_BAD_INSTRUCTION(代码= EXC_i386_INVOP,子代码0x0)
控制台中没有任何有用的打印件。它只显示(lldb)
。
在调试器的堆栈跟踪中找到了唯一远程有用的东西。我可以看到堆栈中的两个框架可能会给出一些线索。首先,在我的代码下面是:
_convertNSDictionaryToDictionary<A, B where ...> (NSDictionary?) -> [A : B]
就在下面,我看到了:
_arrayForceCast<A, B> ([A]) -> [B]
但是点击其中任何一个只是指向汇编代码。
这里发生了什么?是什么导致这次崩溃?我完全难过了。
答案 0 :(得分:1)
您要包装的Objective-C代码中存在错误。
编写Objective-C界面的人非常友好地添加了泛型注释,以便您获得更有用的类型信息。
如果没有泛型(和可空性)注释,有人可能懒得写出这样的界面:
+ (NSDictionary *)stringsDictionary;
这令人恼火,因为在Swift中,非常不方便的是以下签名:
class func stringsDictionary() -> [NSObject : AnyObject]!
但是随着泛型和可空性注释被添加到Objective-C代码中,因此:
+ (NSDictionary<NSString *, NSArray<NSString *> *> * _Nonnull)stringsDictionary;
我们最终更容易消化Swift签名:
class func stringsDictionary() -> [String: [String]]
很明显,这是一个字典,字符串键和字符串数组作为值。
但是在Objective-C代码中没有严格执行Objective-C注释。他们只是建议。在我们与Swift方面建立联系之前,这些泛型注释并没有严格执行。
因此,在通过应用这些Objective-C泛型注释的过程中,Swift通过您问题中提到的方法调用:
_convertNSDictionaryToDictionary<A, B where ...> (NSDictionary?) -> [A : B]
这是Objective-C中的NSDictionary
转换为Swift字典的方式。但是,如果字典中的实际内容与泛型签名(以及Swift因此推断出类型的类型)不匹配,则会导致此崩溃。
实际上,如果您尝试使用as!
运算符并且转换失败,则此崩溃大致相当于崩溃。
例如:
let someDict: AnyObject = ["foo": ["a", "b", "c"], "bar": [1, 2, 3]]
let stringDict = someDict as! [String: [String]]
此演员表会失败,您的应用会崩溃,因为someDict
无法解释为[String: [String]]
。最好的情况是,它可以解释为[String: [Any]]
。
因此,例如,如果Objective-C方法的实现如下所示:
+ (NSDictionary<NSString *, NSArray<NSString *> *> * _Nonnull)stringsDictionary {
return @{ @"foo": @[@"foo1", @"foo2", @"foo3", @"foo4", @"foo5"],
@"bar": @[@1, @2, @3, @4, @5],
@"baz": @[@"baz1", @"baz2", @"baz3", @"baz4", @"baz5"] };
}
尝试从Swift调用此方法会生成确切的错误消息,因为键@"bar"
的值是NSNumber
个对象的数组,所以充其量,这可以被视为Swift中的[String: [Any]]
。
我们可以修复Objective-C通用注释:
+ (NSDictionary<NSString *, NSArray *> * _Nonnull)stringsDictionary;
或者,我们可以修复实现并摆脱看似错误的值(因为该方法被称为stringsDictionary
且NSNumber
值显然不是字符串)。