Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 830
Wienke Avatar asked Mar 18 '11 19:03

Wienke


2 Answers

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.

like image 57
Rob Napier Avatar answered Nov 05 '22 19:11

Rob Napier


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.

like image 43
James Avatar answered Nov 05 '22 19:11

James