在不影响CloudKit正确性的情况下执行持久历史记录清除的正确方法是什么?

2024-03-04

目前,我们正在使用本地CoreData with CloudKit特征,通过使用NSPersistentCloudKitContainer.

为什么我们启用持久历史跟踪功能?

由于问题描述于https://stackoverflow.com/a/72554542/72437 https://stackoverflow.com/a/72554542/72437,我们需要启用NSPersistentHistoryTrackingKey.


清除历史记录

基于https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes,我们应该手动执行持久历史清除。


但是,目前尚不完全清楚我们如何以安全的方式清除历史记录,而不影响正确性CloudKit。我们倾向于使用以下设置运行一些测试。

  1. 运行模拟器。我们将在模拟器中执行插入操作
  2. 运行真实设备。由于步骤 1,真实设备将收到静默推送通知。
  3. 模拟器和真实设备都运行相同的代码。
  4. 每当我们在模拟器中插入一个项目时,我们都会观察真实设备中发生的情况。

测试1:处理后立即清除所有历史数据

@objc func storeRemoteChange(_ notification: Notification) {
    // Process persistent history to merge changes from other coordinators.
    historyQueue.addOperation {
        self.processPersistentHistory()
    }
}

/**
 Process persistent history, posting any relevant transactions to the current view.
 */
private func processPersistentHistory() {
    backgroundContext.performAndWait {
        
        // Fetch history received from outside the app since the last token
        let historyFetchRequest = NSPersistentHistoryTransaction.fetchRequest!
        historyFetchRequest.predicate = NSPredicate(format: "author != %@", appTransactionAuthorName)
        let request = NSPersistentHistoryChangeRequest.fetchHistory(after: lastHistoryToken)
        request.fetchRequest = historyFetchRequest

        let result = (try? backgroundContext.execute(request)) as? NSPersistentHistoryResult
        guard let transactions = result?.result as? [NSPersistentHistoryTransaction] else { return }

        ...
        
        // Update the history token using the last transaction.
        lastHistoryToken = transactions.last!.token
        
        // Remove history before the last history token
        let purgeHistoryRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: lastHistoryToken)
        do {
            try backgroundContext.execute(purgeHistoryRequest)
        } catch {
            error_log(error)
        }
    }
}

我们的观察是,真实设备出了问题CloudKit同步信息。真实设备要么获取重复的数据,要么其数据被删除。

我们对这个问题的假设是

  1. 持久性历史数据在多个持久性协调器之间共享。
  2. 我们可见的协调员,已经完成了交易的处理,在中标记一条记录lastHistoryToken,然后清除所有早于lastHistoryToken.
  3. 然而,还有另一个看不见的协调器,由CloudKit用于同步。很有可能,CloudKit协调器尚未处理已删除的历史交易。
  4. 这会导致所有数据出错,当CloudKit倾向于同步真实设备数据,无需必要的交易历史记录。

测试2:处理后清除所有超过2分钟的历史数据

我们对上面的代码进行了微调,仅删除超过 2 分钟的交易历史记录。

// Remove history older than 2 minutes.
let date = Date(timeMillis: Date.currentTimeMillis - 2*60*1000)
let purgeHistoryRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: date)
do {
    try backgroundContext.execute(purgeHistoryRequest)
} catch {
    error_log(error)
}

我们的观察是

  1. 如果上次之间有时间差storeRemoteChange触发和电流storeRemoteChange不到2分钟,真机即可获得correctCloudKit 同步信息。
  2. 如果上次之间有时间差storeRemoteChange触发和电流storeRemoteChange超过2分钟,真机就会得到wrongCloudKit 同步信息。真实设备要么获取重复的数据,要么其数据被删除。

总结与问题

基于如何在 CoreData+CloudKit 应用程序中修剪历史记录? https://stackoverflow.com/questions/64124940/how-to-prune-history-right-in-a-coredatacloudkit-app

作者建议

因此,在七年后修剪持久历史确实是安全的 处理后几天。

适用于 1 个用户、2 个设备的情况。

  1. 用户倾向于在他经常使用的设备 A 上频繁地读/写。
  2. 自上次在设备 B 上使用以来 7 天后,用户将在其很少使用的设备 B 上启动相同的应用程序。

这是否意味着设备 B 将获得错误的 CloudKit 同步信息? (根据测试 2 的观察,似乎是的)

如果是,在不影响CloudKit正确性的情况下执行持久历史记录清除的好方法是什么?


我如何运行测试 2?

您可以通过以下方式设置并运行测试 2

  1. 设置并运行示例https://developer.apple.com/documentation/coredata/synchronizing_a_local_store_to_the_cloud https://developer.apple.com/documentation/coredata/synchronizing_a_local_store_to_the_cloud
  2. Replace CoreDataStack.swift with https://gist.github.com/yccheok/df21f199b81b19764ffbcd4a4583c430 https://gist.github.com/yccheok/df21f199b81b19764ffbcd4a4583c430。它包含辅助函数Date,以及 2 分钟历史清除代码。
  3. 在模拟器中,点击右上角创建 1 条记录。您可以观察到真实设备现在有 1 条记录。
  4. 3分钟后,再次点击右上角。在模拟器中,可以观察到总共有2条记录。然而,在真实设备中,数据消失了!

enter image description here (In this picture, left device is a real device, and right device is a simulator)


UPDATE: 对于 Core Data + CloudKit 设置尤其重要

In 这个帖子 https://useyourloaf.com/blog/wwdc22-core-data-lab-notes/来自 WWDC22 核心数据实验室的 Apple Core Data 框架工程师回答了这个问题“我是否需要清除持久历史跟踪数据?“ 如下:

不,我们不推荐。 NSPersistentCloudKitContainer 使用 用于跟踪同步内容的持久历史记录令牌。如果删除历史记录 云同步已重置,必须从头开始上传所有内容。它 会恢复,但这不是良好的客户体验。不应该 通常需要删除历史记录。例如,苹果照片 应用程序不会修剪其历史记录,因此除非您生成大量 大量的历史并不能做到这一点。

tl;dr:

似乎在 7 天后清除持久历史记录几乎在所有情况下都有效。
如果必须同步 GB 级的数据,则可能不会。

我做了什么:

我可以重现该错误:
如果在Apple的演示应用程序中,在清除持久历史记录后同步数据,则可能会显示错误的数据。显然,一些对于演示应用程序至关重要的信息已被删除。

下面,我开始使用干净的设置进行测试:
我从模拟器和设备中删除了该应用程序,并清除了所有CD_PostiCloud 私有数据库、区域中的记录com.apple.coredata.cloudkit.zone,使用仪表板。
为了检查可能被无意删除的信息,我插入了func processPersistentHistory()Guard 语句中的打印语句用于过滤事务的持久历史记录:

guard let transactions = result?.result as? [NSPersistentHistoryTransaction],
      !transactions.isEmpty
      else {
        print("**************** \(String(describing: result?.result))")
        return
      }  

如果我在 Xcode 下的模拟器上运行该应用程序,则不会按预期显示任何条目,并且日志现在显示许多此类条目:

**************** Optional(<__NSArray0 0x105a61900>(

)
)  

显然,持久历史记录包含 iCloud 镜像内务信息,当持久历史记录被清除时,这些信息也会被删除。这向我表明镜像软件需要“足够的时间”才能成功完成其操作,因此只应清除“旧”历史条目。但什么是“老”呢? 7天?

接下来,在 Xcode 下的模拟器上,我安装并执行了应用程序并立即清除,如问题的测试 1 中所示。

// Remove history before the last history token
let purgeHistoryRequest = NSPersistentHistoryChangeRequest.deleteHistory(before: lastHistoryToken)
do {
  try taskContext.execute(purgeHistoryRequest)
} catch {
  print("\(error)")
}

在模拟器上,我添加了一个条目。该条目显示在仪表板中。

然后,在 Xcode 下的设备上,我还安装并执行了该应用程序并立即清除。该条目已正确显示,即 iCloud 记录已镜像到设备的持久存储,历史记录已处理并立即清除,尽管镜像软件可能没有“足够的时间”来成功完成其操作。

在模拟器上,我添加了第二个条目。该条目也显示在仪表板中。

然而,设备上的第一个条目消失了,即表格现在为空,但两个条目仍显示在仪表板中,即iCloud 数据未损坏.

然后我设置一个断点DispatchQueue.main.async of func processPersistentHistory()。仅当处理持久存储的远程更改时才会到达此断点。为了到达设备中的断点,我在模拟器中添加了第三个条目。因此,在设备中到达了断点,并且在调试器中我输入了

(lldb) po taskContext.fetch(Post.fetchRequest())  
▿ 3 elements
  - 0 : <Post: 0x281400910> (entity: Post; id: 0xbc533cc5eb8b892a <x-coredata://C9DEC274-B479-4AF5-9349-76C1BABB5016/Post/p3>; data: <fault>)
  - 1 : <Post: 0x281403d90> (entity: Post; id: 0xbc533cc5eb6b892a <x-coredata://C9DEC274-B479-4AF5-9349-76C1BABB5016/Post/p4>; data: <fault>)
  - 2 : <Post: 0x281403390> (entity: Post; id: 0xbc533cc5eb4b892a <x-coredata://C9DEC274-B479-4AF5-9349-76C1BABB5016/Post/p5>; data: <fault>)

这向我表明设备中的持久化存储有正确的数据,只有显示的表是错误的.

接下来我调查了func update in the MainViewController。这个函数是从调用的func didFindRelevantTransactions,在处理历史记录并过帐相关交易时调用。在我的测试期间,transactions.count总是 transactions.forEach.
我试图找出什么NSManagedObjectContext.mergeChanges做。因此我将代码修改为

transactions.forEach { transaction in
  guard let userInfo = transaction.objectIDNotification().userInfo else { return }
  let viewContext = dataProvider.persistentContainer.viewContext
  print("BEFORE: \(dataProvider.fetchedResultsController.fetchedObjects!)")
  print("================ mergeChanges: userInfo: \(userInfo)")
  NSManagedObjectContext.mergeChanges(fromRemoteContextSave: userInfo, into: [viewContext])
  print("AFTER: \(dataProvider.fetchedResultsController.fetchedObjects!)")
}  

看看会发生什么viewContext,我实现了

@objc func managedObjectContextObjectsDidChange(notification: NSNotification) {
  guard let userInfo = notification.userInfo else { return }
  print(#function, userInfo)  
}

并看看这如何影响fetchedResultsController,我也实现了

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, 
                didChange anObject: Any, 
                at indexPath: IndexPath?, 
                for type: NSFetchedResultsChangeType, 
                newIndexPath: IndexPath?) {
  print("**************** ", #function, "\(type) ", anObject)
}  

为了使日志相对较短,我在仪表板中删除了所有CD_Post除第一个条目外的条目,并从模拟器和设备中删除该应用程序。
然后,我在 Xcode 下运行模拟器和设备上的应用程序。两者都显示第一个条目。

然后我在模拟器中输入了另一个条目。不幸的是,正如预期的那样,设备上的表被清除了。这是设备的日志:

BEFORE: [<Post: 0x2802c2d50> (entity: Post; id: 0x9aac7c6d193c7772 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/Post/p1>; data: {
    attachments =     (
    );
    content = nil;
    location = nil;
    tags =     (
    );
    title = "Untitled 3:40:24 PM";
}), <Post: 0x2802d2a80> (entity: Post; id: 0x9aac7c6d195c7772 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/Post/p2>; data: <fault>)]
================ mergeChanges: userInfo: [AnyHashable("deleted_objectIDs"): {(
    0x9aac7c6d195c7772 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/Post/p2>,
    0x9aac7c6d193c7772 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/Post/p1>
)}]
managedObjectContextObjectsDidChange(notification:) [AnyHashable("managedObjectContext"): <_PFWeakReference: 0x2821a8100>, AnyHashable("deleted"): {(
    <Post: 0x2802d2a80> (entity: Post; id: 0x9aac7c6d195c7772 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/Post/p2>; data: {
    attachments =     (
    );
    content = nil;
    location = nil;
    tags =     (
    );
    title = nil;
}),
    <Post: 0x2802c2d50> (entity: Post; id: 0x9aac7c6d193c7772 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/Post/p1>; data: {
    attachments =     (
    );
    content = nil;
    location = nil;
    tags =     (
    );
    title = "Untitled 3:40:24 PM";
})
)}, AnyHashable("NSObjectsChangedByMergeChangesKey"): {(
)}]
****************  controller(_:didChange:at:for:newIndexPath:) NSFetchedResultsChangeType(rawValue: 2)  <Post: 0x2802d2a80> (entity: Post; id: 0x9aac7c6d195c7772 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/Post/p2>; data: {
    attachments =     (
    );
    content = nil;
    location = nil;
    tags =     (
    );
    title = nil;
})
****************  controller(_:didChange:at:for:newIndexPath:) NSFetchedResultsChangeType(rawValue: 2)  <Post: 0x2802c2d50> (entity: Post; id: 0x9aac7c6d193c7772 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/Post/p1>; data: {
    attachments =     (
    );
    content = nil;
    location = nil;
    tags =     (
    );
    title = "Untitled 3:40:24 PM";
})
managedObjectContextObjectsDidChange(notification:) [AnyHashable("updated"): {(
    <NSCKRecordZoneMetadata: 0x2802ce9e0> (entity: NSCKRecordZoneMetadata; id: 0x9aac7c6d193c77d2 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/NSCKRecordZoneMetadata/p1>; data: {
    ckOwnerName = "__defaultOwner__";
    ckRecordZoneName = "com.apple.coredata.cloudkit.zone";
    currentChangeToken = "<CKServerChangeToken: 0x2823fcdc0; data=AQAAAAAAAACQf/////////+gT9nZvOBLv7hsIaI3NVdg>";
    database = "0x9aac7c6d193c77e2 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/NSCKDatabaseMetadata/p1>";
    encodedShareData = nil;
    hasRecordZoneNum = 1;
    hasSubscriptionNum = 0;
    lastFetchDate = "2022-06-15 13:55:25 +0000";
    mirroredRelationships = "<relationship fault: 0x2821a3c60 'mirroredRelationships'>";
    needsImport = 0;
    needsRecoveryFromIdentityLoss = 0;
    needsRecoveryFromUserPurge = 0;
    needsRecoveryFromZoneDelete = 0;
    needsShareDelete = 0;
    needsShareUpdate = 0;
    queries = "<relationship fault: 0x2821a2560 'queries'>";
    records =     (
    );
    supportsAtomicChanges = 1;
    supportsFetchChanges = 1;
    supportsRecordSharing = 1;
    supportsZoneSharing = 1;
})
)}, AnyHashable("managedObjectContext"): <_PFWeakReference: 0x2821a1900>, AnyHashable("deleted"): {(
    <NSCKRecordMetadata: 0x2802ce850> (entity: NSCKRecordMetadata; id: 0x9aac7c6d193c7762 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/NSCKRecordMetadata/p1>; data: {
    ckRecordName = "3FB952E5-6B30-472E-BC6E-0116FA507B88";
    ckRecordSystemFields = nil;
    ckShare = nil;
    encodedRecord = "{length = 50, bytes = 0x6276786e f7090000 52070000 e0116270 ... 61726368 69000ee0 }";
    entityId = 3;
    entityPK = 1;
    lastExportedTransactionNumber = nil;
    moveReceipts =     (
    );
    needsCloudDelete = 0;
    needsLocalDelete = 0;
    needsUpload = 0;
    pendingExportChangeTypeNumber = nil;
    pendingExportTransactionNumber = nil;
    recordZone = nil;
}),
    <NSCKRecordMetadata: 0x2802cdcc0> (entity: NSCKRecordMetadata; id: 0x9aac7c6d195c7762 <x-coredata://496D2B54-DDB9-47EF-945A-CC1DBA1E14E8/NSCKRecordMetadata/p2>; data: {
    ckRecordName = "0919480D-16CB-49F9-8351-9471371040AC";
    ckRecordSystemFields = nil;
    ckShare = nil;
    encodedRecord = "{length = 50, bytes = 0x6276786e f7090000 52070000 e0116270 ... 61726368 69000ee0 }";
    entityId = 3;
    entityPK = 2;
    lastExportedTransactionNumber = nil;
    moveReceipts =     (
    );
    needsCloudDelete = 0;
    needsLocalDelete = 0;
    needsUpload = 0;
    pendingExportChangeTypeNumber = nil;
    pendingExportTransactionNumber = nil;
    recordZone = nil;
})
)}]
managedObjectContextObjectsDidChange(notification:) [AnyHashable("managedObjectContext"): <_PFWeakReference: 0x2821a3060>, AnyHashable("invalidatedAll"): <__NSArrayM 0x282f75830>(

)
]
AFTER: []  

这向我表明:

  • Before NSManagedObjectContext.mergeChanges,该表是正确的,即它包含帖子 p1 和 p2。
  • 两个帖子再次合并。
  • In the viewContext,两个帖子都被删除了(AnyHashable("deleted")).
  • The fetchedResultsController回应也删除了这两个帖子(NSFetchedResultsChangeType(rawValue: 2)).
  • 最终记录的是fetchedResultsController没有对象,因此表是空的。

作为最后检查,我发表了评论func processPersistentHistory()清除历史记录的代码,正如预期的那样,当我在模拟器中输入另一个条目时,该表也正确显示。

结论是什么?

  • 在持久存储(模拟器和设备)以及 iCloud 中,所有数据始终正确。
  • 如果镜像软件没有足够的时间来处理持久历史记录中的条目,则将远程存储更改合并到上下文会失败。
  • 这需要多长时间可能取决于必须同步的数据量。我的经验是,某些 kb 需要几秒钟,但这当然取决于许多参数。但如果是这样,7 天相当于需要同步一些 GB,这是相当不寻常的。在这方面,7 天后清除持久历史记录似乎是内存消耗和正确的应用程序操作之间的一个很好的折衷方案。

重现测试的进一步提示(这可能会帮助其他尝试相同的人):

按照建议,我下载了Apple的演示应用程序和您修改的核心数据堆栈。
它确实针对模拟器进行了编译,但对于设备,我必须在目标的“签名和功能”选项卡中设置 3 个附加设置:

  • 设置开发团队
  • 将包标识符设置为合理的值,例如com.<your company>.CoreDataCloudKitDemo.
  • 选择正确的 iCloud 容器,例如iCloud.com.<your company>.CoreDataCloudKitDemo.
  • 此外,我必须确保模拟器和设备登录到同一个 iCloud 帐户。请注意,对于模拟器来说,大约每天必须重新登录一次。大多数情况下,人们都会被提醒这样做,但有时却不会。

然后,我可以在模拟器和设备上运行该应用程序。
我在 CloudKit 控制台中验证了私有数据库区域com.apple.coredata.cloudkit.zone没有 CD_Post 类型的记录。由于数据不共享,因此不使用 iCloud 共享数据库。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在不影响CloudKit正确性的情况下执行持久历史记录清除的正确方法是什么? 的相关文章

  • 在 iOS 上使用 Web 服务的最佳方式?

    我想构建一个 iOS 应用程序 让您登录到网络服务 之后 应用程序将 当用户选择时 通过 https 发送登录名 密码以及请求的变量 例如 在请求 新闻更新 后 它将收到 XML 格式的请求信息 类似于
  • 在 IOS 上使用 AVComposition 混合两个音频文件

    我正在尝试混合两个音频文件 将一个音频文件放在另一个音频文件之上 不是缝合在一起 但我在 IOS 上学习 AVFoundation 时遇到了困难 我在这里遵循了这个答案 如何使用 AVMutableCompositionTrack 合并音频
  • Parse.com 和 Facebook 登录,运行无限循环

    我将 Parse 和 Facebook iOS SDK 都更新到了最新版本 当我尝试使用 Facebook 登录时 我的应用程序崩溃了 从调试器中我可以看到它正在无限循环中调用 3 4 个方法 我的登录代码如下所示 void openSes
  • 从命令行调试 iOS 应用程序构建

    我正在通过命令行构建 iOS 应用程序 但在调试它时遇到问题 如果我使用 XCode 进行构建 它会让我在设备上 构建和调试 而不会出现任何问题 但现在 我不知道如何使用 gdb 在设备上启动它并逐步执行它 如果我尝试 添加自定义目标 可执
  • 从 iOS 应用程序内的 Junos Pulse 获取用户凭据

    我正在通过 Junos Pulse 在 iPad 中建立 VPN 连接 以进入我组织的 Intranet 谁能告诉我是否有任何 iOS api 或 SDK 可用于获取在 iOS 应用程序内的 Junos pulse 中输入的用户凭据 Jun
  • 在 SwiftUI 中使用分段式选取器在两个页面之间滑动

    我有一个Picker with pickerStyle SegmentedPickerStyle 使其成为分段控件 我想让页面在之间平滑滑动 而不是使用条件语句替换视图 这是我迄今为止所做的 gif 这是到目前为止的代码 由if 而不是在不
  • 在iOS上,“添加到主页”缓存保存在哪里,如何清除它?

    我正在 iPad iOS v7 上制作一个 html5 游戏 当我将其添加到主页时 它非常顽固地释放缓存 如果我在 Safari 中查看它 这会按照您所期望的方式工作 如果我刷新一次或两次 页面就会以最新状态缓存 但在主页上却是另一回事 它
  • 错误域=AVFoundationErrorDomain代码=-11814“无法记录”

    它不断给我错误 错误域 AVFoundationErrorDomain代码 11814 无法记录 我不确定问题是什么 我试图在拍照后计数器达到 1 时录制声音 static int counter counter will always b
  • 如何在 iOS 中更改部分透明图像的颜色?

    我有一个具有部分透明度的单色图像 我有正常版本和 2X 版本的图像 我希望能够用代码将图像着色为不同的颜色 下面的代码适用于普通图像 但 2X 最终会出现伪影 正常图像可能有类似的问题如果是这样 由于分辨率的原因我无法检测到它 UIImag
  • 推入 UINavigationController 时隐藏 FBFriendPickerViewController 导航栏

    介绍一个实例FBFriendPickerViewController using presentViewController animated completion 非常简单 该类似乎是针对该用例的 但是 我想推送一个实例FBFriendP
  • 所需框架与静态库

    构建现代框架 https developer apple com videos play wwdc2014 416 says 每个应用程序都有自己的自定义框架副本 https stackoverflow com a 15262463 242
  • 在真实设备上展示测试广告

    这是我的代码 let request GADRequest request testDevices kGADSimulatorID XXXX2F32d69CCA859FFB559D0FEA3CF6483D08A6 adView load r
  • 关于窗口层次结构的警告

    我的调试器中出现这样的警告 这是什么意思 Warning Attempt to present
  • iphone NSDate 转换问题

    在我的 facebook 图表 Api 中 我正在获取这些数据 来自杰森 updated time 2011 05 17T14 52 16 0000 我正在使用此代码将其转换为有效的日期格式 NSDateFormatter df NSDat
  • SiriKit 错误:此应用程序不支持捐赠意图

    我在 Xcode 10 iOS 12 Beta 中捐赠自定义意图时遇到问题 我创建了一个在我的主应用程序目标和 OrderIntent 目标之间共享的自定义框架 我创建了一个 intentdefinition 文件 并将目标成员资格设置为我
  • 架构armv7的重复符号

    尝试在我现有的应用程序中使用 Layar SDK 时出现以下错误 我该如何解决这个问题 Ld Users pnawale Library Developer Xcode DerivedData hub afxxzaqisdfliwbzxbi
  • Google 地图 API -> OpenGLES 崩溃

    日志是从 Crashlytics 粘贴的 对于许多用户来说 崩溃经常发生 据我所知 它与设备 iOS 版本无关 我在我的代码中找不到任何错误 这似乎是纯粹的库问题 是 Google 地图 API 错误吗 我可以做些什么来修复它 或者我应该在
  • NVActivityIndi​​catorView 仅适用于特定视图

    我正在使用这个库https github com ninjaprox NVActivityIndi catorView https github com ninjaprox NVActivityIndicatorView用于显示加载指示器
  • 苹果企业程序分发问题[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 这个问题涉及到Apple iOS 开发者企业计划 http developer apple com programs ios enterprise 我
  • UIWebView Bug:-[UIWebView cut:]:无法识别的选择器发送到实例

    In the UIWebView 如果包含文本的输入元素具有焦点 并且按下按钮导致输入失去焦点 则随后双击输入以重新获得焦点并从出现的弹出栏中选择 剪切 或 复制 或 粘贴 会导致这UIWebView因错误而崩溃 UIWebView cut

随机推荐