CloudKit - CKQueryOperation,具有依赖关系

时间:2015-09-02 14:00:44

标签: ios swift cloudkit

我刚刚开始使用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)
}

3 个答案:

答案 0 :(得分:2)

如果您不需要取消并且不会因重试网络错误而烦恼,那么我认为您可以很好地链接查询。

我知道我知道,在WWDC 2015中,Nihar Sharma推荐了添加依赖性方法,但看起来他最终还是没有多想。您看到无法重试NSOperation,因为它们无论如何都是一次性的,并且他没有提供取消队列中已有的操作或如何从下一个操作传递数据的示例。鉴于这三个复杂问题可能需要数周时间才能解决,只需坚持使用您的工作并等待下一个WWDC的解决方案。另外,块的全部内容是让您调用内联方法并能够访问上述方法中的参数,因此如果您转向操作,您将无法充分利用该优势。

他不使用链接的主要原因是荒谬的一个,他无法分辨哪个错误是针对哪个请求,他的名字是他的错误someError然后是其他错误等等。他们正确的头脑中没有人名字错误params不同内部块所以只需对所有这些名称使用相同的名称,然后你知道在一个块内你总是使用正确的错误。因此,他是创建他的混乱场景并为其提供解决方案的人,但是最好的解决方案就是不要首先创建多个错误参数名称的混乱场景!

尽管如此,如果你仍然想尝试使用操作依赖,这里是一个如何完成它的例子:

__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记录,然后它获取其类别。

很抱歉没有错误处理但是你可以看到已经有很多代码可以通过链接在几行中完成。而且我个人认为这个结果比简单地将方便方法链接在一起更加复杂和混乱。

答案 1 :(得分:1)

理论上,你可以拥有多个所有者,因此可以拥有多个依赖关系。此外,在已经执行外部查询之后将创建内部查询。你将来不及创建一个依赖。在您的情况下,可能更容易强制执行内部查询到这样的单独队列:

if record != nil {
    for owner in record {
        NSOperationQueue.mainQueue().addOperationWithBlock {

通过这种方式,您将确保每个内部查询将在新队列上执行,并且同时父查询可以完成。

其他:为了使你的代码更清晰,如果for循环中的所有代码都在一个单独的函数中,并且CKReference作为参数,那就更好了。

答案 2 :(得分:0)

我最近遇到了同样的问题,并最终使用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操作,所以我还没有标准化为英文录音类型名称;)< / p>