Search code examples
objective-ccollectionsnsarraynsarraycontroller

How to make collection operators respect the controller's sort descriptor?


Cocoa has this concept of collection operators that allow to return a set of objects based on a property. In my array controller I have a number of objects that are executed on a specific date and I want to process them in some way. So I retrieve a list of unique execution dates with this code:

NSArray *assignments = categoryAssignments.arrangedObjects;
NSArray* distinctDates = [assignments valueForKeyPath: @"@distinctUnionOfObjects.dayOfExecution"];

However, the order in that array does not follow the order in the arrangedObjects array (the dates are sorted descending, while the distinctDates array is sorted ascending).

Is there a way to make the collection operator respect the array controller's sort descriptor?


Solution

  • @distinctUnionOfObjects has to work with arbitrarily (unsorted) input data. The input data might even (as @Kevin correctly commented) contain objects that are not comparable at all. It could be that @distinctUnionOfObjects uses a NSMutableSet internally to find a list of unique objects (and the objects in a set do not have a defined order). But that is pure speculation and does not help.

    It is simply not documented that the output of @distinctUnionOfObjects preserves any order of the input data, and I do not know any parameter to enforce that.

    Therefore you have to sort the returned list according to your requirements.

    Alternatively, you could replace valueForKeyPath:... by the following code which preserves the order of the elements. The code uses a NSMutableSet to keep track of all elements added to the result array so far, because testing for membership is faster with sets as with arrays.

    NSArray *assignments = categoryAssignments.arrangedObjects;
    
    NSMutableSet *found = [NSMutableSet set];
    NSMutableArray *distinctDates = [NSMutableArray array];
    for (NSDictionary *obj in objects) {
        NSDate *date = obj[@"dayOfExecution"];
        if (![found containsObject:date]) {
            [distinctDates addObject:date];
            [found addObject:date];
        }
    }