什么是_convertNSDictionaryToDictionary <a,b =“”where =“”... =“”>(NSDictionary?) - &gt; [A:B]为什么会崩溃我的应用程序?

时间:2016-04-07 01:35:29

标签: objective-c swift generics

我有以下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)

enter image description here

在调试器的堆栈跟踪中找到了唯一远程有用的东西。我可以看到堆栈中的两个框架可能会给出一些线索。首先,在我的代码下面是:

_convertNSDictionaryToDictionary<A, B where ...> (NSDictionary?) -> [A : B]

就在下面,我看到了:

_arrayForceCast<A, B> ([A]) -> [B]

但是点击其中任何一个只是指向汇编代码。

这里发生了什么?是什么导致这次崩溃?我完全难过了。

1 个答案:

答案 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;

或者,我们可以修复实现并摆脱看似错误的值(因为该方法被称为stringsDictionaryNSNumber值显然不是字符串)。