├── .gitignore ├── LICENSE ├── MAMutableArray.h ├── MAMutableArray.m ├── MAMutableDictionary.h ├── MAMutableDictionary.m ├── README.markdown └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | a.out -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MACollections is released into the public domain. You may use it for any purpose and modify it in any way you see fit. 2 | 3 | Attribution is requested but not required. If you wish to provide attribution, thanks! You may do so in any way you like. 4 | -------------------------------------------------------------------------------- /MAMutableArray.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | 5 | @interface MAMutableArray : NSMutableArray 6 | @end 7 | 8 | void MAMutableArrayTest(void); 9 | -------------------------------------------------------------------------------- /MAMutableArray.m: -------------------------------------------------------------------------------- 1 | 2 | #import "MAMutableArray.h" 3 | 4 | 5 | @implementation MAMutableArray { 6 | NSUInteger _count; 7 | NSUInteger _capacity; 8 | id *_objs; 9 | } 10 | 11 | - (id)initWithCapacity: (NSUInteger)capacity 12 | { 13 | return [super init]; 14 | } 15 | 16 | - (void)dealloc 17 | { 18 | [self removeAllObjects]; 19 | free(_objs); 20 | [super dealloc]; 21 | } 22 | 23 | - (NSUInteger)count 24 | { 25 | return _count; 26 | } 27 | 28 | - (id)objectAtIndex: (NSUInteger)index 29 | { 30 | return _objs[index]; 31 | } 32 | 33 | - (void)addObject:(id)anObject 34 | { 35 | [self insertObject: anObject atIndex: [self count]]; 36 | } 37 | 38 | - (void)insertObject: (id)anObject atIndex: (NSUInteger)index 39 | { 40 | if(_count >= _capacity) 41 | { 42 | NSUInteger newCapacity = MAX(_capacity * 2, 16); 43 | id *newObjs = malloc(newCapacity * sizeof(*newObjs)); 44 | 45 | memcpy(newObjs, _objs, _count * sizeof(*_objs)); 46 | 47 | free(_objs); 48 | _objs = newObjs; 49 | _capacity = newCapacity; 50 | } 51 | 52 | memmove(_objs + index + 1, _objs + index, ([self count] - index) * sizeof(*_objs)); 53 | _objs[index] = [anObject retain]; 54 | 55 | _count++; 56 | } 57 | 58 | - (void)removeLastObject 59 | { 60 | [self removeObjectAtIndex: [self count] - 1]; 61 | } 62 | 63 | - (void)removeObjectAtIndex: (NSUInteger)index 64 | { 65 | [_objs[index] release]; 66 | memmove(_objs + index, _objs + index + 1, ([self count] - index - 1) * sizeof(*_objs)); 67 | 68 | _count--; 69 | } 70 | 71 | - (void)replaceObjectAtIndex: (NSUInteger)index withObject: (id)anObject 72 | { 73 | [anObject retain]; 74 | [_objs[index] release]; 75 | _objs[index] = anObject; 76 | } 77 | 78 | @end 79 | 80 | void MAMutableArrayTest(void) 81 | { 82 | NSMutableArray *referenceArray = [NSMutableArray array]; 83 | MAMutableArray *testArray = [MAMutableArray array]; 84 | 85 | struct seed_t { unsigned short v[3]; }; 86 | __block struct seed_t seed = { { 0, 0, 0 } }; 87 | 88 | __block NSMutableArray *array; 89 | 90 | void (^blocks[])(void) = { 91 | ^{ 92 | [array addObject: [NSNumber numberWithInt: nrand48(seed.v)]]; 93 | }, 94 | ^{ 95 | id obj = [NSNumber numberWithInt: nrand48(seed.v)]; 96 | NSUInteger index = nrand48(seed.v) % ([array count] + 1); 97 | [array insertObject: obj atIndex: index]; 98 | }, 99 | ^{ 100 | if([array count] > 0) 101 | [array removeLastObject]; 102 | }, 103 | ^{ 104 | if([array count] > 0) 105 | [array removeObjectAtIndex: nrand48(seed.v) % [array count]]; 106 | }, 107 | ^{ 108 | if([array count] > 0) 109 | { 110 | id obj = [NSNumber numberWithInt: nrand48(seed.v)]; 111 | NSUInteger index = nrand48(seed.v) % [array count]; 112 | [array replaceObjectAtIndex: index withObject: obj]; 113 | } 114 | } 115 | }; 116 | 117 | NSMutableArray *operations = [NSMutableArray array]; 118 | 119 | for(int i = 0; i < 100000; i++) 120 | { 121 | NSUInteger index = nrand48(seed.v) % (sizeof(blocks) / sizeof(*blocks)); 122 | void (^block)(void) = blocks[index]; 123 | [operations addObject: [NSNumber numberWithInteger: index]]; 124 | 125 | struct seed_t oldSeed = seed; 126 | array = testArray; 127 | block(); 128 | seed = oldSeed; 129 | array = referenceArray; 130 | block(); 131 | 132 | if(![referenceArray isEqual: testArray]) 133 | { 134 | int one = nrand48(oldSeed.v); 135 | int two = nrand48(oldSeed.v); 136 | NSLog(@"Next two random numbers are %d %d", one, two); 137 | NSLog(@"Arrays are not equal after %@: %@ %@", operations, referenceArray, testArray); 138 | exit(1); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /MAMutableDictionary.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | @interface MAFixedMutableDictionary : NSMutableDictionary 5 | - (id)initWithSize: (NSUInteger)size; 6 | @end 7 | 8 | @interface MAMutableDictionary : NSMutableDictionary 9 | @end 10 | 11 | void MAMutableDictionaryTest(void); 12 | -------------------------------------------------------------------------------- /MAMutableDictionary.m: -------------------------------------------------------------------------------- 1 | 2 | #import "MAMutableDictionary.h" 3 | 4 | @interface _MAMutableDictionaryBucket : NSObject 5 | @property (nonatomic, copy) id key; 6 | @property (nonatomic, retain) id obj; 7 | @property (nonatomic, retain) _MAMutableDictionaryBucket *next; 8 | @end 9 | 10 | @implementation _MAMutableDictionaryBucket 11 | 12 | - (void)dealloc 13 | { 14 | [_key release]; 15 | [_obj release]; 16 | [_next release]; 17 | [super dealloc]; 18 | } 19 | 20 | @end 21 | 22 | 23 | @interface _MABlockEnumerator : NSEnumerator 24 | { 25 | id (^_block)(void); 26 | } 27 | - (id)initWithBlock: (id (^)(void))block; 28 | @end 29 | 30 | @implementation _MABlockEnumerator 31 | 32 | - (id)initWithBlock: (id (^)(void))block 33 | { 34 | if((self = [self init])) 35 | _block = [block copy]; 36 | return self; 37 | } 38 | 39 | - (void)dealloc 40 | { 41 | [_block release]; 42 | [super dealloc]; 43 | } 44 | 45 | - (id)nextObject 46 | { 47 | return _block(); 48 | } 49 | 50 | @end 51 | 52 | 53 | @implementation MAFixedMutableDictionary { 54 | NSUInteger _count; 55 | NSUInteger _size; 56 | _MAMutableDictionaryBucket **_array; 57 | } 58 | 59 | - (id)initWithSize: (NSUInteger)size 60 | { 61 | if((self = [super init])) 62 | { 63 | _size = size; 64 | _array = calloc(size, sizeof(*_array)); 65 | } 66 | return self; 67 | } 68 | 69 | - (void)dealloc 70 | { 71 | for(NSUInteger i = 0; i < _size; i++) 72 | [_array[i] release]; 73 | free(_array); 74 | 75 | [super dealloc]; 76 | } 77 | 78 | - (NSUInteger)count 79 | { 80 | return _count; 81 | } 82 | 83 | - (id)objectForKey: (id)key 84 | { 85 | NSUInteger bucketIndex = [key hash] % _size; 86 | _MAMutableDictionaryBucket *bucket = _array[bucketIndex]; 87 | while(bucket) 88 | { 89 | if([[bucket key] isEqual: key]) 90 | return [bucket obj]; 91 | bucket = [bucket next]; 92 | } 93 | return nil; 94 | } 95 | 96 | - (NSEnumerator *)keyEnumerator 97 | { 98 | __block NSUInteger index = -1; 99 | __block _MAMutableDictionaryBucket *bucket = nil; 100 | NSEnumerator *e = [[_MABlockEnumerator alloc] initWithBlock: ^{ 101 | bucket = [bucket next]; 102 | while(!bucket) 103 | { 104 | index++; 105 | if(index >= _size) 106 | return (id)nil; 107 | bucket = _array[index]; 108 | } 109 | return [bucket key]; 110 | }]; 111 | return [e autorelease]; 112 | } 113 | 114 | - (void)removeObjectForKey: (id)key 115 | { 116 | NSUInteger bucketIndex = [key hash] % _size; 117 | _MAMutableDictionaryBucket *previousBucket = nil; 118 | _MAMutableDictionaryBucket *bucket = _array[bucketIndex]; 119 | while(bucket) 120 | { 121 | if([[bucket key] isEqual: key]) 122 | { 123 | if(previousBucket == nil) 124 | { 125 | _MAMutableDictionaryBucket *nextBucket = [[bucket next] retain]; 126 | [_array[bucketIndex] release]; 127 | _array[bucketIndex] = nextBucket; 128 | } 129 | else 130 | { 131 | [previousBucket setNext: [bucket next]]; 132 | } 133 | _count--; 134 | return; 135 | } 136 | previousBucket = bucket; 137 | bucket = [bucket next]; 138 | } 139 | } 140 | 141 | - (void)setObject: (id)obj forKey: (id)key 142 | { 143 | _MAMutableDictionaryBucket *newBucket = [[_MAMutableDictionaryBucket alloc] init]; 144 | [newBucket setKey: key]; 145 | [newBucket setObj: obj]; 146 | 147 | [self removeObjectForKey: key]; 148 | 149 | NSUInteger bucketIndex = [key hash] % _size; 150 | [newBucket setNext: _array[bucketIndex]]; 151 | [_array[bucketIndex] release]; 152 | _array[bucketIndex] = newBucket; 153 | _count++; 154 | } 155 | 156 | @end 157 | 158 | @implementation MAMutableDictionary { 159 | NSUInteger _size; 160 | MAFixedMutableDictionary *_fixedDict; 161 | } 162 | 163 | static const NSUInteger kMaxLoadFactorNumerator = 7; 164 | static const NSUInteger kMaxLoadFactorDenominator = 10; 165 | 166 | - (id)initWithCapacity: (NSUInteger)capacity 167 | { 168 | capacity = MAX(capacity, 4); 169 | if((self = [super init])) 170 | { 171 | _size = capacity; 172 | _fixedDict = [[MAFixedMutableDictionary alloc] initWithSize: _size]; 173 | } 174 | return self; 175 | } 176 | 177 | - (void)dealloc 178 | { 179 | [_fixedDict release]; 180 | [super dealloc]; 181 | } 182 | 183 | - (NSUInteger)count 184 | { 185 | return [_fixedDict count]; 186 | } 187 | 188 | - (id)objectForKey: (id)key 189 | { 190 | return [_fixedDict objectForKey: key]; 191 | } 192 | 193 | - (NSEnumerator *)keyEnumerator 194 | { 195 | return [_fixedDict keyEnumerator]; 196 | } 197 | 198 | - (void)removeObjectForKey: (id)key 199 | { 200 | [_fixedDict removeObjectForKey: key]; 201 | } 202 | 203 | - (void)setObject: (id)obj forKey:(id)key 204 | { 205 | [_fixedDict setObject: obj forKey: key]; 206 | 207 | if(kMaxLoadFactorDenominator * [_fixedDict count] / _size > kMaxLoadFactorNumerator) 208 | { 209 | NSUInteger newSize = _size * 2; 210 | MAFixedMutableDictionary *newDict = [[MAFixedMutableDictionary alloc] initWithSize: newSize]; 211 | 212 | for(id key in _fixedDict) 213 | [newDict setObject: [_fixedDict objectForKey: key] forKey: key]; 214 | 215 | [_fixedDict release]; 216 | _size = newSize; 217 | _fixedDict = newDict; 218 | } 219 | } 220 | 221 | @end 222 | 223 | static void Test(NSMutableDictionary *testDictionary) 224 | { 225 | NSMutableDictionary *referenceDictionary = [NSMutableDictionary dictionary]; 226 | 227 | struct seed_t { unsigned short v[3]; }; 228 | __block struct seed_t seed = { { 0, 0, 0 } }; 229 | 230 | __block NSMutableDictionary *dict; 231 | 232 | void (^blocks[])(void) = { 233 | ^{ 234 | id key = [NSNumber numberWithInt: nrand48(seed.v) % 1024]; 235 | id value = [NSNumber numberWithInt: nrand48(seed.v)]; 236 | [dict setObject: value forKey: key]; 237 | }, 238 | ^{ 239 | id key = [NSNumber numberWithInt: nrand48(seed.v) % 1024]; 240 | [dict removeObjectForKey: key]; 241 | } 242 | }; 243 | 244 | for(int i = 0; i < 10000; i++) 245 | { 246 | NSUInteger index = nrand48(seed.v) % (sizeof(blocks) / sizeof(*blocks)); 247 | void (^block)(void) = blocks[index]; 248 | 249 | struct seed_t oldSeed = seed; 250 | dict = testDictionary; 251 | block(); 252 | seed = oldSeed; 253 | dict = referenceDictionary; 254 | block(); 255 | 256 | if(![testDictionary isEqual: referenceDictionary]) 257 | { 258 | NSLog(@"Dictionaries are not equal: %@ %@", referenceDictionary, testDictionary); 259 | exit(1); 260 | } 261 | } 262 | } 263 | 264 | void MAMutableDictionaryTest(void) 265 | { 266 | // Test([NSMutableDictionary dictionary]); 267 | Test([[[MAFixedMutableDictionary alloc] initWithSize: 10] autorelease]); 268 | Test([MAMutableDictionary dictionary]); 269 | } 270 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | These are sample implementations of `NSMutableArray` and `NSMutableDictionary`. They do not rely on existing concrete implementations, but are instead "from scratch" in that they implement an array/hash table directly with more primitive constructs. 2 | 3 | These are primarily intended for educational purposes. The `MAMutableArray` implementation is discussed here: 4 | 5 | http://mikeash.com/pyblog/friday-qa-2012-03-09-lets-build-nsmutablearray.html 6 | 7 | A discussion of `MAMutableDictionary` is forthcoming. 8 | 9 | These may well be useful as actual code, though. I believe the unit tests are quite thorough and should demonstrate that they are solid enough to use in real apps. They could come in handy if you need some customizable behavior that's difficult to add on to the framework implementations. (One good example would be adding weak reference support to `MAMutableDictionary`, which would be easy to do but is really hard to add into a standard `NSMutableDictionary`.) 10 | 11 | If you want to use it, the code is public domain and can be used however you like. Credit is preferred but not required. For more information, see the LICENSE file. -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // xcrun clang -framework Foundation main.m MAMutableArray.m MAMutableDictionary.m 2 | #import 3 | #import "MAMutableArray.h" 4 | #import "MAMutableDictionary.h" 5 | 6 | 7 | int main(int argc, char **argv) 8 | { 9 | @autoreleasepool 10 | { 11 | MAMutableArrayTest(); 12 | MAMutableDictionaryTest(); 13 | } 14 | } 15 | --------------------------------------------------------------------------------