aboutsummaryrefslogtreecommitdiff
path: root/Foundation/GTMNSArray+Merge.m
blob: 5bf07c8f6fe3bd2c2e945d36261ee8b36ba3e8de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//
//  GTMNSArray+Merge.m
//
//  Copyright 2008 Google Inc.
//
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
//  use this file except in compliance with the License.  You may obtain a copy
//  of the License at
// 
//  http://www.apache.org/licenses/LICENSE-2.0
// 
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
//  License for the specific language governing permissions and limitations 
//  under the License.
//

#import "GTMNSArray+Merge.h"

#import "GTMDefines.h"

#if GTM_IPHONE_SDK
#import <objc/message.h>
#else  // GTM_IPHONE_SDK
#import <objc/objc-runtime.h>
#endif  // GTM_IPHONE_SDK

@implementation NSArray (GTMNSArrayMergingAdditions)

- (NSArray *)gtm_mergeArray:(NSArray *)newArray
              mergeSelector:(SEL)merger {
  return [self gtm_mergeArray:newArray
              compareSelector:@selector(compare:)
                mergeSelector:merger];
}

- (NSArray *)gtm_mergeArray:(NSArray *)newArray
            compareSelector:(SEL)comparer
              mergeSelector:(SEL)merger {
  // must have a compare selector
  if (!comparer) return nil;

  // Sort and merge the contents of |self| with |newArray|.
  NSArray *sortedMergedArray = nil;
  if ([self count] && [newArray count]) {
    NSMutableArray *mergingArray = [NSMutableArray arrayWithArray:self];
    [mergingArray sortUsingSelector:comparer];
    NSArray *sortedNewArray
      = [newArray sortedArrayUsingSelector:comparer];
    
    NSUInteger oldIndex = 0;
    NSUInteger oldCount = [mergingArray count];
    id oldItem = (oldIndex < oldCount)
                 ? [mergingArray objectAtIndex:0]
                 : nil;
    
    id newItem = nil;
    GTM_FOREACH_OBJECT(newItem, sortedNewArray) {
      BOOL stillLooking = YES;
      while (oldIndex < oldCount && stillLooking) {
        // We must take care here, since Intel leaves junk in high bytes of
        // return register for predicates that return BOOL.
        // For details see: 
        // http://developer.apple.com/documentation/MacOSX/Conceptual/universal_binary/universal_binary_tips/chapter_5_section_23.html
        // and
        // http://www.red-sweater.com/blog/320/abusing-objective-c-with-class#comment-83187
        NSComparisonResult result
          = ((NSComparisonResult (*)(id, SEL, id))objc_msgSend)(newItem, comparer, oldItem);
        if (result == NSOrderedSame && merger) {
          // It's a match!
          id repItem = [oldItem performSelector:merger
                                     withObject:newItem];
          [mergingArray replaceObjectAtIndex:oldIndex
                                  withObject:repItem];
          ++oldIndex;
          oldItem = (oldIndex < oldCount)
                    ? [mergingArray objectAtIndex:oldIndex]
                    : nil;
          stillLooking = NO;
        } else if (result == NSOrderedAscending
                   || (result == NSOrderedSame && !merger)) {
          // This is either a new item and belongs right here, or it's
          // a match to an existing item but we're not merging.
          [mergingArray insertObject:newItem
                             atIndex:oldIndex];
          ++oldIndex;
          ++oldCount;
          stillLooking = NO;
        } else {
          ++oldIndex;
          oldItem = (oldIndex < oldCount)
                    ? [mergingArray objectAtIndex:oldIndex]
                    : nil;
        }
      }
      if (stillLooking) {
        // Once we get here, the rest of the new items get appended.
        [mergingArray addObject:newItem];
      }
    }
    sortedMergedArray = mergingArray;
  } else if ([self count]) {
    sortedMergedArray = [self sortedArrayUsingSelector:comparer];
  } else if ([newArray count]) {
    sortedMergedArray = [newArray sortedArrayUsingSelector:comparer];
  }
  return sortedMergedArray;
}

@end