将 SQL 存储添加到 NSPersistentStoreCoordinator 时如何调试/处理间歇性“授权被拒绝”和“磁盘 i/o”错误?

Posted

技术标签:

【中文标题】将 SQL 存储添加到 NSPersistentStoreCoordinator 时如何调试/处理间歇性“授权被拒绝”和“磁盘 i/o”错误?【英文标题】:How to debug/handle intermittent "authorization denied" and "disk i/o" errors when adding SQL store to an NSPersistentStoreCoordinator? 【发布时间】:2012-10-11 18:02:35 【问题描述】:

我在应用商店中有一个应用,并且正在使用日志服务来获取崩溃日志和相关的日志数据。我看到了间歇性崩溃(受影响的用户数量少,每个用户的崩溃数量少),但这让我感到困惑。

在这些崩溃中发生的情况如下:

    应用启动并初始化核心数据栈

    应用尝试使用以下代码将 SQL 存储添加到 NSPersistentStoreCoordinator(storeURL 有效):

    NSDictionary *options = @
        NSMigratePersistentStoresAutomaticallyOption : @(YES),
        NSInferMappingModelAutomaticallyOption : @(YES)
    ;
    
    sqlStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                         configuration:nil
                                                                   URL:storeURL
                                                               options:options
                                                             error:&error];
    

    添加此商店时出现以下错误之一:

NS错误:

域=NSCocoaErrorDomain Code=256 "操作无法完成。(Cocoa 错误 256.)" UserInfo=0x1dd946a0 NSUnderlyingException=授权被拒绝,NSSQLiteErrorDomain=23

NS错误:

域=NSCocoaErrorDomain Code=256 "操作无法完成。(Cocoa 错误 256.)" UserInfo=0xc6525d0 NSUnderlyingException=磁盘 I/O 错误,NSSQLiteErrorDomain=10

在这种情况下,应用程序将崩溃,因为应用程序需要 SQL 存储才能运行。我可以尝试通过尝试新的 storeURL 来优雅地处理此失败,但我不希望用户丢失现有数据。此外,我从未亲自重现过此问题,并且基于受影响的用户数量和崩溃日志数量较少,我认为这是一个影响较小的问题,并且不会在后续应用启动时再次出现。

我希望有一位 Core Data 专家能就如何调试和预防/处理这些情况提出一些建议。我的核心数据堆栈初始化代码直接来自 xcode 项目生成器,并且我已经排除了任何并发问题,因为持久存储协调器仅初始化一次(在启动时)并且在此初始化中发生此错误。

如果相关,很乐意提供更多代码/信息。

谢谢!

【问题讨论】:

您的某些代码是否正在删除/删除数据库文件?闻起来像是在删除文件时打开了文件。如果要删除,还需要确保删除所有 文件,而不仅仅是主文件。否则 DB 看起来会损坏。 除了我包含的代码之外,没有任何代码可以直接对数据库文件执行任何操作。一旦数据库打开,所有数据更改都通过托管对象上下文完成。此外,虽然这看起来像是某种形式的损坏,但它似乎只发生一次,并且不会在随后的发布中再次发生。由于我没有重新制作自己,这是基于分析崩溃日志的推测。 您是否在数据库上使用任何类型的加密(即NSPersistentStoreFileProtectionKey)? 无加密。我发布的代码显示了添加商店时使用的所有选项。 【参考方案1】:

它看起来像 XJones,我已经能够找到导致此问题的原因。它似乎是一个 ios 边缘案例错误或未记录的行为。我已在 Apple 错误 ID 12935031 下提交此文件。

由于从 iOS 5 开始,Core Data 存储使用数据保护 (默认加密)。

复制步骤:

1) 在设备上开启密码保护

2) 创建一个启动重要位置监控或区域监控的应用程序,即使在后台也能保持启动。 IE。使用后台重大位置变化或区域监控的应用。

3) 等待设备上的电池耗尽(也可能有其他原因)

4) 设备将关闭

5) 将设备连接到 Mac

6) 充电充足后,设备将启动。重要提示:此时不要解锁设备。

7) 退出或进入监控范围或导致位置发生重大变化。设备现在将自动重新启动应用程序,因为它已注册重要位置监控或区域监控

8) 但是,由于用户尚未解锁设备(尚未输入密码),应用程序将无法读取任何受保护的数据文件。在 Core Data 应用程序中,这将导致持久存储协调器无法将持久存储文件添加到托管对象上下文。这将导致应用程序崩溃或根据开发人员使用的代码甚至尝试重置数据库。在其他应用程序中,它可能会因其他原因导致崩溃,因为这是在 iOS 5 及更高版本中默认为 Core Data 存储打开的数据保护功能的意外、未记录的副作用。

在 Apple 纠正此问题或至少记录下解决方案或解决方法是确保您的应用程序停止监视 applicationWillTerminate 中的重大位置更改/区域或 通过为 NSFileProtectionKey 设置 NSFileProtectionNone 来关闭默认数据保护功能在将 Core Data 存储添加到持久存储协调器时,在选项字典中键入。 使用 while() 循环来等待文件存储可用,该循环检查受保护的数据是否可用。使用 KVO 可能还有其他方法可以做到这一点,但这种方法工作可靠,并且最容易插入到现有代码中,而无需重新设计整个应用程序启动过程。

更新:如果商店中的数据保护已经处于活动状态,看起来仅仅设置该密钥是不够的。你必须手动设置它:

<strike></strike>

[[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObject:NSFileProtectionNone forKey:NSFileProtectionKey] ofItemAtPath:storePath error:nil];

以下是需要后台位置监控的应用程序的解决方案,这要归功于 XJones 的更多输入以及对 Apple 分散文档的更多研究:

while(![[UIApplication sharedApplication] isProtectedDataAvailable]) 
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]];

此代码在您尝试将存储数据文件添加到持久存储协调器之前进入。

2015 年更新:您还可以将 NSPersistentStoreFileProtectionKey 设置为 NSFileProtectionNone。这将正确禁用文件保护(如果您不需要它)并且无需任何变通方法即可正常工作。它在以前的尝试中不起作用的原因是因为我正在测试的字典键不正确。

【讨论】:

感谢您的帮助。就我而言,我正在使用重大更改位置服务,并打算在应用程序终止或在后台禁用它,但我有一个错误,即在某些情况下后台位置服务仍然处于活动状态。 坏消息,复制过程是正确的,但修复似乎无效。 你能详细说明一下吗?您是说将文件保护设置为无事后无效吗?就我而言,我不希望我的应用程序在后台启动,所以如果我能完全防止这种情况发生,那么问题就会被消除。 似乎将文件保护设置为无无效,即使在新文件上也是如此。甚至尝试使用 NSFileManager 在退出时将其设置为无,但仍然没有任何变化。您关闭位置监控的修复应该仍然有效。但就我而言,问题无法通过这种方式解决,因为我特别需要后台监控。 查看我刚刚添加的新答案。 UIApplication 提供与访问受保护数据相关的通知。【参考方案2】:

我刚刚注意到UIApplication 文档中的以下通知:

UIApplicationProtectedDataDidBecomeAvailable UIApplicationProtectedDataWillBecomeUnavailable

这些在 iOS 4.0 及更高版本中可用。 @lupinglade,我认为您需要观察这些通知,并且只有在收到 UIApplicationProtectedDataDidBecomeAvailable 后才能访问受保护的文件(即商店)。

【讨论】:

这里是修复,在尝试将存储文件添加到持久存储协调器之前,这在代码中:while(![[UIApplication sharedApplication] isProtectedDataAvailable]) [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]]; 【参考方案3】:

@lupinglade 解决方案在我的情况下非常有效(VoIP 应用程序在从重新启动时启动),但我还想指出,当受保护的数据可用时调用 AppDelegate 的委托函数:

applicationProtectedDataDidBecomeAvailable:

这使得在我的案例中实施该解决方案变得更加容易。

根据头文件,它从 iOS 4 开始可用。

【讨论】:

【参考方案4】:

同样的问题,还是没找到解决办法。我发现这似乎与在设备上设置了锁码有关。如果没有锁码,我永远无法重现错误,现在我已经能够重现很多次了。控制台日志是:

错误是:Error Domain=NSCocoaErrorDomain Code=256 “操作无法完成。(Cocoa 错误 256。)” UserInfo=0x1fd80110 NSUnderlyingException=authorization denied, NSSQLiteErrorDomain=23

文件确实存在且未使用任何加密。

【讨论】:

您使用哪些步骤在您的应用中重现此问题? 您的应用程序是否使用定位服务或其他一些后台应用程序类型?似乎需要这种因素的组合来触发并重现此问题: - 必须在 iOS 设置中启用密码保护 - 您的应用程序必须使用位置监控,在我的情况下是区域监控 - 您的应用程序必须至少启动一次才能启动位置变化监控 - iPhone必须耗尽电池并自行关闭 - 将iPhone连接到Mac充电 - 当iPhone启动时,一旦电池有一点电量,iOS会出现此错误重新启动您的应用 似乎当iOS重新启动您的应用程序以进行重大的位置或区域监控更改时,它启动得太早或处于某种错误的“模式”,导致persistentstorecoordinator无法添加商店。由于复制过程的复杂性,我仍在测试,因为它需要一些时间来调试。只需在此之后强制退出(或如果它崩溃)/重新启动应用程序,一切都会再次运行。假设您的代码在发生错误时不会移开或删除 sqlite 文件。可能与使用密码锁功能时的加密有关。 我已经研究这个错误大约一年了,直到最近我才接近并能够重现它。 更多细节: - 由于我不使用密码锁定功能,我无法重现此问题,直到我决定打开它,因为一些遇到此问题的客户说他们正在使用它. - 当我的测试手机的电池耗尽并将其连接充电时,问题终于重现了 - 我现在已经能够以这种方式重现 4 或 5 次 - 有此问题的客户启用了位置(区域)监控选项在我们的应用程序中 - 几乎可以肯定,该错误与 iOS 在电池完全放电后在启动时自动重启应用程序有关

以上是关于将 SQL 存储添加到 NSPersistentStoreCoordinator 时如何调试/处理间歇性“授权被拒绝”和“磁盘 i/o”错误?的主要内容,如果未能解决你的问题,请参考以下文章

关于使用C#将图片用二进制方式存储到sql server数据库中

SQL - 将用户定义的变量添加到表(mysql)?

有没有办法将(破解)自定义 SQL 添加到核心数据提取中?

Snowflake SQL:需要将日期脊柱添加到现有表列

将“额外数据”添加到 LINQ to SQL 生成的实体?

sql server如何将字段添加到第一列