【问题标题】:Fetching CKAsset Image From CloudKit is Very Slow从 CloudKit 获取 CKAsset 图像非常慢
【发布时间】:2016-05-28 15:51:12
【问题描述】:

我使用 CloudKit 作为我的 iOS 应用程序的服务器后端。我用它来存放一些相对静态的数据以及一些图像(CKAsset)。当我需要从公共数据库中实际获取这些资产时,我遇到了一个问题。它们以极其缓慢的速度加载。

我的用例是将图像加载到集合视图内的每个单元格中。这些图像只有 200kb 大小,但下载过程平均需要 2.2 秒才能完成下载并将图像设置在一个单元格中。为了比较,我获取了类似大小的库存图像的 URL,并使用 NSURLSession 加载它们。每张图片加载只需要 0.18 - 0.25 秒。

我尝试了多种从CK下载图像的不同方式:直接获取记录、查询和操作查询。他们都有相似的结果。在为单元设置图像之前,我还将调度回完成块内的主队列。

我的数据库设置为具有包含多个数据字段的主要对象。然后,我为照片设置了一个向后参考样式系统,其中每张照片都只有一个对主要对象的引用。这样我就可以按需加载照片,而不会阻塞主要数据。

看起来像这样:

主要对象: title: String, startDate: Date

照片对象: owner: String(reference to primary object), image: Asset

这是我尝试直接获取其中一张照片的示例请求:

let publicDb = CKContainer.defaultContainer().publicCloudDatabase
let configRecordId = CKRecordID(recordName: "e783f542-ec0f-46j4-9e99-b3e3ez505adf")

publicDb.fetchRecordWithID(configRecordId) { (record, error) -> Void in
    dispatch_async(dispatch_get_main_queue()) {
        guard let photoRecord = record else { return }
        guard let asset = photoRecord["image"] as? CKAsset else { return }

        guard let photo = NSData(contentsOfURL: asset.fileURL) else { return }

        let image = UIImage(data: photo)!

        cell.cardImageView.image = image
    }
}

我似乎无法弄清楚为什么这些图片下载要花这么长时间,但如果我不能让它们在合理的时间内加载,这真的是一件非常棒的事情。

更新:我用较小的图像(23kb)尝试了获取操作。获取速度更快,从 0.3 到 1.1 秒不等。这更好,但仍然没有达到我对 CloudKit 应该能够提供的期望。

【问题讨论】:

  • 您正在获取整条记录。该记录中是否有更多资产?您可以将其限制为仅图像字段。除此之外,您只需执行更新主队列中 UI 的最后一条语句。
  • @EdwinVermeer 带有资产的记录只有两个字段,一个是图像,另一个是引用其所有者的字符串值。我知道我可以将那些其他行移出调度到主队列,但这没有区别。 fetch 的完成块是永远被调用的东西,而不是该块内的性能。
  • 您在哪种网络上进行测试?您可能想检查操作的优先级,也可以尝试手动获取记录,而不是使用便捷方法。
  • @mattsven 我已经在几种不同的网络条件下尝试过这个,所有这些都表现得非常好。我也尝试过 CK 提供的各种形式的记录请求。这包括通过 id 直接获取记录以及尝试使用高优先级操作进行获取。
  • @Jonathan 但问题仍然存在吗?这一切都在同一台设备上吗?您是否尝试过从 CloudKit 仪表板中删除所有数据并再次执行此操作?

标签: ios swift cloudkit


【解决方案1】:

我正在使用 CKQueryOperation。我发现,在我的代码中添加以下行后,下载 CKAssets 的速度提高了大约 5-10 倍。

    queryOperation.qualityOfService = .UserInteractive

这是我的完整代码:

func getReportPhotos(report:Report, completionHandler: (report:Report?, error:NSError?) -> ()) {
    let photo : Photo = report.photos![0] as! Photo
    let predicate : NSPredicate = NSPredicate(format: "recordID = %@", CKRecordID(recordName: photo.identifier!))
    let query : CKQuery = CKQuery(recordType: "Photo", predicate: predicate)
    let queryOperation : CKQueryOperation = CKQueryOperation()
    queryOperation.query = query
    queryOperation.resultsLimit = numberOfReportsPerQuery        
    queryOperation.qualityOfService = .UserInteractive
    queryOperation.recordFetchedBlock = { record in
        photo.date = record.objectForKey("date") as? NSDate
        photo.fileType = record.objectForKey("fileType") as? String
        let asset : CKAsset? = record.objectForKey("image") as? CKAsset
        if asset != nil {
            let photoData : NSData? = NSData(contentsOfURL:asset!.fileURL)
            let photo : Photo = report.photos![0] as! Photo
            photo.image = UIImage(data:photoData!)
        }

    }
    queryOperation.queryCompletionBlock = { queryCursor, error in
        dispatch_async(dispatch_get_main_queue(), {
            completionHandler(report: report, error: error)
        })
    }
    publicDatabase?.addOperation(queryOperation)
}

【讨论】:

  • 进行更改后,您现在可以在大约 0.1-0.5 秒内下载 80k 图像吗?您是如何在 CK 仪表板中构建数据的?它使我的速度提高了大约一倍。从大约 2.X 秒下降到 1.0-1.7 秒。
  • 我在不到 2 秒的时间内下载了 25 条记录,每条平均图像资源为 90K,有时不到 1 秒,因此每条记录不到 0.08 秒。我的 WiFi 连接良好,但我今天外出时使用 LTE 进行了测试,结果相似。每条记录有 14 个字符串字段、一个位置字段和日期字段,以及一个保存图像数据的资产字段。我仍处于开发模式,所有搜索、查询和排序都已检查。
  • 这些是我正在寻找的速度类型。所以你的数据设置只有一条记录,上面有所有不同的字段?我的设置有一个包含所有字符串数据的记录以及对“照片”记录的引用。然后,当我想获取图像时,我只能直接获取它。
  • 我先把照片资产放在同一个记录里。它太慢了,所以我把它移到一个单独的记录中,然后懒洋洋地加载它。那还是很慢。当我更改服务质量时,性能已经足够好,因此我将照片资产放回了同一记录中。我总是可以使用 desiredKeys 来获取除图像资产之外的所有内容,或者如果我愿意,也可以只获取图像资产。
【解决方案2】:

似乎有一些东西减慢了你的主线程,这会在执行你的 dispatch_async 调用的捕获块时引入​​延迟。您的代码是否有可能并行多次调用此记录获取函数?这将导致 NSData(contentsOfURL:asset.fileURL) 处理占用主线程并引入累积延迟。

在任何情况下,如果只是作为一种好的做法,使用 NSData 加载图像应该在后台而不是在主线程中执行。

【讨论】:

  • 我目前正在cellForItemAtIndexPath: 中运行该代码,这意味着它正在运行多次。我无法想象用例会减慢速度,特别是因为我可以用 NSURLSession 做同样的事情并获得出色的结果。这不是应该如何使用 CloudKit 吗?我从我的主要数据中断开了图像,以便我可以按需加载图像,而不是全部加载。
  • cellForItemAtIndexPath 可以调用相当不可预测的时间,并且总是在主线程上执行。执行异步数据加载操作不是很安全。我什至感到惊讶的是,您的单元格中没有混淆图像,因为在您的 dispatch_async 调用中捕获的单元格对象存在在获取和显示之间的延迟期间被重用于另一个项目的风险。无论如何,你似乎找到了一种让它工作的方法,所以我想你不需要关于这个问题的更多信息。
猜你喜欢
  • 2016-03-05
  • 2021-10-14
  • 1970-01-01
  • 1970-01-01
  • 2021-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-14
相关资源
最近更新 更多