| // |
| // GTMIBArray.m |
| // |
| // Copyright 2009 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 "GTMIBArray.h" |
| #import "GTMDefines.h" |
| |
| @implementation GTMIBArray |
| |
| - (void)dealloc { |
| [realArray_ release]; |
| [super dealloc]; |
| } |
| |
| - (void)setupRealArray { |
| |
| #ifdef DEBUG |
| // It is very easy to create a cycle if you are chaining these in IB, so in |
| // debug builds, we try to catch this to inform the developer. Use -[NSArray |
| // indexOfObjectIdenticalTo:] to get pointer comparisons instead of object |
| // equality. |
| static NSMutableArray *ibArraysBuilding = nil; |
| if (!ibArraysBuilding) { |
| ibArraysBuilding = [[NSMutableArray alloc] init]; |
| } |
| _GTMDevAssert([ibArraysBuilding indexOfObjectIdenticalTo:self] == NSNotFound, |
| @"There is a cycle in your GTMIBArrays!"); |
| [ibArraysBuilding addObject:self]; |
| #endif // DEBUG |
| |
| // Build the array up. |
| NSMutableArray *builder = [NSMutableArray array]; |
| Class ibArrayClass = [GTMIBArray class]; |
| id objs[] = { |
| object1_, object2_, object3_, object4_, object5_, |
| }; |
| for (size_t idx = 0 ; idx < sizeof(objs) / (sizeof(objs[0])) ; ++idx) { |
| id obj = objs[idx]; |
| if (obj) { |
| if ([obj isKindOfClass:ibArrayClass]) { |
| [builder addObjectsFromArray:obj]; |
| } else { |
| [builder addObject:obj]; |
| } |
| } |
| } |
| |
| #ifdef DEBUG |
| [ibArraysBuilding removeObject:self]; |
| #endif // DEBUG |
| |
| // Now copy with our zone. |
| realArray_ = [builder copyWithZone:[self zone]]; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // NSArray has two methods that everything else seems to work on, simply |
| // implement those. |
| |
| - (NSUInteger)count { |
| if (!realArray_) [self setupRealArray]; |
| return [realArray_ count]; |
| } |
| |
| - (id)objectAtIndex:(NSUInteger)idx { |
| if (!realArray_) [self setupRealArray]; |
| return [realArray_ objectAtIndex:idx]; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Directly relay the enumeration based calls just in case there is some extra |
| // efficency to be had. |
| |
| - (NSEnumerator *)objectEnumerator { |
| if (!realArray_) [self setupRealArray]; |
| return [realArray_ objectEnumerator]; |
| } |
| |
| - (NSEnumerator *)reverseObjectEnumerator { |
| if (!realArray_) [self setupRealArray]; |
| return [realArray_ reverseObjectEnumerator]; |
| } |
| |
| - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state |
| objects:(id *)stackbuf |
| count:(NSUInteger)len { |
| if (!realArray_) [self setupRealArray]; |
| return [realArray_ countByEnumeratingWithState:state |
| objects:stackbuf |
| count:len]; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Directly relay the copy methods, again, for any extra efficency. |
| |
| - (id)copyWithZone:(NSZone *)zone { |
| if (!realArray_) [self setupRealArray]; |
| return [realArray_ copyWithZone:zone]; |
| } |
| |
| - (id)mutableCopyWithZone:(NSZone *)zone { |
| if (!realArray_) [self setupRealArray]; |
| return [realArray_ mutableCopyWithZone:zone]; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // On 10.6, being loaded out of a nib causes the object to get hashed and |
| // stored off. The implementation of -hash in NSArray then calls -count, which |
| // causes this object to latch on to an empty array. So... |
| // 1. -hash gets overridden to simply use the class pointer to maintain |
| // the -[NSObject hash] contract that equal objects must have the same hash |
| // value. This puts the work in isEqual... |
| // 2. -isEqual: overide. Objects can't use the NSArray version until all of |
| // the outlets have been filled in and the object is truly setup. The best |
| // escape for this is to simply do pointer comparison until the outlets are |
| // fully setup. |
| // 3. awakeFromNib gets overridden to force the initialize of the real array |
| // when all the outlets have been filled in. |
| // |
| // NOTE: The first attempt was to just overide hash, but that only makes the |
| // first IBArray in a nib work. The fact that isEqual was falling through to |
| // the NSArray version (comparing to empty arrays), prevented all of the |
| // IBArrays from being fully loaded from the nib correctly. |
| |
| - (NSUInteger)hash { |
| return (NSUInteger)(void*)[self class]; |
| } |
| |
| - (BOOL)isEqual:(id)anObject { |
| if ([anObject isMemberOfClass:[self class]]) { |
| GTMIBArray *ibArray2 = anObject; |
| if (!realArray_ || !(ibArray2->realArray_)) { |
| // If realArray_ or ibArray2 haven't been fully configured yet, the only |
| // way they can be equal is if they are the same pointer. |
| return (self == anObject); |
| } |
| } |
| return [super isEqual:anObject]; |
| } |
| |
| - (void)awakeFromNib { |
| [realArray_ autorelease]; |
| realArray_ = nil; |
| [self setupRealArray]; |
| } |
| |
| @end |