【问题标题】:Obtaining CKAsset URL without downloading data不下载数据获取CKAsset URL
【发布时间】:2016-05-08 08:27:48
【问题描述】:

我在我的应用程序中使用 CloudKit 作为远程保存数据的一种方式。我的一个记录有一个包含图像的 CKAsset 属性。当我获取记录时,我意识到完成查询需要很长时间。经过多次测试,我得出结论,当您查询记录时,CloutKit 会下载整个 Asset 文件和记录对象。因此,当您从记录对象获取资产并请求它的 fileURL 时,它会为您提供本地文件路径 URL,而不是 HTTP 类型的 URL。这对我来说是个问题,因为您必须让用户等待很长时间才能在查询结束之前下载整个记录(及其相关图像)。来自 Parse 背景,Parse 曾经为您提供 HTTP URL,并且查询速度足够快,可以在异步加载图像的同时加载带有对象的 UI。我的问题是,我怎样才能实现 Parse 的功能?我需要限制查询下载资产数据并获取异步加载图像的链接。

【问题讨论】:

  • 没有http url。您只能获取本地网址。像下面的答案一样,您可以在没有资产的情况下查询数据。

标签: ios cloudkit


【解决方案1】:

使用@anilgokt​​as 的 Post 方法,我们也可以不使用 Alamofire 和 SwiftyJSON 来获取下载 URL,尽管它可能会稍微复杂一些,而且不那么整洁。

func Request() {
    let container = "INSERT CONTAINER NAME"
    let cloudKitAPIToken = "INSERT API TOKEN"
    let urlString = "https://api.apple-cloudkit.com/database/1/" + container + "/development/public/records/query?ckAPIToken=" + cloudKitAPIToken
    
    let query = ["recordType": "testRecord"]
    
    //create the url with URL
    let url = URL(string: urlString)! //change the url
    
    //create the session object
    let session = URLSession.shared
    
    //now create the URLRequest object using the url object
    var request = URLRequest(url: url)
    request.httpMethod = "POST" //set http method as POST
    
    do {
        request.httpBody = try JSONSerialization.data(withJSONObject: ["query": query], options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
    } catch let error {
        print(error.localizedDescription)
    }
    
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    
    //create dataTask using the session object to send data to the server
    let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
        
        guard error == nil else {
            return
        }
        
        guard let data = data else {
            return
        }
        
        do {
            //create json object from data
            if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
                DispatchQueue.main.async {
                    for i in json.values {
                        if let sub = i as? NSArray {
                            for j in sub {
                                if let dict = j as? NSDictionary, let subDict = dict["fields"] as? NSDictionary, let titleDict = subDict["title"] as? [String:String], let title = titleDict["value"], let videoDict = subDict["video"] as? NSDictionary, let vidVal = videoDict["value"] as? NSDictionary, let url = vidVal["downloadURL"] as? String  {
                                    let downloadURL = url.replacingOccurrences(of: "${f}", with: "\(title).jpg")
                                    print(title)
                                    print(downloadURL)
                                }
                            }
                        }
                    }
                    // handle json...
                }
            }
        } catch let error {
            print(error.localizedDescription)
        }
    })
    task.resume()
}

【讨论】:

    【解决方案2】:

    这是我的方法。

    假设我有记录类型:书籍

    BookID(自动 ID 或您的唯一 ID)
    书名字符串
    BookImage CKAsset
    BookURL 字符串

    如果我使用 CKAssest 我存储在 BookURL : “资产:\BookID.png” 如果我使用外部服务器来存储图像,我在 BookURL 中使用普通 URL "http://myexternalServer\images\BookID.png"

    查询:

    1. 使用 desiredKeys 我查询所有没有 BookImage(CKAsset) 字段的字段。

    2. 如果 BookURL 为空,则该书没有图片。
      如果 BookURL 以“Asset:\”开头,我从 cloudkit
      查询 BookImage 如果 BookURL 是普通 URL,我会从外部服务器 (NSUrlSession) 下载图像

    这样,我可以随时更改并决定如何在 cloudkit(CKAssets) 或外部服务器(http 下载)上存储图像

    【讨论】:

    • 这是个好方法吗?
    • @YaoFan 这对我有用。灵活。
    【解决方案3】:

    您可以使用 CloudKit JavaScript 访问资产的 url。这是一个例子;

    (使用AlamofireSwiftyJSON

    func testRecordRequest() -> Request {
        let urlString = "https://api.apple-cloudkit.com/database/1/" + Constants.container + "/development/public/records/query?ckAPIToken=" + Constants.cloudKitAPIToken
        let query = ["recordType": "TestRecord"]
        return Alamofire.request(.POST, urlString, parameters: ["query": query], encoding: .JSON, headers: nil)
    }
    

    JSON 响应包含资产的“downloadURL”。

    "downloadURL": "https://cvws.icloud-content.com/B/.../${f}?o=AmVtU..."
    

    "${f}" 看起来像一个变量,所以把它改成你喜欢的任何东西。

    let downloadURLString = json["fields"][FieldNames.image]["value"]["downloadURL"].stringValue
    let recordName = json["recordName"].stringValue
    let fileName = recordName + ".jpg"
    let imageURLString = downloadURLString.stringByReplacingOccurrencesOfString("${f}", withString: fileName)
    

    现在我们可以使用这个 urlString 来创建一个 NSURL 并将其与任何图像和缓存解决方案一起使用,例如 Kingfisher、HanekeSwift 等。 (您可能还想保存图像类型 png/jpg)

    【讨论】:

    • 真棒技巧@anilgokt​​as,我有一个疑问,[Keys.image] 是什么?
    • @drdrdrdr [Keys.image] -> 记录类型上图像的字段名称。您可以在 CloudKit 仪表板上查看它。很混乱,刚改了。谢谢
    • @anilgokt​​as 此请求返回"serverErrorCode = "AUTHENTICATION_REQUIRED";" 以及登录网页的redirectURL。你是如何克服这个问题的?
    • 我正在尝试@anilgokt​​as 方法,因为我需要在 iCloud 中分配的视频的 URL。但是,发布请求返回:"BAD_REQUEST","reason":"BadRequestException: missing required query field"。我正在使用recordType 进行查询。有什么想法吗?
    • @drdrdrdr 您可以使用您发送的answer 中的let json = JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]
    【解决方案4】:

    正如其他人所说,您无法获取 CKAsset 的 url(web)。所以你最好的选择是 1. 对每个 UIImageView 使用带有进度的 fetch 操作。我已经建立了一个自定义的,向用户显示进度。不包括缓存,但您可以创建一个类并采用 NSCoding 并将整个记录保存到缓存目录。在这里,您可以看到我已完成的提取,以将资产发送回我调用它的位置,以将其与我的其他数据结合起来。

           // only get the asset in this fetch.  we have everything else
    

    让操作 = CKFetchRecordsOperation(recordIDs: [myRecordID])

        operation.desiredKeys = ["GameTurnImage"]
            operation.perRecordProgressBlock = {
                record,progress in
    
                dispatch_async(dispatch_get_main_queue(), {
                    self.progressIndicatorView.progress = CGFloat(progress)
                })
    
            }
    
        operation.perRecordCompletionBlock = {
            record,recordID,error in
            if let _ = record{
                let asset = record!.valueForKey("GameTurnImage") as? CKAsset
    
                if let _ = asset{
    
                        let url = asset!.fileURL
                        let imageData = NSData(contentsOfFile: url.path!)!
                    dispatch_async(dispatch_get_main_queue(), {
    
                        self.image = UIImage(data: imageData)
                        self.progressIndicatorView.reveal()
    
                    })
                    completion(asset!)
                }
            }
    
        }
        CKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)
    

    另一种选择是将图像存储在 AWS 服务器或类似的东西上,然后您可以使用 SDWebImage 之类的东西来完成所有缓存或繁重的工作,并将 CKRecord 中的字符串保存到图像中。

    我已经询问了几个人关于 CKAsset 功能以公开 url。我不知道 CloudKit 的 JS Api,但可能有办法做到这一点,但我会让其他人对此发表评论。

    【讨论】:

      【解决方案5】:

      对同一条记录分别调用两次。第一次调用应该获取您想要的所有非资产字段,然后第二个请求应该获取所需的资产。

      类似:

      let dataQueryOperation = CKQueryOperation(query: CKQuery(predicate: myPredicate)
      dataQueryOperation.desiredKeys = ["name", "age"] // etc
      database.addOperation(dataQueryOperation)
      
      let imageQueryOperation = CKQueryOperation(query: CKQuery(predicate: myPredicate)
      imageQueryOperation.desiredKeys = ["images"]
      database.addOperation(imageQueryOperation)
      

      如果需要,将其重构为一个方法,以便您可以轻松地为每个包含资产的字段创建一个新的 CKQueryOperation。

      狩猎愉快。

      【讨论】:

        猜你喜欢
        • 2016-03-05
        • 2016-11-23
        • 2017-11-16
        • 2021-11-09
        • 1970-01-01
        • 1970-01-01
        • 2020-07-27
        • 1970-01-01
        • 2016-05-28
        相关资源
        最近更新 更多