Search code examples
ioscore-datanspredicatensfetchrequestset-intersection

CoreData fetch predicate using a set intersection


My core data model has an entity with an attribute setOfStrings of type Transformable.
The corresponding class has a property @NSManaged var setOfStrings: Set<String>?.
I want to fetch objects using a predicate that filters out objects where their attribute setOfStrings has a set intersection with a given set, say givenSet.
I tried to use the following predicate:

NSPredicate(format: "ANY setOfStrings IN %@", givenSet)

but it always evaluates to false.
If I out comment this predicate, the unfiltered objects are correctly fetched.
I read many SO Q/A related to this subject, e.g. this one. However it does not work.
I know, if my attribute setOfStrings would be a relation, I could use a subquery to fetch the right objects, as shown here. But I don’t have a relation, just a set of strings.
I have also read this post that says

I assume that the set is defined as a transformable property, which means that it is stored as a binary archive in the SQLite file. Then the above queries will work at most for objects already loaded into the managed object context, but not against the store file.

This is obviously related to my problem. But the suggested solution to replace a simple set of strings by a to-many relation seems to me too complicated.
Which predicate should I use?


Solution

  • I'm afraid a predicate based on a transformable attribute just won't work as part of a fetch. You've either got to fetch everything, and then filter the results in memory, or use a different type of property: a to-many relationship would be best, but you might be able to concatenate your strings into a single string and store that. Then your predicate might work against that.

    But it depends on the details of the strings. For example, if they are single words, like colours: "red", "green", "blue", "cyan", etc, then you can concatenate them with spaces and store a single string:

    "red green blue cyan"
    

    (Of course, you then have to add code to separate them again wherever the individual colours are required).

    Suppose you then want to use a predicate to find those objects with either "green" or "blue". You would then construct a predicate along these lines:

    colourString CONTAINS "blue" OR colourString CONTAINS "green"
    

    If your strings or your search criteria are more complicated, then a different means of concatenation might be necessary. For example, if you have multi-word colours, like "pale green", "pale yellow", a search for "green" might match "dark green", "pale green" etc (which might be good or bad). If that's bad, you might need to use a different delimiter (eg. a comma) then your string might be:

    "red,blue,pale green,cyan"
    

    and your predicate to find the colour "green", but not "pale green" would be:

    colourString CONTAINS ",green,"
    

    though there are then the nasty edge cases where "green" is the first or last colour, so you need include a leading and trailing comma in your colourString:

    ",red,blue,pale green,cyan,"
    

    which is starting to get increasingly messy. And if your strings might contain commas already, a different delimiter and more complicated predicates will be required. Depending on your data, it may be that a to-many relationship is easier.