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

Gru*_*kes 6 realm ios swift

我有一个使用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
    }
Run Code Online (Sandbox Code Playgroud)

这是崩溃文件的相关部分:

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

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

如果Realm崩溃的原因是因为getKey()失败了,那么为什么会失败呢?如果那不是原因,那么失败的其他原因是什么?如果出现故障,代码可以执行任何操作(例如重试创建Realm对象)吗?

Pat*_*ner 0

初始化方法

\n\n

我首先要解决这个问题:在这种情况下,这不太可能与您的崩溃有关,但是当您重写任何 init 方法时,您应该始终调用 super 的版本除非没有超类或没有可用的超类实现可供调用,否则该 init 方法。在 Objective-C 中,你将 [super init] 的结果分配给 self,但是,这种模式并没有被 swift 采用,而且从 Apple 的文档中还不清楚,当你从直接调用 super.init() 时会发生什么Swift 中的 NSObject 子类。我现在已经筋疲力尽了,以为我确实花了一些时间查看 apple/swift GitHub 存储库Apple/Swift Github等其他地方,但无法找到有关从 Swift 子类调用 NSObject init 的任何真正令人满意的信息。如果您确实想了解更多信息,而不只是相信我的话,我绝对会鼓励您继续搜索。在那之前,如果您从 Swift 子类调用 NSObject 的 init() ,它不太可能会导致问题,并且它可能在某些时候也能拯救您的屁股!;)

\n\n

碰撞

\n\n
\n

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

\n
\n\n

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

\n\n
\n

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

\n
\n\n

我查看了您在那里使用的 Realm API,并不清楚为什么对 Realm(configuration:) 的调用失败,尽管显然这应该是可能的,因为调用可能会抛出。但是,文档没有说明它将抛出的条件。

\n\n
\n

如果出现失败,代码可以执行哪些操作(例如重试创建 Realm 对象)?

\n
\n\n

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

\n\n
\n

有时您知道抛出函数或方法不会\xe2\x80\x99t,事实上,在运行时会抛出错误。在那些场合,你可以写试试!在表达式之前禁用错误传播并将调用包装在运行时断言中,不会引发任何错误。如果确实抛出错误,您\xe2\x80\x99将收到运行时错误。Swift 错误处理文档

\n
\n\n

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

\n\n

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

\n\n
if let realm = try? Realm(configuration: configuration) {\n    // Do something with realm and ignore the error\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
let realm: Realm? = nil\ndo {\n   realm = try Realm(configuration: configuration)\n   // do something with realm\n} catch let e {\n   realm = nil\n   // do something with the error\n   Swift.print(e)\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者:

\n\n
do {\n    let realm = try Realm(configuration: configuration)\n    // do something with realm\n} catch let e {\n    // do something with the error\n    Swift.print(e)\n}  \n
Run Code Online (Sandbox Code Playgroud)\n\n


\n因此,这可能不是您永远不会让 Realm 调用失败的黄金门票,但我希望它能提供一些帮助,使您的代码更加健壮。对于任何错误,我深表歉意,对我来说已经晚了。祝你好运和欢呼!:)

\n