Search code examples
iosobjective-cnsarray

Objective-C: Selectively combine two arrays


I am aware of how normal NSArray concatenation works in Objective-C. This is not that question.

I have data that is being incrementally updated from a web service. My object has the following class definition (with a lot removed):

//  NoteTemplate.h
#import <UIKit/UIKit.h>
@interface NoteTemplate 

@property (copy, nonatomic) NSString *objectId;

I am caching a list of these on-device and checking at launch to see if there are any new or updated NoteTemplate objects in my database to load. So, I end up with two arrays:

NSArray <NoteTemplate *> *oldArray
NSArray <NoteTemplate *> *newArray

If there are no updates, then all I need to do is simply concatenate the two arrays together and that's that.

If there are updates, however, I want to combine the two arrays, but whenever there is a common objectId, the item in newArray should take precedence over the item in oldArray.

Thus far, I am brute-forcing it like this:

- (void)updateNoteTemplatesWithArray:(NSArray *)newTemplates {
    NSArray *oldTemplates = [self getNoteTemplates];
    NSMutableArray *combined = [NSMutableArray arrayWithArray:newTemplates];
    for (NoteTemplate *noteTemplate in oldTemplates) {
        NSArray *matches = [combined filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id blockTemplate, NSDictionary<NSString *,id> *bindings) {
            return [((NoteTemplate *)blockTemplate).objectId isEqualToString:noteTemplate.objectId];
        }]];
        if (matches.count == 0) {
            [combined addObject:noteTemplate];
        }
    }
    [self setNoteTemplates:[combined copy]];
}

Is there a more optimized way to do this? I can't see that this will affect performance at all, so perhaps an optimization is unnecessary. Still, this approach feels hacky and way over-engineered.


Solution

  • To extend @Larme's suggestion with Set usage you can try the following approach:

    @interface NoteTemplate: NSObject
    
    @property (copy, nonatomic) NSString *objectId;
    @property (copy, nonatomic) NSString *text;
    
    - (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text;
    
    @end
    
    @implementation NoteTemplate
    - (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text {
        self = [super init];
        if (self != nil) {
            _objectId = objectId;
            _text = text;
        }
        return self;
    }
    
    - (BOOL)isEqual:(id)object {
        return [self.objectId isEqualToString:[object objectId]];
    }
    
    @end
    

    And the usage code:

    NoteTemplate *nt1 = [[NoteTemplate alloc] initWithObjectId:@"1" text:@"old set"];
    NoteTemplate *nt2 = [[NoteTemplate alloc] initWithObjectId:@"2" text:@"old set"];
    NoteTemplate *nt3 = [[NoteTemplate alloc] initWithObjectId:@"1" text:@"new set"];
    NoteTemplate *nt4 = [[NoteTemplate alloc] initWithObjectId:@"3" text:@"new set"];
    
    NSSet <NoteTemplate *> *oldSet = [NSSet setWithObjects:nt1, nt2, nil];
    NSSet <NoteTemplate *> *newSet = [NSSet setWithObjects:nt3, nt4, nil];
    
    NSMutableSet <NoteTemplate *> *mergedSet = [newSet mutableCopy];
    [mergedSet unionSet:oldSet];
    
    for (NoteTemplate *note in mergedSet) {
        NSLog(@"Set item %@ %@", note.objectId, note.text);
    }
    

    After executing this code you'll see in the log:

    Set item 3 new set
    Set item 1 new set
    Set item 2 old set
    

    I assume that's what you were looking for.