偶尔无法创建加密的Realm对象的原因是什么?

时间:2017-07-21 20:35:52

标签: ios swift realm

我有一个使用Realm发布的应用程序,并且有一些崩溃日志显示有时无法使用配置创建领域导致EXC_BREAKPOINT(SIGTRAP)崩溃。 (有几百个应用程序安装的9个崩溃文件,所以它不是经常发生的事情)

@objc class Database : NSObject
{    
    let configuration = Realm.Configuration(encryptionKey: Database.getKey() as Data)
    var transactionRealm:Realm? = nil

    override init()
    {
        let realm = try! Realm(configuration: configuration) // Crash here
        <snip>
    }

    // This getKey() method is taken from the Realm website
    class func getKey() -> NSData {
        let keychainIdentifier = "Realm.EncryptionKey.AppKey"
        let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8, allowLossyConversion: false)!

        // First check in the keychain for an existing key
        var query: [NSString: AnyObject] = [
            kSecClass: kSecClassKey,
            kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
            kSecAttrKeySizeInBits: 512 as AnyObject,
            kSecReturnData: true as AnyObject
        ]

        // To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item
        // See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328
        var dataTypeRef: AnyObject?
        var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
        if status == errSecSuccess {
            return dataTypeRef as! NSData
        }

        // No pre-existing key from this application, so generate a new one
        let keyData = NSMutableData(length: 64)!
        let result = SecRandomCopyBytes(kSecRandomDefault, 64, keyData.mutableBytes.bindMemory(to: UInt8.self, capacity: 64))
        assert(result == 0, "REALM - Failed to get random bytes")

        // Store the key in the keychain
        query = [
            kSecClass: kSecClassKey,
            kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
            kSecAttrKeySizeInBits: 512 as AnyObject,
            kSecValueData: keyData,
            kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock
        ]

        status = SecItemAdd(query as CFDictionary, nil)
        assert(status == errSecSuccess, "REALM - Failed to insert the new key in the keychain")

        return keyData
    }

以下是崩溃文件的相关部分:

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000103c0f30c
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [0]
Triggered by Thread:  0

Thread 0 name:
Thread 0 Crashed:
0   libswiftCore.dylib              0x0000000103c0f30c 0x103ab4000 + 1422092
1   libswiftCore.dylib              0x0000000103c0f30c 0x103ab4000 + 1422092
2   libswiftCore.dylib              0x0000000103b13d2c 0x103ab4000 + 392492
3   libswiftCore.dylib              0x0000000103b13bf4 0x103ab4000 + 392180
4   My app                          0x000000010334ff14 _TFC14Caller_Name_ID8DatabasecfT_S0_ + 1648 (Database.swift:21)
5   My app                          0x0000000103330624 -[Model createDatabase] + 200 (Model.m:271)

Database.swift文件的第21行是Init()方法中上面代码中指示的那个。

如果Realm崩溃的原因是因为getKey()失败了,那么为什么会失败呢?如果那不是原因,那么失败的其他原因是什么? 如果发生故障,代码可以做什么(例如重试创建Realm对象)?

1 个答案:

答案 0 :(得分:0)

init方法

我首先要解决这个问题:在这个实例中,这不太可能与你的崩溃有关,但是当你覆盖任何init方法时,你应该总是调用该init方法的超级版本,除非没有超类或没有可用的超类实现来调用。在Objective-C中你将[super init]的结果分配给self,但是,这个模式并没有被swift采用,而且从Apple的文档中不清楚当你从一个直接的NSObject子类中调用super.init()时会发生什么。迅速。我在这个时候失去了动力,以为我花了一些时间在其他地方查看apple / swift GitHub存储库Apple/Swift Github,并且无法找到任何关于从Swift子类调用NSObject init的确实令人满意的信息。如果你真的想知道更多而不仅仅是接受我的话,我肯定会鼓励你继续搜索。在那之前,如果从Swift子类调用NSObject的init()并且它可能在某些时候也可以保存你的屁股,那么它不太可能导致问题! ;)

崩溃

  

如果Realm崩溃的原因是因为getKey()失败了,那么为什么会失败呢?

我不确定为什么getKey()可能会失败,但这不太可能是导致崩溃的原因。我相信在调用init()内部的时间代码时,属性的默认值是可用的,在这种情况下,你的应用程序在它到达init()内的调用之前会崩溃。我明天会对此进行一些研究。

  

如果那不是原因,那么失败的其他原因是什么?

我已经查看了你在那里使用的Realm API,并且不清楚为什么对Realm(配置:)的调用失败了,尽管很明显,因为调用可以抛出,所以预计可以实现。但是,文档没有说明它将抛出的条件。

  

如果发生故障,代码可以做什么(例如重试创建Realm对象)?

是的!幸运的是,“尝试”机制除了“尝试!”之外还有其他变化。实际上,在这种情况下,应用程序似乎崩溃的原因是你正在尝试使用!在生产代码中,只应在您知道调用极不可能失败的情况下使用,例如从应用程序包内部检索应用程序附带的资源。根据Apple的文档:

  

有时你知道抛出函数或方法实际上不会在运行时抛出错误。在那些场合,你可以写试试!在表达式之前禁用错误传播并在运行时断言中包装调用,不会抛出任何错误。如果实际抛出了错误,您将收到运行时错误。 Swift Error Handling Documentation

当涉及到影响用户体验时,我总是喜欢谨慎行事,所以即使尝试过一次,我也会感到非常惊讶!在我的任何代码中(甚至Swift游乐场玩具和实验)。

为了优雅地失败并重试或采取其他行动,您的代码需要使用try吗?它会将抛出的错误转换为可选项,如:

if let realm = try? Realm(configuration: configuration) {
    // Do something with realm and ignore the error
}

或者您可以使用完整的try-catch机制,如下所示:

let realm: Realm? = nil
do {
   realm = try Realm(configuration: configuration)
   // do something with realm
} catch let e {
   realm = nil
   // do something with the error
   Swift.print(e)
}

或者:

do {
    let realm = try Realm(configuration: configuration)
    // do something with realm
} catch let e {
    // do something with the error
    Swift.print(e)
}  


所以这可能不是你从未让这个Realm调用失败的黄金票,但我希望它能为你的代码提供更强大的帮助。我为任何错误道歉,这对我来说已经很晚了。祝你好运和欢呼! :)