简体   繁体   English

CloudKit - 具有依赖性的CKQueryOperation

[英]CloudKit - CKQueryOperation with dependency

I'm just beginning working with CloudKit, so bear with me. 我刚刚开始使用CloudKit,所以请耐心等待。

Background info 背景信息

At WWDC 2015, apple gave a talk about CloudKit https://developer.apple.com/videos/wwdc/2015/?id=715 在WWDC 2015上,苹果发表了关于CloudKit的演讲https://developer.apple.com/videos/wwdc/2015/?id=715

In this talk, they warn against creating chaining queries and instead recommend this tactic: 在这次演讲中,他们警告不要创建链接查询,而是推荐这种策略:

let firstFetch = CKFetchRecordsOperation(...)
let secondFetch = CKFetchRecordsOperation(...)
...
secondFetch.addDependency(firstFetch)

letQueue = NSOperationQueue()
queue.addOperations([firstFetch, secondFetch], waitUntilFinished: false)

Example structure 示例结构

The test project database contains pets and their owners, it looks like this: 测试项目数据库包含宠物及其所有者,它看起来像这样:

|Pets               |   |Owners     |
|-name              |   |-firstName |
|-birthdate         |   |-lastName  |
|-owner (Reference) |   |           |

My Question 我的问题

I am trying to find all pets that belong to an owner, and I'm worried I'm creating the chain apple warns against. 我试图找到属于所有者的所有宠物,我担心我正在创建链苹果警告。 See below for two methods that do the same thing, but two ways. 请参阅下面的两种方法,它们可以做同样的事情,但有两种方法。 Which is more correct or are both wrong? 哪个更正确或者都错了? I feel like I'm doing the same thing but just using completion blocks instead. 我觉得我在做同样的事情,但只是使用完成块。

I'm confused about how to change otherSearchBtnClick: to use dependency. 我很困惑如何更改otherSearchBtnClick:使用依赖项。 Where would I need to add 我需要在哪里添加

ownerQueryOp.addDependency(queryOp)

in otherSearchBtnClick:? 在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)
}

If you don't need cancellation and aren't bothered about retrying on a network error then I think you are fine chaining the queries. 如果您不需要取消并且不想重试网络错误,那么我认为您可以很好地链接查询。

I know I know, in WWDC 2015 Nihar Sharma recommended the add dependency approach but it would appear he just threw that in at the end without much thought. 我知道,我知道,在WWDC 2015中,Nihar Sharma推荐了添加依赖性方法,但看起来他最终还是没有多想。 You see it isn't possible to retry a NSOperation because they are one-shot anyway, and he offered no example for cancelling operations already in the queue, or how to pass data from one operation from the next. 您看到无法重试NSOperation,因为它们无论如何都是一次性的,并且他没有提供取消队列中已有的操作或如何从下一个操作传递数据的示例。 Given these 3 complications that could take you weeks to solve, just stick with what you have working and wait for the next WWDC for their solution. 鉴于这三个复杂问题可能需要数周时间才能解决,只需坚持使用您的工作并等待下一个WWDC的解决方案。 Plus the whole point of blocks is to let you call inline methods and be able to access the params in the method above, so if you move to operations you kind of don't get full advantage of that benefit. 另外,块的全部内容是让您调用内联方法并能够访问上述方法中的参数,因此如果您转向操作,您可能无法充分利用该优势。

His main reason for not using chaining is the ridiculous one that he couldn't tell which error is for which request, he had names his errors someError then otherError etc. No one in their right mind names error params different inside blocks so just use the same name for all of them and then you know inside a block you are always using the right error. 他不使用链接的主要原因是荒谬的一个,他无法分辨哪个错误是针对哪个请求,他的名字是他的错误someError然后是其他错误等等。他们正确的头脑中没有人名字错误参数不同内部块所以只需使用所有这些名称相同,然后你知道在一个区块内你总是使用正确的错误。 Thus he was the one that created his messy scenario and offered a solution for it, however the best solution is just don't create the messy scenario of multiple error param names in the first place! 因此,他是创建他的混乱场景并为其提供解决方案的人,但是最好的解决方案就是不要首先创建多个错误参数名称的混乱场景!

With all that being said, in case you still want to try to use operation dependencies here is an example of how it could be done: 尽管如此,如果您仍想尝试使用操作依赖性,这里是一个如何完成它的示例:

__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];

How it works is first it vetches a Venue record, then it fetches its Category. 它是如何工作的首先是它审查Venue记录,然后它获取其类别。

Sorry there is no error handling but as you can see it was already a ton of code to do something can could be done in a couple of lines with chaining. 对不起,没有错误处理但是你可以看到它已经有很多代码可以通过链接在几行中完成。 And personally I find this result more convoluted and confusing than simply chaining together the convenience methods. 而且我个人认为这个结果比简单地将便利方法链接在一起更令人费解和困惑。

in theory you could have multiple owners and therefore multiple dependencies. 从理论上讲,您可能拥有多个所有者,因此可能有多个依赖 Also the inner queries will be created after the outer query is already executed. 此外,在已经执行外部查询之后将创建内部查询。 You will be too late to create a dependency. 你将来不及创建一个依赖。 In your case it's probably easier to force the execution of the inner queries to a separate queue like this: 在您的情况下,可能更容易强制执行内部查询到这样的单独队列:

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

This way you will make sure that every inner query will be executed on a new queue and in the mean time that parent query can finish. 这样,您将确保每个内部查询将在新队列上执行,并且同时父查询可以完成。

Something else: to make your code cleaner, it would be better if all the code inside the for loop was in a separate function with a CKReference as a parameter. 其他的:为了使你的代码更干净,如果for循环中的所有代码都在一个单独的函数中,CKReference作为参数,那就更好了。

I had the same problem recently and ended up using a NSBlockOperation to prepare the second query and added a dependency to make it all work: 我最近遇到了同样的问题,并最终使用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)

And now it's working as i expected, because you can set up your operations in a very granular way and they execute in the correct order ^.^ 现在它按照我的预期工作,因为您可以以非常精细的方式设置操作,并按正确的顺序执行^。^

in your case i would structure it like this: 在你的情况下,我会像这样构造它:

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)

and now all it needs to be done is to add them to the newly created operation queue after we direct them to our database 现在我们需要做的就是在将它们引导到我们的数据库之后将它们添加到新创建的操作队列中

getOwnerOperation.database = publicDB
queryPetsForOurOwner.database = publicDB
let operationqueue = NSOperationQueue.mainQueue()
operationqueue.addOperations([getOwnerOperation, queryPetsForOurOwner, preparePetsForOwnerQuery], waitUntilFinished: false)

PS: i know i said Family and Person and the names are not like that, but i'm spanish and testing some cloudkit operations, so i haven't standardized to english recor type names yet ;) PS:我知道我说家庭和人,名字不是那样,但我是西班牙语并测试一些cloudkit操作,所以我还没有标准化为英文录音类型名称;)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM