0
NSArray *arrClient = [[NSArray alloc] initWithObjects:@"record 1", @"record 2", nil];
NSArray *arrServer = [[NSArray alloc] initWithObjects:@"record 1", @"record 3", nil];

On arrServer I would like to apply predicates to filter only those entries that DON'T already exist in arrClient. e.g. in this case record 1 exist in both arrays and shall be ignored, hence only an array with one entry with the "record 3" string shall be returned.

Is this possible?

UPDATE

The answers below are great. I believe I need to give a real example to verify if what I am doing makes sense after all. (I am still giving a compact version below)

Now the clientItems will be of type FTRecord (Core Data)

@interface FTRecord : NSManagedObject
...
@property (nonatomic) NSTimeInterval recordDate;

@end

@implementation FTRecord
...
@dynamic recordDate;

@end

This class below is a holder for parsing json from a REST service. Hence the serverItems we mentioned earlier will be of this type.

@interface FTjsonRecord : NSObject <JSONSerializable>
{

}

@property (nonatomic) NSDate *recordDate;

@implementation FTjsonRecord

- (NSUInteger)hash
{
    return [[self recordDate] hash];
}

- (BOOL)isEqual:(id)object
{
    if ([object isKindOfClass:[FTjsonRecord self]]) {
        FTjsonRecord *other = object;
        return [[self recordDate] isEqualToDate:[other recordDate]];
    }
    else if ([object isKindOfClass:[FTRecord self]]) {
        FTRecord *other = object;
        return [[self recordDate] isEqualToDate:[NSDate dateWithTimeIntervalSinceReferenceDate:[other recordDate]]];
    }
    else {
        return NO;
    }
}

Going with Wain's example, this seems to work fine. Now is this feasible? Keep in mind that serverItems are just temporary and only used for syncing with server, and will be thrown away. clientItems is the one that remains in place.

UPDATE 2:

This time I am trying Manu's solution:

I have created this method on my Client DBStore, which is called by the predicate. The reason I can't use containsObject is because the class types in serverItems and clientItems are not the same type.

-(BOOL)recordExistsForDate:(NSDate *)date
{
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"recordDate == %@", date];
    NSArray *arr = [allRecords filteredArrayUsingPredicate:predicate];
    if (arr && [arr count] > 0) {
        return YES;
    } else {
        return NO;
    }
}

NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(FTjsonRecord *evaluatedObject, NSDictionary *bindings) {
                return ![store recordExistsForDate:[evaluatedObject recordDate]];
}];

NSSet *set = [[serverRecords items] filteredSetUsingPredicate:predicate]; 

What worries me about this solution though, is the linear read from my clientItems (allRecords). I am not sure how efficient it is using the predicate on the array, wonder if there is a better way to achieve this.

2
  • Have you looked at using NSSet instead of predicates? Commented Oct 28, 2013 at 15:55
  • Could you give an example? Commented Oct 28, 2013 at 16:19

2 Answers 2

1

You can use NSSet to get the union, intersection and difference (minus) with other sets. This more accurately matches what you're trying to do.

NSMutableSet *serverItems = [[NSMutableSet alloc] init];
[arrServerItems addObjectsFromArray:arrServer];

NSSet *clientItems = [[NSSet alloc] init];
[clientItems addObjectsFromArray:arrClient];

[arrServerItems minus:clientItems];

This does remove the ordering information though.

For predicates you can use:

NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", arrClient];
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks, that looks really good. Now my serverItems and clientItems happen to have different types. I need in this case to implement the isEqual on the serverItem type in order to make minusSet work, correct?
Yes, but no. You should really have given a true example in your question. You don't want to change isEqual if you're putting things in collections. You're better off having a custom comparison method and using predicateWithBlock as per @Manu answer.
Mhh interesting. I actually got it working. You mentioned though I shouldn't do it. I have now updated the question with the real example. May you please have a look if it makes any sense or I am taking this too far?
It should work ok, but you can't guarantee that it will in future. Because the objects are different you can have [O1 isEqual:O2] != [O2 isEqual:O1] which should never be the case. It's safer to use the block approach because then you control how the method is called and don't change the isEqual method at all.
Thanks Wain, after a lot of reading, I am now trying Manu's solution at Update 2. This one works too now, but I am worried about the performance, if ClientItems (AllRecords) gets bigger... Any improvement suggestions?
|
0

depend to the predicate that you want to use:

you can use an array of arguments using this

[NSPredicate predicateWithFormat:<#(NSString *)#> argumentArray:<#(NSArray *)#>];

and build your predicate using the objects in the array

or use a predicate with block

[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
    <#code#>
}]

and in the block evluate the object comparing it with the object in your array

a possible check you can do is:

return ![arrClient containsObject:evaluatedObject];

will exclude objects contained in arrClient

containsObject: use 'isEqual:' to compare the objects

2 Comments

Manu, if you were so kind to check update 2 in the question, I would appreciate it. Keep in mind that the two array's don't contain the same type. (Mentioned more in detail in update 1) Hence I can't use your suggested containsObject. So I wonder if this approach I have taken is good or not. I am just concerned a bit about the performance. Thanks
in the Update 1, you have overrided hash and isEqual, so even the objects are different classes you still able to use containsObject (off course both classes have to override isEqual), or you can simply do in the block the same check you do in the overrided isEqual

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.