Search code examples
arraysswiftnspredicateckqueryckqueryoperation

Swift predicate only matches first value in array of values


I have a class Download that serves as a wrapper for CKQueryOperation. One of the inits allows me to build my predicate with an array of values:

init(type: String, queryField: String, queryValues: [CKRecordValue], to rec: RecievesRecordable, from database: CKDatabase? = nil) {
    let predicate = NSPredicate(format: "\(queryField) = %@", argumentArray: queryValues)
    query = CKQuery(recordType: type, predicate: predicate)

    reciever = rec
    db = database

    super.init()
}

When I test it, query only matches the first value in the array. So if queryValues = [testValue0, testValue1] and I have one record whose field matches testValue0 and I have a second record that matches testValue1, only the first record will be discovered. If I switch the order, than the other record gets recognized.

It seems weird that I can create a predicate with an array but only the first value gets matched. The documentation says that values are substituted in the order they appear, but shouldn't it still be moving on to the second value?

For more context, each record is stored in a separate database (private vs public) and my Download class launches two separate CKQueryOperations that both rely on query, if database param is left nil. Whichever op fails ends up finding no results that match the first value and then giving up before checking the second value.

I can include the full code for 'Download' and my failing unit test if needed.


Solution

  • You are using the wrong predicate. You want to use the IN operation. And instead of using string substitution for the field name, use the %K format specifier:

    let predicate = NSPredicate(format: "%K IN %@", arguments: queryField, queryValues)
    

    Note that the NSPredicate(format:argumentArray:) expects there to be one format specifier in the format string for each value in the argument array. Since your format only had one format specifier, only the first value was taken from the array. And since you used =, the field was simply compared against that one value.