[英]Can't fetch data from iCloud using CloudKit's CKQueryOperation
[英]CloudKit - CKQueryOperation with dependency
我剛剛開始使用CloudKit,所以請耐心等待。
背景信息
在WWDC 2015上,蘋果發表了關於CloudKit的演講https://developer.apple.com/videos/wwdc/2015/?id=715
在這次演講中,他們警告不要創建鏈接查詢,而是推薦這種策略:
let firstFetch = CKFetchRecordsOperation(...)
let secondFetch = CKFetchRecordsOperation(...)
...
secondFetch.addDependency(firstFetch)
letQueue = NSOperationQueue()
queue.addOperations([firstFetch, secondFetch], waitUntilFinished: false)
示例結構
測試項目數據庫包含寵物及其所有者,它看起來像這樣:
|Pets | |Owners |
|-name | |-firstName |
|-birthdate | |-lastName |
|-owner (Reference) | | |
我的問題
我試圖找到屬於所有者的所有寵物,我擔心我正在創建鏈蘋果警告。 請參閱下面的兩種方法,它們可以做同樣的事情,但有兩種方法。 哪個更正確或者都錯了? 我覺得我在做同樣的事情,但只是使用完成塊。
我很困惑如何更改otherSearchBtnClick:使用依賴項。 我需要在哪里添加
ownerQueryOp.addDependency(queryOp)
在otherSearchBtnClick:?
@IBAction func searchBtnClick(sender: AnyObject) {
var petString = ""
let container = CKContainer.defaultContainer()
let publicDatabase = container.publicCloudDatabase
let privateDatabase = container.privateCloudDatabase
let predicate = NSPredicate(format: "lastName == '\(ownerLastNameTxt.text)'")
let ckQuery = CKQuery(recordType: "Owner", predicate: predicate)
publicDatabase.performQuery(ckQuery, inZoneWithID: nil) {
record, error in
if error != nil {
println(error.localizedDescription)
} else {
if record != nil {
for owner in record {
let myRecord = owner as! CKRecord
let myReference = CKReference(record: myRecord, action: CKReferenceAction.None)
let myPredicate = NSPredicate(format: "owner == %@", myReference)
let petQuery = CKQuery(recordType: "Pet", predicate: myPredicate)
publicDatabase.performQuery(petQuery, inZoneWithID: nil) {
record, error in
if error != nil {
println(error.localizedDescription)
} else {
if record != nil {
for pet in record {
println(pet.objectForKey("name") as! String)
}
}
}
}
}
}
}
}
}
@IBAction func otherSearchBtnClick (sender: AnyObject) {
let container = CKContainer.defaultContainer()
let publicDatabase = container.publicCloudDatabase
let privateDatabase = container.privateCloudDatabase
let queue = NSOperationQueue()
let petPredicate = NSPredicate(format: "lastName == '\(ownerLastNameTxt.text)'")
let petQuery = CKQuery(recordType: "Owner", predicate: petPredicate)
let queryOp = CKQueryOperation(query: petQuery)
queryOp.recordFetchedBlock = { (record: CKRecord!) in
println("recordFetchedBlock: \(record)")
self.matchingOwners.append(record)
}
queryOp.queryCompletionBlock = { (cursor: CKQueryCursor!, error: NSError!) in
if error != nil {
println(error.localizedDescription)
} else {
println("queryCompletionBlock: \(cursor)")
println("ALL RECORDS ARE: \(self.matchingOwners)")
for owner in self.matchingOwners {
let ownerReference = CKReference(record: owner, action: CKReferenceAction.None)
let ownerPredicate = NSPredicate(format: "owner == %@", ownerReference)
let ownerQuery = CKQuery(recordType: "Pet", predicate: ownerPredicate)
let ownerQueryOp = CKQueryOperation(query: ownerQuery)
ownerQueryOp.recordFetchedBlock = { (record: CKRecord!) in
println("recordFetchedBlock (pet values): \(record)")
self.matchingPets.append(record)
}
ownerQueryOp.queryCompletionBlock = { (cursor: CKQueryCursor!, error: NSError!) in
if error != nil {
println(error.localizedDescription)
} else {
println("queryCompletionBlock (pet values)")
for pet in self.matchingPets {
println(pet.objectForKey("name") as! String)
}
}
}
publicDatabase.addOperation(ownerQueryOp)
}
}
}
publicDatabase.addOperation(queryOp)
}
如果您不需要取消並且不想重試網絡錯誤,那么我認為您可以很好地鏈接查詢。
我知道,我知道,在WWDC 2015中,Nihar Sharma推薦了添加依賴性方法,但看起來他最終還是沒有多想。 您看到無法重試NSOperation,因為它們無論如何都是一次性的,並且他沒有提供取消隊列中已有的操作或如何從下一個操作傳遞數據的示例。 鑒於這三個復雜問題可能需要數周時間才能解決,只需堅持使用您的工作並等待下一個WWDC的解決方案。 另外,塊的全部內容是讓您調用內聯方法並能夠訪問上述方法中的參數,因此如果您轉向操作,您可能無法充分利用該優勢。
他不使用鏈接的主要原因是荒謬的一個,他無法分辨哪個錯誤是針對哪個請求,他的名字是他的錯誤someError然后是其他錯誤等等。他們正確的頭腦中沒有人名字錯誤參數不同內部塊所以只需使用所有這些名稱相同,然后你知道在一個區塊內你總是使用正確的錯誤。 因此,他是創建他的混亂場景並為其提供解決方案的人,但是最好的解決方案就是不要首先創建多個錯誤參數名稱的混亂場景!
盡管如此,如果您仍想嘗試使用操作依賴性,這里是一個如何完成它的示例:
__block CKRecord* venueRecord;
CKRecordID* venueRecordID = [[CKRecordID alloc] initWithRecordName:@"4c31ee5416adc9282343c19c"];
CKFetchRecordsOperation* fetchVenue = [[CKFetchRecordsOperation alloc] initWithRecordIDs:@[venueRecordID]];
fetchVenue.database = [CKContainer defaultContainer].publicCloudDatabase;
// init a fetch for the category, it's just a placeholder just now to go in the operation queue and will be configured once we have the venue.
CKFetchRecordsOperation* fetchCategory = [[CKFetchRecordsOperation alloc] init];
[fetchVenue setFetchRecordsCompletionBlock:^(NSDictionary<CKRecordID *,CKRecord *> * _Nullable recordsByRecordID, NSError * _Nullable error) {
venueRecord = recordsByRecordID.allValues.firstObject;
CKReference* ref = [venueRecord valueForKey:@"category"];
// configure the category fetch
fetchCategory.recordIDs = @[ref.recordID];
fetchCategory.database = [CKContainer defaultContainer].publicCloudDatabase;
}];
[fetchCategory setFetchRecordsCompletionBlock:^(NSDictionary<CKRecordID *,CKRecord *> * _Nullable recordsByRecordID, NSError * _Nullable error) {
CKRecord* categoryRecord = recordsByRecordID.allValues.firstObject;
// here we have a venue and a category so we could call a completion handler with both.
}];
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[fetchCategory addDependency:fetchVenue];
[queue addOperations:@[fetchVenue, fetchCategory] waitUntilFinished:NO];
它是如何工作的首先是它審查Venue記錄,然后它獲取其類別。
對不起,沒有錯誤處理但是你可以看到它已經有很多代碼可以通過鏈接在幾行中完成。 而且我個人認為這個結果比簡單地將便利方法鏈接在一起更令人費解和困惑。
從理論上講,您可能擁有多個所有者,因此可能有多個依賴 此外,在已經執行外部查詢之后將創建內部查詢。 你將來不及創建一個依賴。 在您的情況下,可能更容易強制執行內部查詢到這樣的單獨隊列:
if record != nil {
for owner in record {
NSOperationQueue.mainQueue().addOperationWithBlock {
這樣,您將確保每個內部查詢將在新隊列上執行,並且同時父查詢可以完成。
其他的:為了使你的代碼更干凈,如果for循環中的所有代碼都在一個單獨的函數中,CKReference作為參數,那就更好了。
我最近遇到了同樣的問題,並最終使用NSBlockOperation來准備第二個查詢並添加了一個依賴項以使其全部工作:
let container = CKContainer.defaultContainer()
let publicDB = container.publicCloudDatabase
let operationqueue = NSOperationQueue.mainQueue()
let familyPredicate = NSPredicate(format: "name == %@", argumentArray: [familyName])
let familyQuery = CKQuery(recordType: "Familias", predicate: familyPredicate)
let fetchFamilyRecordOp = CKQueryOperation(query: familyQuery)
fetchFamilyRecordOp.recordFetchedBlock = { record in
familyRecord = record
}
let fetchMembersOP = CKQueryOperation()
// Once we have the familyRecord, we prepare the PersonsFetch
let prepareFamilyRef = NSBlockOperation() {
let familyRef = CKReference(record: familyRecord!, action: CKReferenceAction.None)
let familyRecordID = familyRef?.recordID
let membersPredicate = NSPredicate(format: "familia == %@", argumentArray: [familyRecordID!])
let membersQuery = CKQuery(recordType: "Personas", predicate: membersPredicate)
fetchMembersOP.query = membersQuery
}
prepareFamilyRef.addDependency(fetchFamilyRecordOp)
fetchMembersOP.recordFetchedBlock = { record in
members.append(record)
}
fetchMembersOP.addDependency(prepareFamilyRef)
fetchMembersOP.database = publicDB
fetchFamilyRecordOp.database = publicDB
operationqueue.addOperations([fetchFamilyRecordOp, fetchMembersOP, prepareFamilyRef], waitUntilFinished: false)
現在它按照我的預期工作,因為您可以以非常精細的方式設置操作,並按正確的順序執行^。^
在你的情況下,我會像這樣構造它:
let predicate = NSPredicate(format: "lastName == '\(ownerLastNameTxt.text)'")
let ckQuery = CKQuery(recordType: "Owner", predicate: predicate)
let getOwnerOperation = CKQueryOperation(query: ckQuery)
getOwnerOperation.recordFetchedBlock = { record in
let name = record.valueForKey("name") as! String
if name == myOwnerName {
ownerRecord = record
}
}
//now we have and operation that will save in our var OwnerRecord the record that is exactly our owner
//now we create another that will fetch our pets
let queryPetsForOurOwner = CKQueryOperation()
queryPetsForOurOwner.recordFetchedBlock = { record in
results.append(record)
}
//That's all this op has to do, BUT it needs the owner operation to be completed first, but not inmediately, we need to prepare it's query first so:
var fetchPetsQuery : CKQuery?
let preparePetsForOwnerQuery = NSBlockOperation() {
let myOwnerRecord = ownerRecord!
let ownerRef = CKReference(record: myOwnerRecord, action: CKReferenceAction.None)
let myPredicate = NSPredicate(format: "owner == %@", myReference)
fetchPetsQuery = CKQuery(recordType: "Pet", predicate: myPredicate)
}
queryPetsForOurOwner.query = fetchPetsQuery
preparePetsForOwnerQuery.addDependency(getOwnerOperation)
queryPetsForOurOwner.addDependency(preparePetsForOwnerQuery)
現在我們需要做的就是在將它們引導到我們的數據庫之后將它們添加到新創建的操作隊列中
getOwnerOperation.database = publicDB
queryPetsForOurOwner.database = publicDB
let operationqueue = NSOperationQueue.mainQueue()
operationqueue.addOperations([getOwnerOperation, queryPetsForOurOwner, preparePetsForOwnerQuery], waitUntilFinished: false)
PS:我知道我說家庭和人,名字不是那樣,但我是西班牙語並測試一些cloudkit操作,所以我還沒有標准化為英文錄音類型名稱;)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.