Which is better, NSSet’s containsObject or fast enum?
Asked Answered
F

2

5

I need to determine whether an object is included in a Core Data to-many relationship (which is an NSSet), and I’m trying to decide which of two solutions is better:

Solution 1)

if ([managedObject.items containsObject:itemOfInterest])
    return …

Solution 2)

for (NSManagedObject *item in managedObject.items)
    if ([item == itemOfInterest])
        return …

Solution 1 is more concise, but the NSSet Class Ref says fast enumeration performs better than NSSet’s objectEnumerator. Does it also perform better than containsObject?

Fogy answered 18/3, 2011 at 19:10 Comment(2)
I may be missing something here but why do you need to search at all? If you already have both objects on each side of a relationship you already have the info you need. Is this a many-to-many relationship or a one-to-many without a reciprocal?Ameliaamelie
@TechZen: A fetch is made per user’s search criteria. The results are displayed in a table. The user makes a subselection of these results via the table. There is another table with members of the to-many relationship, presented with checkboxes. The checkbox states must be set according to whether all, none, or some of the selected objects contain their item. There can be a lot of objects involved, so it is important to use the most efficient method. Thanks to the advice of Messrs Raybould and Napier, I feel confident that containsObject is the way to go.Fogy
F
5

I'd always go for option 1.

Its more concise, I can tell exactly what your trying to do with the code and chances are that the containsObject contains some pretty nifty optimisations.

Felonious answered 18/3, 2011 at 19:15 Comment(0)
M
20

Neither. You should use an NSFetchRequest with a predicate. Your patterns can accidentally fault the entire relationship, which is very expensive and not needed just to check for whether it contains one object. There are ways to be careful and not fault the entire relationship, but it's fragile (small changes to your search lead to huge changes in performance) and so it's better to be in the habit of using NSFetchRequest rather than the collection for searching. I like to set my fetchLimit to 1 in these cases so once it finds it, it stops looking.

For convenience, you may want to create a -containsFoo: method on your managed object so you don't have to write the fetch logic all over the place.

Your two solutions above are subtly different. The first one tests whether there is an object in the collection that isEqual: to itemOfInterest. Your second solution tests whether there is an object in the collection at the same memory location as itemOfInterest. For objects with custom isEqual: logic, these can return different results. This means that solution 2 might be slightly faster for non-core data collections, but it's because you're actually testing a different thing, not because of object enumeration. (In reality, this is only true for small collections; see below.)

Why do you believe that solution 1 uses -objectEnumerator?

As @James Raybould points out, you generally should not try to rewrite the built-in methods for performance reasons. If an isEqual: version of Solution 2 were faster than Solution 1, wouldn't you think Apple would have implemented -containsObject: using the code in solution 2?

In reality, the underlying CFSet is implemented as a hash, so checking for containment is logarithmic rather than linear. Generally speaking, for large sets with reasonable hash functions, solution 1 will be faster. See the code for it in CFSet.c. Look for CFSetContainsValue(). CFSet's implementation isn't guaranteed to stay the same, of course, but it's useful for understanding how performance concerns are generally addressed within Cocoa.

Millard answered 18/3, 2011 at 19:41 Comment(3)
Ignore my comment - this is easily one of the best answers I've ever read!Felonious
Thanks for your in-depth response. I learned a lot from it. The search in question is performed on a selection of managed objects in a table, the contents of which are themselves the result of a particular fetch request. So I really do need to work with cached objects, rather than running another fetch. (I do use a fetch with a “predicateWithFormat:@“ANY items == %@", itemSelected” when pulling objects out from the store based on this to-many relationship.)Fogy
Your situation sounds reasonable, and containsObject: is your best bet here as James notes. You're correct that you shouldn't fetch again. And containsObject: uses hash and isEqual: which are documented to not fault (you should never override these methods for an NSManagedObject).Millard
F
5

I'd always go for option 1.

Its more concise, I can tell exactly what your trying to do with the code and chances are that the containsObject contains some pretty nifty optimisations.

Felonious answered 18/3, 2011 at 19:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.