aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AppKit/GTMNSImage+Scaling.h46
-rw-r--r--AppKit/GTMNSImage+Scaling.m168
-rw-r--r--AppKit/GTMNSImage+ScalingTest.m47
-rw-r--r--DebugUtils/GTMMethodCheckTest.m4
-rw-r--r--Foundation/GTMExceptionalInlines.h52
-rw-r--r--Foundation/GTMExceptionalInlines.m52
-rw-r--r--Foundation/GTMExceptionalInlinesTest.m65
-rw-r--r--Foundation/GTMGeometryUtils.h24
-rw-r--r--Foundation/GTMGeometryUtils.m8
-rw-r--r--Foundation/GTMGeometryUtilsTest.m25
-rw-r--r--Foundation/GTMHTTPFetcher.m24
-rw-r--r--Foundation/GTMLoggerTest.m12
-rw-r--r--Foundation/GTMNSData+zlibTest.m382
-rw-r--r--Foundation/GTMNSString+Replace.h5
-rw-r--r--Foundation/GTMNSString+Replace.m5
-rw-r--r--Foundation/GTMNSString+ReplaceTest.m4
-rw-r--r--Foundation/GTMPathTest.m4
-rw-r--r--Foundation/GTMValidatingContainersTest.m3
-rw-r--r--GTM.xcodeproj/project.pbxproj34
-rw-r--r--GTMiPhone.xcodeproj/project.pbxproj118
-rw-r--r--ReleaseNotes.txt15
-rw-r--r--UnitTesting/GTMIPhoneUnitTestDelegate.m4
-rw-r--r--UnitTesting/GTMUIKit+UnitTesting.h12
-rw-r--r--UnitTesting/GTMUIKit+UnitTesting.m8
-rw-r--r--UnitTesting/GTMUIKit+UnitTestingTest.m7
-rwxr-xr-xUnitTesting/RunIPhoneUnitTest.sh16
-rwxr-xr-xUnitTesting/RunMacOSUnitTests.sh2
-rw-r--r--XcodeConfig/Project/DebugLeopardOrLater.xcconfig3
-rw-r--r--XcodeConfig/Project/DebugTigerOrLater.xcconfig3
-rw-r--r--XcodeConfig/Project/DebugiPhone.xcconfig3
-rw-r--r--XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig3
-rw-r--r--XcodeConfig/Project/ReleaseTigerOrLater.xcconfig3
-rw-r--r--XcodeConfig/Project/ReleaseiPhone.xcconfig3
-rw-r--r--XcodeConfig/Target/LoadableBundle.xcconfig4
-rw-r--r--XcodeConfig/Target/LoadableBundleGCSupported.xcconfig2
-rw-r--r--XcodeConfig/Target/SharedLibrary.xcconfig2
-rw-r--r--XcodeConfig/Target/SharedLibraryGCSupported.xcconfig2
-rw-r--r--XcodeConfig/Target/StaticLibrary.xcconfig2
-rw-r--r--XcodeConfig/Target/StaticLibraryGCSupported.xcconfig2
-rw-r--r--XcodeConfig/subconfig/64bit.xcconfig26
-rw-r--r--XcodeConfig/subconfig/General.xcconfig24
-rw-r--r--XcodeConfig/subconfig/LeopardOrLater.xcconfig7
-rw-r--r--XcodeConfig/subconfig/TigerOrLater.xcconfig3
-rw-r--r--XcodeConfig/subconfig/iPhone.xcconfig11
-rw-r--r--iPhone/GTMABAddressBook.h319
-rw-r--r--iPhone/GTMABAddressBook.m902
-rw-r--r--iPhone/GTMABAddressBook.stringsbin0 -> 1428 bytes
-rw-r--r--iPhone/GTMABAddressBookTest.m558
-rw-r--r--iPhone/TestData/phone.pngbin0 -> 3242 bytes
49 files changed, 2745 insertions, 283 deletions
diff --git a/AppKit/GTMNSImage+Scaling.h b/AppKit/GTMNSImage+Scaling.h
new file mode 100644
index 0000000..297d18c
--- /dev/null
+++ b/AppKit/GTMNSImage+Scaling.h
@@ -0,0 +1,46 @@
+//
+// GTMNSImage+Scaling.h
+//
+// Scales NSImages to a variety of sizes for drawing
+//
+// Copyright 2006-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 <Foundation/Foundation.h>
+
+@interface NSImage (GTMNSImageScaling)
+
+// Return an existing representation of a size
+- (NSImageRep *)gtm_representationOfSize:(NSSize)size;
+
+// Return the exact or next largest representation for a size
+- (NSImageRep *)gtm_bestRepresentationForSize:(NSSize)size;
+
+// Create a new represetation for a given size
+- (BOOL)gtm_createRepresentationOfSize:(NSSize)size;
+
+// Create 32 and 16px reps
+- (BOOL)gtm_createIconRepresentations;
+
+// Remove reps larger than a given size and create a new rep if needed
+- (void)gtm_shrinkToSize:(NSSize)size;
+
+// Remove reps larger than a given size
+- (void)gtm_removeRepresentationsLargerThanSize:(NSSize)size;
+
+// Return a dup shrunk to a given size
+- (NSImage *)gtm_duplicateOfSize:(NSSize)size;
+@end
diff --git a/AppKit/GTMNSImage+Scaling.m b/AppKit/GTMNSImage+Scaling.m
new file mode 100644
index 0000000..57687fd
--- /dev/null
+++ b/AppKit/GTMNSImage+Scaling.m
@@ -0,0 +1,168 @@
+//
+// GTMNSImage+Scaling.m
+//
+// Scales NSImages to a variety of sizes for drawing
+//
+// Copyright 2006-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 "GTMNSImage+Scaling.h"
+#import "GTMGeometryUtils.h"
+
+@implementation NSImage (GTMNSImageScaling)
+
+- (NSImageRep *)gtm_bestRepresentationForSize:(NSSize)size {
+ NSImageRep *bestRep = [self gtm_representationOfSize:size];
+ if (bestRep) {
+ return bestRep;
+ }
+ NSArray *reps = [self representations];
+
+ CGFloat repDistance = CGFLOAT_MAX;
+
+ NSImageRep *thisRep;
+ NSEnumerator *repEnum = [reps objectEnumerator];
+ while ((thisRep = [repEnum nextObject])) {
+ CGFloat thisDistance;
+ thisDistance = MIN(size.width - [thisRep size].width,
+ size.height-[thisRep size].height);
+
+ if (repDistance < 0 && thisDistance > 0) continue;
+ if (ABS(thisDistance) < ABS(repDistance)
+ || (thisDistance < 0 && repDistance > 0)) {
+ repDistance = thisDistance;
+ bestRep = thisRep;
+ }
+ }
+
+ if (!bestRep) {
+ bestRep = [self bestRepresentationForDevice:nil];
+ }
+
+ return bestRep;
+}
+
+- (NSImageRep *)gtm_representationOfSize:(NSSize)size {
+ NSArray *reps = [self representations];
+
+ NSImageRep *thisRep;
+ NSEnumerator *repEnum = [reps objectEnumerator];
+ while ((thisRep = [repEnum nextObject])) {
+ if (NSEqualSizes([thisRep size], size)) {
+ return thisRep;
+ }
+ }
+ return nil;
+}
+
+- (BOOL)gtm_createIconRepresentations {
+ [self setFlipped:NO];
+ [self gtm_createRepresentationOfSize:NSMakeSize(16, 16)];
+ [self gtm_createRepresentationOfSize:NSMakeSize(32, 32)];
+ [self setScalesWhenResized:NO];
+ return YES;
+}
+
+- (BOOL)gtm_createRepresentationOfSize:(NSSize)size {
+ if ([self gtm_representationOfSize:size]) {
+ return NO;
+ }
+
+ NSBitmapImageRep *bestRep =
+ (NSBitmapImageRep *)[self gtm_bestRepresentationForSize:size];
+
+ NSRect drawRect = GTMNSScaleRectToRect(GTMNSRectOfSize([bestRep size]),
+ GTMNSRectOfSize(size),
+ GTMScaleProportionally,
+ GTMRectAlignCenter);
+
+ if ([bestRep respondsToSelector:@selector(CGImage)]) {
+ CGImageRef imageRef = (CGImageRef)[bestRep performSelector:@selector(CGImage)];
+
+ CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();
+ if (!cspace) return NO;
+
+ CGContextRef smallContext =
+ CGBitmapContextCreate(NULL,
+ size.width,
+ size.height,
+ 8, // bits per component
+ size.width * 4, // bytes per pixel
+ cspace,
+ kCGBitmapByteOrder32Host
+ | kCGImageAlphaPremultipliedLast);
+ CFRelease(cspace);
+
+ if (!smallContext) return NO;
+
+
+ CGContextDrawImage(smallContext, GTMNSRectToCGRect(drawRect), imageRef);
+
+ CGImageRef smallImage = CGBitmapContextCreateImage(smallContext);
+
+ if (smallImage) {
+ NSBitmapImageRep *cgRep =
+ [[[NSBitmapImageRep alloc] initWithCGImage:smallImage] autorelease];
+ [self addRepresentation:cgRep];
+ CGImageRelease(smallImage);
+ } else {
+ CGContextRelease(smallContext);
+ return NO;
+ }
+ CGContextRelease(smallContext);
+ return YES;
+ } else {
+ // This functionality is here to allow it to work under Tiger
+ // It can probably only be called safely from the main thread
+ NSImage* scaledImage = [[NSImage alloc] initWithSize:size];
+ [scaledImage lockFocus];
+ NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
+ [graphicsContext setImageInterpolation:NSImageInterpolationHigh];
+ [graphicsContext setShouldAntialias:YES];
+ [bestRep drawInRect:drawRect];
+ NSBitmapImageRep* iconRep =
+ [[[NSBitmapImageRep alloc] initWithFocusedViewRect:
+ NSMakeRect(0, 0, size.width, size.height)] autorelease];
+ [scaledImage unlockFocus];
+ [scaledImage release];
+ [self addRepresentation:iconRep];
+ return YES;
+ }
+ return NO;
+}
+
+- (void)gtm_removeRepresentationsLargerThanSize:(NSSize)size {
+ NSEnumerator *e = [[self representations] reverseObjectEnumerator];
+ NSImageRep *thisRep;
+ while((thisRep = [e nextObject]) ) {
+ if ([thisRep size].width > size.width
+ && [thisRep size].height > size.height)
+ [self removeRepresentation:thisRep];
+ }
+}
+
+- (NSImage *)gtm_duplicateOfSize:(NSSize)size {
+ NSImage *duplicate = [[self copy] autorelease];
+ [duplicate gtm_shrinkToSize:size];
+ [duplicate setFlipped:NO];
+ return duplicate;
+}
+
+- (void)gtm_shrinkToSize:(NSSize)size {
+ [self gtm_createRepresentationOfSize:size];
+ [self setSize:size];
+ [self gtm_removeRepresentationsLargerThanSize:size];
+}
+@end
diff --git a/AppKit/GTMNSImage+ScalingTest.m b/AppKit/GTMNSImage+ScalingTest.m
new file mode 100644
index 0000000..ec17cc5
--- /dev/null
+++ b/AppKit/GTMNSImage+ScalingTest.m
@@ -0,0 +1,47 @@
+//
+// GTMNSImage+ScalingTest.m
+//
+// Copyright 2006-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 <Cocoa/Cocoa.h>
+
+#import "GTMSenTestCase.h"
+
+#import "GTMNSImage+Scaling.h"
+
+@interface GTMNSImage_ScalingTest : GTMTestCase
+@end
+
+@implementation GTMNSImage_ScalingTest
+
+- (void)testScaling {
+ NSImage *testImage = [NSImage imageNamed:@"NSApplicationIcon"];
+
+ NSImageRep *rep = nil;
+ rep = [testImage gtm_bestRepresentationForSize:NSMakeSize(99, 99)];
+ STAssertTrue(NSEqualSizes([rep size], NSMakeSize(128, 128)), nil);
+
+ [testImage gtm_createIconRepresentations];
+ STAssertNotNil([testImage gtm_representationOfSize:NSMakeSize(16, 16)], nil);
+ STAssertNotNil([testImage gtm_representationOfSize:NSMakeSize(32, 32)], nil);
+
+ NSImage *duplicate = [testImage gtm_duplicateOfSize: NSMakeSize(48, 48)];
+ rep = [duplicate gtm_bestRepresentationForSize:NSMakeSize(50, 50)];
+ STAssertTrue(NSEqualSizes([rep size], NSMakeSize(48, 48)), nil);
+
+}
+
+@end
diff --git a/DebugUtils/GTMMethodCheckTest.m b/DebugUtils/GTMMethodCheckTest.m
index 91b7300..7b9fa0c 100644
--- a/DebugUtils/GTMMethodCheckTest.m
+++ b/DebugUtils/GTMMethodCheckTest.m
@@ -45,5 +45,9 @@ GTM_METHOD_CHECK(GTMMethodCheckTest, GTMMethodCheckTestClassMethod); // COV_NF_
// GTMMethodCheck only runs in debug
STAssertTrue(gTestCheckVar, @"Should be true");
#endif
+
+ // Next two calls just verify our code coverage
+ [self GTMMethodCheckTestMethod];
+ [[self class] GTMMethodCheckTestClassMethod];
}
@end
diff --git a/Foundation/GTMExceptionalInlines.h b/Foundation/GTMExceptionalInlines.h
new file mode 100644
index 0000000..25635c1
--- /dev/null
+++ b/Foundation/GTMExceptionalInlines.h
@@ -0,0 +1,52 @@
+//
+// GTMExceptionalInlines.h
+//
+// 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 <Foundation/Foundation.h>
+#import "GTMDefines.h"
+
+// This file exists because when you have full warnings on you can run into
+// troubles with functions that Apple has inlined that have structures or
+// local variables defined in them.
+// You only see this warning if you have -Wuninitialized turned on,
+// and you will only see them in release mode. -Wno-unitialized turns them
+// off, but you also lose all the good warnings that come with -Wuninitialized.
+// If you have the inline versions of any of the functions below in a
+// @syncronized, or @try block, you will get
+// warning: variable 'r' might be clobbered by 'longjmp' or 'vfork'
+// By moving this local vars "out of line" you fix the problem.
+// These functions do nothing more than act as "out of line" calls to the
+// functions they are masking to avoid the warning.
+// If you run into others, feel free to add them.
+
+// Please only use these to avoid the warning above. Use the Apple defined
+// functions where possible.
+
+FOUNDATION_EXPORT NSRange GTMNSMakeRange(NSUInteger loc, NSUInteger len);
+
+FOUNDATION_EXPORT CGPoint GTMCGPointMake(CGFloat x, CGFloat y);
+FOUNDATION_EXPORT CGSize GTMCGSizeMake(CGFloat width, CGFloat height);
+FOUNDATION_EXPORT CGRect GTMCGRectMake(CGFloat x, CGFloat y,
+ CGFloat width, CGFloat height);
+
+#if !GTM_IPHONE_SDK
+// iPhone does not have NSTypes defined, only CGTypes. So no NSRect, NSPoint etc.
+FOUNDATION_EXPORT NSPoint GTMNSMakePoint(CGFloat x, CGFloat y);
+FOUNDATION_EXPORT NSSize GTMNSMakeSize(CGFloat w, CGFloat h);
+FOUNDATION_EXPORT NSRect GTMNSMakeRect(CGFloat x, CGFloat y,
+ CGFloat w, CGFloat h);
+#endif
diff --git a/Foundation/GTMExceptionalInlines.m b/Foundation/GTMExceptionalInlines.m
new file mode 100644
index 0000000..120e235
--- /dev/null
+++ b/Foundation/GTMExceptionalInlines.m
@@ -0,0 +1,52 @@
+//
+// GTMExceptionalInlines.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 "GTMExceptionalInlines.h"
+
+NSRange GTMNSMakeRange(NSUInteger loc, NSUInteger len) {
+ return NSMakeRange(loc, len);
+}
+
+CGPoint GTMCGPointMake(CGFloat x, CGFloat y) {
+ return CGPointMake(x, y);
+}
+
+CGSize GTMCGSizeMake(CGFloat width, CGFloat height) {
+ return CGSizeMake(width, height);
+}
+
+CGRect GTMCGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height) {
+ return CGRectMake(x, y, width, height);
+}
+
+#if !GTM_IPHONE_SDK
+// iPhone does not have NSTypes defined, only CGTypes. So no NSRect, NSPoint etc.
+
+NSPoint GTMNSMakePoint(CGFloat x, CGFloat y) {
+ return NSMakePoint(x, y);
+}
+
+NSSize GTMNSMakeSize(CGFloat w, CGFloat h) {
+ return NSMakeSize(w, h);
+}
+
+NSRect GTMNSMakeRect(CGFloat x, CGFloat y, CGFloat w, CGFloat h) {
+ return NSMakeRect(x, y, w, h);
+}
+
+#endif
diff --git a/Foundation/GTMExceptionalInlinesTest.m b/Foundation/GTMExceptionalInlinesTest.m
new file mode 100644
index 0000000..f785301
--- /dev/null
+++ b/Foundation/GTMExceptionalInlinesTest.m
@@ -0,0 +1,65 @@
+//
+// GTMExceptionalInlinesTest.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 "GTMSenTestCase.h"
+#import "GTMExceptionalInlines.h"
+
+@interface GTMExceptionalInlinesTest : GTMTestCase
+@end
+
+@implementation GTMExceptionalInlinesTest
+- (void)testExceptionalInlines {
+ // Numbers chosen basically at random.
+ NSUInteger loc = 5;
+ NSUInteger len = 10;
+ CGFloat x = 22.5;
+ CGFloat y = 40.2;
+ CGFloat h = 21.6;
+ CGFloat w = 54.2;
+
+ NSRange range1 = GTMNSMakeRange(loc, len);
+ NSRange range2 = NSMakeRange(loc, len);
+ STAssertTrue(NSEqualRanges(range1, range2), nil);
+
+ CGPoint cgpoint1 = GTMCGPointMake(x, y);
+ CGPoint cgpoint2 = CGPointMake(x, y);
+ STAssertTrue(CGPointEqualToPoint(cgpoint1, cgpoint2), nil);
+
+ CGSize cgsize1 = GTMCGSizeMake(x, y);
+ CGSize cgsize2 = CGSizeMake(x, y);
+ STAssertTrue(CGSizeEqualToSize(cgsize1, cgsize2), nil);
+
+ CGRect cgrect1 = GTMCGRectMake(x, y, w, h);
+ CGRect cgrect2 = CGRectMake(x, y, w, h);
+ STAssertTrue(CGRectEqualToRect(cgrect1, cgrect2), nil);
+
+#if !GTM_IPHONE_SDK
+ NSPoint point1 = GTMNSMakePoint(x, y);
+ NSPoint point2 = NSMakePoint(x, y);
+ STAssertTrue(NSEqualPoints(point1, point2), nil);
+
+ NSSize size1 = GTMNSMakeSize(w, h);
+ NSSize size2 = NSMakeSize(w, h);
+ STAssertTrue(NSEqualSizes(size1, size2), nil);
+
+ NSRect rect1 = GTMNSMakeRect(x, y, w, h);
+ NSRect rect2 = NSMakeRect(x, y, w, h);
+ STAssertTrue(NSEqualRects(rect1, rect2), nil);
+#endif
+}
+@end
diff --git a/Foundation/GTMGeometryUtils.h b/Foundation/GTMGeometryUtils.h
index 9691ffc..855b97f 100644
--- a/Foundation/GTMGeometryUtils.h
+++ b/Foundation/GTMGeometryUtils.h
@@ -25,7 +25,8 @@
enum {
GTMScaleProportionally = 0, // Fit proportionally
GTMScaleToFit, // Forced fit (distort if necessary)
- GTMScaleNone // Don't scale (clip)
+ GTMScaleNone, // Don't scale (clip)
+ GTMScaleToFillProportionally = 101 // Scale proportionally to fill area
};
typedef NSUInteger GTMScaling;
@@ -398,6 +399,27 @@ CG_INLINE NSRect GTMNSAlignRectangles(NSRect alignee, NSRect aligner,
alignment));
}
+/// Align a rectangle to another
+//
+// Args:
+// scalee - rect to be scaled
+// scaler - rect to scale to
+// scaling - way to scale the rectangle
+// alignment - way to align the scaled rectangle
+CG_INLINE NSRect GTMNSScaleRectToRect(NSRect scalee,
+ NSRect scaler,
+ GTMScaling scaling,
+ GTMRectAlignment alignment) {
+
+ return GTMCGRectToNSRect(
+ GTMCGAlignRectangles(
+ GTMCGScaleRectangleToSize(GTMNSRectToCGRect(scalee),
+ GTMNSSizeToCGSize(scaler.size),
+ scaling),
+ GTMNSRectToCGRect(scaler),
+ alignment));
+}
+
/// Scale rectangle
//
// Args:
diff --git a/Foundation/GTMGeometryUtils.m b/Foundation/GTMGeometryUtils.m
index 9ac2933..07de80c 100644
--- a/Foundation/GTMGeometryUtils.m
+++ b/Foundation/GTMGeometryUtils.m
@@ -78,6 +78,8 @@ CGRect GTMCGAlignRectangles(CGRect alignee, CGRect aligner, GTMRectAlignment ali
CGRect GTMCGScaleRectangleToSize(CGRect scalee, CGSize size, GTMScaling scaling) {
switch (scaling) {
+
+ case GTMScaleToFillProportionally:
case GTMScaleProportionally: {
CGFloat height = CGRectGetHeight(scalee);
CGFloat width = CGRectGetWidth(scalee);
@@ -85,12 +87,14 @@ CGRect GTMCGScaleRectangleToSize(CGRect scalee, CGSize size, GTMScaling scaling)
(height > size.height || width > size.width)) {
CGFloat horiz = size.width / width;
CGFloat vert = size.height / height;
- CGFloat newScale = horiz < vert ? horiz : vert;
+ BOOL expand = (scaling == GTMScaleToFillProportionally);
+ // We use the smaller scale unless expand is true. In that case, larger.
+ CGFloat newScale = ((horiz < vert) ^ expand) ? horiz : vert;
scalee = GTMCGRectScale(scalee, newScale, newScale);
}
break;
}
-
+
case GTMScaleToFit:
scalee.size = size;
break;
diff --git a/Foundation/GTMGeometryUtilsTest.m b/Foundation/GTMGeometryUtilsTest.m
index 5a59c8b..38b86fb 100644
--- a/Foundation/GTMGeometryUtilsTest.m
+++ b/Foundation/GTMGeometryUtilsTest.m
@@ -164,6 +164,31 @@
STAssertEquals(result, rect, nil);
}
+
+- (void)testGTMNSScaleRectToRect {
+ typedef struct {
+ NSRect expectedRect;
+ GTMScaling scaling;
+ GTMRectAlignment alignment;
+ } TestData;
+
+ NSRect rect1 = NSMakeRect(0, 0, 4, 4);
+ NSRect rect2 = NSMakeRect(0, 0, 2, 1);
+
+ TestData data[] = {
+ { NSMakeRect(2, 3, 2, 1), GTMScaleToFillProportionally, GTMRectAlignTopRight },
+ { NSMakeRect(0, 0, 4, 4), GTMScaleToFit, GTMRectAlignCenter },
+ { NSMakeRect(1, 1.5, 2, 1), GTMScaleNone, GTMRectAlignCenter },
+ { NSMakeRect(1, 0, 2, 1), GTMScaleProportionally, GTMRectAlignBottom },
+ };
+
+ for (size_t i = 0; i < sizeof(data) / sizeof(TestData); i++) {
+ NSRect outRect = GTMNSScaleRectToRect(rect2, rect1, data[i].scaling, data[i].alignment);
+ STAssertEquals(outRect, data[i].expectedRect, nil);
+ }
+}
+
+
- (void)testGTMNSDistanceBetweenPoints {
NSPoint pt1 = NSMakePoint(0, 0);
NSPoint pt2 = NSMakePoint(3, 4);
diff --git a/Foundation/GTMHTTPFetcher.m b/Foundation/GTMHTTPFetcher.m
index a73d752..614f3ad 100644
--- a/Foundation/GTMHTTPFetcher.m
+++ b/Foundation/GTMHTTPFetcher.m
@@ -697,6 +697,7 @@ CannotBeginFetch:
{ nil, 0 }
};
+ BOOL isGood = NO;
// NSError's isEqual always returns false for equal but distinct instances
// of NSError, so we have to compare the domain and code values explicitly
@@ -705,10 +706,11 @@ CannotBeginFetch:
if ([[error domain] isEqual:retries[idx].domain]
&& [error code] == retries[idx].code) {
- return YES;
+ isGood = YES;
+ break;
}
}
- return NO;
+ return isGood;
}
@@ -927,11 +929,9 @@ CannotBeginFetch:
cookieStorageMethod_ = method;
- if (method == kGTMHTTPFetcherCookieStorageMethodSystemDefault) {
- [request_ setHTTPShouldHandleCookies:YES];
- } else {
- [request_ setHTTPShouldHandleCookies:NO];
- }
+ BOOL handleCookies
+ = method == kGTMHTTPFetcherCookieStorageMethodSystemDefault ? YES : NO;
+ [request_ setHTTPShouldHandleCookies:handleCookies];
}
- (id)delegate {
@@ -973,12 +973,10 @@ CannotBeginFetch:
- (void)setFetchHistory:(NSMutableDictionary *)fetchHistory {
[fetchHistory_ autorelease];
fetchHistory_ = [fetchHistory retain];
-
- if (fetchHistory_ != nil) {
- [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodFetchHistory];
- } else {
- [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodStatic];
- }
+ GTMHTTPFetcherCookieStorageMethod method
+ = fetchHistory_ ? kGTMHTTPFetcherCookieStorageMethodFetchHistory
+ : kGTMHTTPFetcherCookieStorageMethodStatic;
+ [self setCookieStorageMethod:method];
}
- (void)setShouldCacheDatedData:(BOOL)flag {
diff --git a/Foundation/GTMLoggerTest.m b/Foundation/GTMLoggerTest.m
index 9093c7a..043c73d 100644
--- a/Foundation/GTMLoggerTest.m
+++ b/Foundation/GTMLoggerTest.m
@@ -94,12 +94,20 @@
@"GTMLoggerUnitTest.log"] retain];
STAssertNotNil(path_, nil);
// Make sure we're cleaned up from the last run
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
[[NSFileManager defaultManager] removeFileAtPath:path_ handler:nil];
+#else
+ [[NSFileManager defaultManager] removeItemAtPath:path_ error:NULL];
+#endif
}
- (void)tearDown {
STAssertNotNil(path_, nil);
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
[[NSFileManager defaultManager] removeFileAtPath:path_ handler:nil];
+#else
+ [[NSFileManager defaultManager] removeItemAtPath:path_ error:NULL];
+#endif
[path_ release];
path_ = nil;
}
@@ -372,8 +380,8 @@
STAssertNotNil(fmtr, nil);
// E.g. 2008-01-04 09:16:26.906 otest[5567/0xa07d0f60] [lvl=1] (no func) test
- static NSString *const kFormatBasePattern =
- @"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3} otest\\[[0-9]+/0x[0-9a-f]+\\] \\[lvl=[0-3]\\] \\(no func\\) ";
+ NSString * kFormatBasePattern =
+ @"[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3} ((otest)|(GTMiPhoneTest))\\[[0-9]+/0x[0-9a-f]+\\] \\[lvl=[0-3]\\] \\(no func\\) ";
NSString *msg = nil;
msg = [self stringFromFormatter:fmtr
diff --git a/Foundation/GTMNSData+zlibTest.m b/Foundation/GTMNSData+zlibTest.m
index 1b6264c..0d86886 100644
--- a/Foundation/GTMNSData+zlibTest.m
+++ b/Foundation/GTMNSData+zlibTest.m
@@ -25,13 +25,64 @@
@interface GTMNSData_zlibTest : GTMTestCase
@end
-
-static void FillWithRandom(char *data, unsigned long len) {
- char *max = data + len;
- for ( ; data < max ; ++data) {
- *data = random() & 0xFF;
- }
-}
+// NOTE: we don't need to test the actually compressor/inflation (we're using
+// zlib, it works), we just need to test our wrapper. So we can use canned
+// data, etc. (and yes, when using random data, things once failed because
+// we generated a random block of data that was valid compressed data?!)
+
+static unsigned char randomDataLarge[] = {
+ // openssl rand -rand /dev/random 512 | xxd -i
+ 0xe1, 0xa6, 0xe2, 0xa2, 0x0b, 0xf7, 0x8d, 0x6b, 0x31, 0xfe, 0xaa, 0x64,
+ 0x50, 0xbe, 0x52, 0x7e, 0x83, 0x74, 0x00, 0x8f, 0x62, 0x96, 0xc7, 0xe9,
+ 0x20, 0x59, 0x78, 0xc6, 0xea, 0x10, 0xd5, 0xdb, 0x3f, 0x6b, 0x13, 0xd9,
+ 0x44, 0x18, 0x24, 0x17, 0x63, 0xc8, 0x74, 0xa5, 0x37, 0x6c, 0x9c, 0x00,
+ 0xe5, 0xcf, 0x0a, 0xdf, 0xb9, 0x66, 0xb1, 0xbd, 0x04, 0x8f, 0x55, 0x9e,
+ 0xb0, 0x24, 0x4e, 0xf0, 0xc4, 0x69, 0x2c, 0x1f, 0x63, 0x9f, 0x41, 0xa8,
+ 0x89, 0x9b, 0x98, 0x00, 0xb6, 0x78, 0xf7, 0xe4, 0x4c, 0x72, 0x14, 0x84,
+ 0xaa, 0x3d, 0xc1, 0x42, 0x9f, 0x12, 0x85, 0xdd, 0x16, 0x8b, 0x8f, 0x67,
+ 0xe0, 0x26, 0x5b, 0x5e, 0xaa, 0xe7, 0xd3, 0x67, 0xfe, 0x21, 0x77, 0xa6,
+ 0x52, 0xde, 0x33, 0x8b, 0x96, 0x49, 0x6a, 0xd6, 0x58, 0x58, 0x36, 0x00,
+ 0x23, 0xd2, 0x45, 0x13, 0x9f, 0xd9, 0xc7, 0x2d, 0x55, 0x12, 0xb4, 0xfe,
+ 0x53, 0x27, 0x1f, 0x14, 0x71, 0x9b, 0x7e, 0xcc, 0x5e, 0x8c, 0x59, 0xef,
+ 0x80, 0xac, 0x89, 0xf4, 0x45, 0x8d, 0x98, 0x6d, 0x97, 0xfd, 0x53, 0x5f,
+ 0x19, 0xd6, 0x11, 0xf7, 0xcb, 0x5d, 0xca, 0xab, 0xe1, 0x01, 0xf1, 0xe9,
+ 0x1f, 0x1f, 0xf3, 0x53, 0x76, 0xa2, 0x59, 0x8e, 0xb3, 0x91, 0xff, 0xe8,
+ 0x1b, 0xc0, 0xc0, 0xda, 0xdd, 0x93, 0xb5, 0x9d, 0x62, 0x13, 0xb8, 0x07,
+ 0xf2, 0xf5, 0xb9, 0x4b, 0xe1, 0x09, 0xed, 0xdb, 0xe6, 0xd9, 0x2d, 0xc4,
+ 0x0d, 0xb6, 0xbd, 0xfc, 0xdb, 0x5c, 0xcc, 0xf6, 0x53, 0x4e, 0x01, 0xa4,
+ 0x03, 0x95, 0x4a, 0xa4, 0xaa, 0x4f, 0x45, 0xaf, 0xbf, 0xf1, 0x7e, 0x60,
+ 0x1d, 0x86, 0x93, 0x65, 0x7b, 0x24, 0x0c, 0x09, 0xe0, 0xd1, 0xd8, 0x60,
+ 0xd9, 0xd9, 0x55, 0x2a, 0xec, 0xd5, 0xdc, 0xd0, 0xc6, 0x5e, 0x2c, 0x22,
+ 0xf5, 0x19, 0x0b, 0xc3, 0xa1, 0x38, 0x11, 0x67, 0x6f, 0x6c, 0x0e, 0x34,
+ 0x44, 0x83, 0xee, 0xd3, 0xf2, 0x4b, 0x7b, 0x03, 0x68, 0xfe, 0xc5, 0x76,
+ 0xb2, 0x2e, 0x26, 0xeb, 0x1f, 0x66, 0x02, 0xa4, 0xd9, 0xda, 0x28, 0x3a,
+ 0xc3, 0x94, 0x03, 0xe8, 0x29, 0x7e, 0xfe, 0x3d, 0xc8, 0xc1, 0x0a, 0x74,
+ 0xc7, 0xaf, 0xa6, 0x84, 0x86, 0x85, 0xc3, 0x8c, 0x00, 0x38, 0xd4, 0xb5,
+ 0xb2, 0xe0, 0xf0, 0xc4, 0x8d, 0x10, 0x0d, 0xf1, 0xcd, 0x05, 0xdb, 0xd0,
+ 0xcf, 0x17, 0x4f, 0xa8, 0xe5, 0xf0, 0x53, 0x55, 0x62, 0xc7, 0x55, 0xe5,
+ 0xbe, 0x18, 0x2f, 0xda, 0x48, 0xf1, 0xaa, 0x85, 0x46, 0x80, 0x15, 0x70,
+ 0x82, 0xd2, 0xa6, 0xb0, 0x3d, 0x31, 0xb5, 0xcc, 0x23, 0x95, 0x5e, 0x15,
+ 0x35, 0x32, 0xd0, 0x86, 0xd1, 0x6e, 0x2d, 0xc0, 0xfe, 0x45, 0xae, 0x28,
+ 0x24, 0xa7, 0x14, 0xf4, 0xe9, 0xb5, 0x6f, 0xac, 0x25, 0xf9, 0x88, 0xf6,
+ 0x60, 0x5d, 0x6b, 0x5c, 0xf2, 0x38, 0xe8, 0xdc, 0xbd, 0xa6, 0x13, 0xc0,
+ 0xa4, 0xc8, 0xe9, 0x7a, 0xc6, 0xb6, 0x88, 0x26, 0x98, 0x9f, 0xe3, 0x9a,
+ 0xd9, 0x5b, 0xd4, 0xd0, 0x02, 0x1f, 0x55, 0x30, 0xbe, 0xde, 0x9c, 0xd1,
+ 0x53, 0x93, 0x72, 0xe6, 0x19, 0x79, 0xe9, 0xf1, 0x70, 0x78, 0x92, 0x31,
+ 0xf6, 0x17, 0xc0, 0xdd, 0x99, 0xc8, 0x97, 0x67, 0xdc, 0xf6, 0x67, 0x6b,
+ 0x9b, 0x1c, 0x90, 0xea, 0x1a, 0x9e, 0x26, 0x68, 0xc2, 0x13, 0x94, 0x3a,
+ 0x3e, 0x73, 0x61, 0x4e, 0x37, 0xa8, 0xa1, 0xfa, 0xf8, 0x22, 0xdd, 0x20,
+ 0x40, 0xc6, 0x52, 0x27, 0x47, 0x1a, 0x79, 0xfa, 0x40, 0xa6, 0x62, 0x6b,
+ 0xe6, 0xc7, 0x67, 0xb7, 0xa8, 0x2d, 0xd1, 0x9f, 0x17, 0xb8, 0x77, 0x5e,
+ 0x97, 0x1e, 0x92, 0xd7, 0xd2, 0x25, 0x04, 0x92, 0xf9, 0x41, 0x70, 0x93,
+ 0xe1, 0x13, 0x07, 0x94, 0x8e, 0x0b, 0x82, 0x98
+};
+
+static unsigned char randomDataSmall[] = {
+ // openssl rand -rand /dev/random 24 | xxd -i
+ 0xd1, 0xec, 0x35, 0xc3, 0xa0, 0x4c, 0x73, 0x37, 0x2f, 0x5a, 0x12, 0x44,
+ 0xee, 0xe4, 0x22, 0x07, 0x29, 0xa8, 0x4a, 0xde, 0xc8, 0xbb, 0xe7, 0xdb
+};
+
static BOOL HasGzipHeader(NSData *data) {
// very simple check
@@ -43,20 +94,11 @@ static BOOL HasGzipHeader(NSData *data) {
@implementation GTMNSData_zlibTest
-- (void)setUp {
- // seed random from /dev/random
- srandomdev();
-}
-
- (void)testBoundryValues {
- NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
- STAssertNotNil(localPool, @"failed to alloc local pool");
-
// build some test data
- NSMutableData *data = [NSMutableData data];
+ NSData *data = [NSData dataWithBytes:randomDataLarge
+ length:sizeof(randomDataLarge)];
STAssertNotNil(data, @"failed to alloc data block");
- [data setLength:512];
- FillWithRandom([data mutableBytes], [data length]);
// bogus args to start
STAssertNil([NSData gtm_dataByDeflatingData:nil], nil);
@@ -107,29 +149,28 @@ static BOOL HasGzipHeader(NSData *data) {
STAssertNil([NSData gtm_dataByInflatingData:data], nil);
// test deflated data runs that end before they are done
- [GTMUnitTestDevLog expect:[deflated length] - 1
+ [GTMUnitTestDevLog expect:([deflated length] / 11) + 1
casesOfString:@"Error trying to inflate some of the payload, "
"error -5"];
- for (NSUInteger x = 1 ; x < [deflated length] ; ++x) {
+ for (NSUInteger x = 1 ; x < [deflated length] ; x += 11) {
STAssertNil([NSData gtm_dataByInflatingBytes:[deflated bytes]
length:x], nil);
}
// test gzipped data runs that end before they are done
- [GTMUnitTestDevLog expect:[gzipped length] - 1
+ [GTMUnitTestDevLog expect:([gzipped length] / 11) + 1
casesOfString:@"Error trying to inflate some of the payload, "
"error -5"];
- for (NSUInteger x = 1 ; x < [gzipped length] ; ++x) {
+ for (NSUInteger x = 1 ; x < [gzipped length] ; x += 11) {
STAssertNil([NSData gtm_dataByInflatingBytes:[gzipped bytes]
length:x], nil);
}
// test extra data before the deflated/gzipped data (just to make sure we
// don't seek to the "real" data)
- NSMutableData *prefixedDeflated = [NSMutableData data];
+ NSMutableData *prefixedDeflated =
+ [NSMutableData dataWithBytes:randomDataSmall length:sizeof(randomDataSmall)];
STAssertNotNil(prefixedDeflated, @"failed to alloc data block");
- [prefixedDeflated setLength:20];
- FillWithRandom([prefixedDeflated mutableBytes], [prefixedDeflated length]);
[prefixedDeflated appendData:deflated];
[GTMUnitTestDevLog expectString:@"Error trying to inflate some of the "
"payload, error -3"];
@@ -139,10 +180,9 @@ static BOOL HasGzipHeader(NSData *data) {
STAssertNil([NSData gtm_dataByInflatingBytes:[prefixedDeflated bytes]
length:[prefixedDeflated length]],
nil);
- NSMutableData *prefixedGzipped = [NSMutableData data];
+ NSMutableData *prefixedGzipped =
+ [NSMutableData dataWithBytes:randomDataSmall length:sizeof(randomDataSmall)];
STAssertNotNil(prefixedDeflated, @"failed to alloc data block");
- [prefixedGzipped setLength:20];
- FillWithRandom([prefixedGzipped mutableBytes], [prefixedGzipped length]);
[prefixedGzipped appendData:gzipped];
[GTMUnitTestDevLog expectString:@"Error trying to inflate some of the "
"payload, error -3"];
@@ -179,171 +219,141 @@ static BOOL HasGzipHeader(NSData *data) {
STAssertNil([NSData gtm_dataByInflatingBytes:[suffixedGZipped bytes]
length:[suffixedGZipped length]],
nil);
-
- [localPool release];
}
- (void)testInflateDeflate {
- // generate a range of sizes w/ random content
- for (int n = 0 ; n < 2 ; ++n) {
- for (int x = 1 ; x < 128 ; ++x) {
- NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
- STAssertNotNil(localPool, @"failed to alloc local pool");
-
- NSMutableData *data = [NSMutableData data];
- STAssertNotNil(data, @"failed to alloc data block");
-
- // first pass small blocks, second pass, larger ones, but second pass
- // avoid making them multimples of 128.
- [data setLength:((n*x*128) + x)];
- FillWithRandom([data mutableBytes], [data length]);
-
- // w/ *Bytes apis, default level
- NSData *deflated = [NSData gtm_dataByDeflatingBytes:[data bytes]
- length:[data length]];
- STAssertNotNil(deflated, @"failed to deflate data block");
- STAssertGreaterThan([deflated length],
- (NSUInteger)0, @"failed to deflate data block");
- STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data");
- NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes]
- length:[deflated length]];
- STAssertNotNil(dataPrime, @"failed to inflate data block");
- STAssertGreaterThan([dataPrime length],
- (NSUInteger)0, @"failed to inflate data block");
- STAssertEqualObjects(data,
- dataPrime, @"failed to round trip via *Bytes apis");
-
- // w/ *Data apis, default level
- deflated = [NSData gtm_dataByDeflatingData:data];
- STAssertNotNil(deflated, @"failed to deflate data block");
- STAssertGreaterThan([deflated length],
- (NSUInteger)0, @"failed to deflate data block");
- STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data");
- dataPrime = [NSData gtm_dataByInflatingData:deflated];
- STAssertNotNil(dataPrime, @"failed to inflate data block");
- STAssertGreaterThan([dataPrime length],
- (NSUInteger)0, @"failed to inflate data block");
- STAssertEqualObjects(data,
- dataPrime, @"failed to round trip via *Data apis");
-
- // loop over the compression levels
- for (int level = 1 ; level < 9 ; ++level) {
- // w/ *Bytes apis, using our level
- deflated = [NSData gtm_dataByDeflatingBytes:[data bytes]
- length:[data length]
- compressionLevel:level];
- STAssertNotNil(deflated, @"failed to deflate data block");
- STAssertGreaterThan([deflated length],
- (NSUInteger)0, @"failed to deflate data block");
- STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data");
- dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes]
- length:[deflated length]];
- STAssertNotNil(dataPrime, @"failed to inflate data block");
- STAssertGreaterThan([dataPrime length],
- (NSUInteger)0, @"failed to inflate data block");
- STAssertEqualObjects(data,
- dataPrime, @"failed to round trip via *Bytes apis");
-
- // w/ *Data apis, using our level
- deflated = [NSData gtm_dataByDeflatingData:data compressionLevel:level];
- STAssertNotNil(deflated, @"failed to deflate data block");
- STAssertGreaterThan([deflated length],
- (NSUInteger)0, @"failed to deflate data block");
- STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data");
- dataPrime = [NSData gtm_dataByInflatingData:deflated];
- STAssertNotNil(dataPrime, @"failed to inflate data block");
- STAssertGreaterThan([dataPrime length],
- (NSUInteger)0, @"failed to inflate data block");
- STAssertEqualObjects(data,
- dataPrime, @"failed to round trip via *Data apis");
- }
-
- [localPool release];
- }
+ NSData *data = [NSData dataWithBytes:randomDataLarge
+ length:sizeof(randomDataLarge)];
+ STAssertNotNil(data, @"failed to alloc data block");
+
+ // w/ *Bytes apis, default level
+ NSData *deflated = [NSData gtm_dataByDeflatingBytes:[data bytes]
+ length:[data length]];
+ STAssertNotNil(deflated, @"failed to deflate data block");
+ STAssertGreaterThan([deflated length],
+ (NSUInteger)0, @"failed to deflate data block");
+ STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data");
+ NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes]
+ length:[deflated length]];
+ STAssertNotNil(dataPrime, @"failed to inflate data block");
+ STAssertGreaterThan([dataPrime length],
+ (NSUInteger)0, @"failed to inflate data block");
+ STAssertEqualObjects(data,
+ dataPrime, @"failed to round trip via *Bytes apis");
+
+ // w/ *Data apis, default level
+ deflated = [NSData gtm_dataByDeflatingData:data];
+ STAssertNotNil(deflated, @"failed to deflate data block");
+ STAssertGreaterThan([deflated length],
+ (NSUInteger)0, @"failed to deflate data block");
+ STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data");
+ dataPrime = [NSData gtm_dataByInflatingData:deflated];
+ STAssertNotNil(dataPrime, @"failed to inflate data block");
+ STAssertGreaterThan([dataPrime length],
+ (NSUInteger)0, @"failed to inflate data block");
+ STAssertEqualObjects(data,
+ dataPrime, @"failed to round trip via *Data apis");
+
+ // loop over the compression levels
+ for (int level = 1 ; level < 9 ; ++level) {
+ // w/ *Bytes apis, using our level
+ deflated = [NSData gtm_dataByDeflatingBytes:[data bytes]
+ length:[data length]
+ compressionLevel:level];
+ STAssertNotNil(deflated, @"failed to deflate data block");
+ STAssertGreaterThan([deflated length],
+ (NSUInteger)0, @"failed to deflate data block");
+ STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data");
+ dataPrime = [NSData gtm_dataByInflatingBytes:[deflated bytes]
+ length:[deflated length]];
+ STAssertNotNil(dataPrime, @"failed to inflate data block");
+ STAssertGreaterThan([dataPrime length],
+ (NSUInteger)0, @"failed to inflate data block");
+ STAssertEqualObjects(data,
+ dataPrime, @"failed to round trip via *Bytes apis");
+
+ // w/ *Data apis, using our level
+ deflated = [NSData gtm_dataByDeflatingData:data compressionLevel:level];
+ STAssertNotNil(deflated, @"failed to deflate data block");
+ STAssertGreaterThan([deflated length],
+ (NSUInteger)0, @"failed to deflate data block");
+ STAssertFalse(HasGzipHeader(deflated), @"has gzip header on zlib data");
+ dataPrime = [NSData gtm_dataByInflatingData:deflated];
+ STAssertNotNil(dataPrime, @"failed to inflate data block");
+ STAssertGreaterThan([dataPrime length],
+ (NSUInteger)0, @"failed to inflate data block");
+ STAssertEqualObjects(data,
+ dataPrime, @"failed to round trip via *Data apis");
}
}
- (void)testInflateGzip {
- // generate a range of sizes w/ random content
- for (int n = 0 ; n < 2 ; ++n) {
- for (int x = 1 ; x < 128 ; ++x) {
- NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
- STAssertNotNil(localPool, @"failed to alloc local pool");
-
- NSMutableData *data = [NSMutableData data];
- STAssertNotNil(data, @"failed to alloc data block");
-
- // first pass small blocks, second pass, larger ones, but second pass
- // avoid making them multimples of 128.
- [data setLength:((n*x*128) + x)];
- FillWithRandom([data mutableBytes], [data length]);
-
- // w/ *Bytes apis, default level
- NSData *gzipped = [NSData gtm_dataByGzippingBytes:[data bytes]
- length:[data length]];
- STAssertNotNil(gzipped, @"failed to gzip data block");
- STAssertGreaterThan([gzipped length],
- (NSUInteger)0, @"failed to gzip data block");
- STAssertTrue(HasGzipHeader(gzipped),
- @"doesn't have gzip header on gzipped data");
- NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes]
- length:[gzipped length]];
- STAssertNotNil(dataPrime, @"failed to inflate data block");
- STAssertGreaterThan([dataPrime length],
- (NSUInteger)0, @"failed to inflate data block");
- STAssertEqualObjects(data,
- dataPrime, @"failed to round trip via *Bytes apis");
-
- // w/ *Data apis, default level
- gzipped = [NSData gtm_dataByGzippingData:data];
- STAssertNotNil(gzipped, @"failed to gzip data block");
- STAssertGreaterThan([gzipped length],
- (NSUInteger)0, @"failed to gzip data block");
- STAssertTrue(HasGzipHeader(gzipped),
- @"doesn't have gzip header on gzipped data");
- dataPrime = [NSData gtm_dataByInflatingData:gzipped];
- STAssertNotNil(dataPrime, @"failed to inflate data block");
- STAssertGreaterThan([dataPrime length],
- (NSUInteger)0, @"failed to inflate data block");
- STAssertEqualObjects(data, dataPrime,
- @"failed to round trip via *Data apis");
-
- // loop over the compression levels
- for (int level = 1 ; level < 9 ; ++level) {
- // w/ *Bytes apis, using our level
- gzipped = [NSData gtm_dataByGzippingBytes:[data bytes]
- length:[data length]
- compressionLevel:level];
- STAssertNotNil(gzipped, @"failed to gzip data block");
- STAssertGreaterThan([gzipped length],
- (NSUInteger)0, @"failed to gzip data block");
- STAssertTrue(HasGzipHeader(gzipped),
- @"doesn't have gzip header on gzipped data");
- dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes]
- length:[gzipped length]];
- STAssertNotNil(dataPrime, @"failed to inflate data block");
- STAssertGreaterThan([dataPrime length],
- (NSUInteger)0, @"failed to inflate data block");
- STAssertEqualObjects(data, dataPrime,
- @"failed to round trip via *Bytes apis");
-
- // w/ *Data apis, using our level
- gzipped = [NSData gtm_dataByGzippingData:data compressionLevel:level];
- STAssertNotNil(gzipped, @"failed to gzip data block");
- STAssertGreaterThan([gzipped length],
- (NSUInteger)0, @"failed to gzip data block");
- STAssertTrue(HasGzipHeader(gzipped),
- @"doesn't have gzip header on gzipped data");
- dataPrime = [NSData gtm_dataByInflatingData:gzipped];
- STAssertNotNil(dataPrime, @"failed to inflate data block");
- STAssertGreaterThan([dataPrime length],
- (NSUInteger)0, @"failed to inflate data block");
- STAssertEqualObjects(data,
- dataPrime, @"failed to round trip via *Data apis");
- }
-
- [localPool release];
- }
+ NSData *data = [NSData dataWithBytes:randomDataLarge
+ length:sizeof(randomDataLarge)];
+ STAssertNotNil(data, @"failed to alloc data block");
+
+ // w/ *Bytes apis, default level
+ NSData *gzipped = [NSData gtm_dataByGzippingBytes:[data bytes]
+ length:[data length]];
+ STAssertNotNil(gzipped, @"failed to gzip data block");
+ STAssertGreaterThan([gzipped length],
+ (NSUInteger)0, @"failed to gzip data block");
+ STAssertTrue(HasGzipHeader(gzipped),
+ @"doesn't have gzip header on gzipped data");
+ NSData *dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes]
+ length:[gzipped length]];
+ STAssertNotNil(dataPrime, @"failed to inflate data block");
+ STAssertGreaterThan([dataPrime length],
+ (NSUInteger)0, @"failed to inflate data block");
+ STAssertEqualObjects(data,
+ dataPrime, @"failed to round trip via *Bytes apis");
+
+ // w/ *Data apis, default level
+ gzipped = [NSData gtm_dataByGzippingData:data];
+ STAssertNotNil(gzipped, @"failed to gzip data block");
+ STAssertGreaterThan([gzipped length],
+ (NSUInteger)0, @"failed to gzip data block");
+ STAssertTrue(HasGzipHeader(gzipped),
+ @"doesn't have gzip header on gzipped data");
+ dataPrime = [NSData gtm_dataByInflatingData:gzipped];
+ STAssertNotNil(dataPrime, @"failed to inflate data block");
+ STAssertGreaterThan([dataPrime length],
+ (NSUInteger)0, @"failed to inflate data block");
+ STAssertEqualObjects(data, dataPrime,
+ @"failed to round trip via *Data apis");
+
+ // loop over the compression levels
+ for (int level = 1 ; level < 9 ; ++level) {
+ // w/ *Bytes apis, using our level
+ gzipped = [NSData gtm_dataByGzippingBytes:[data bytes]
+ length:[data length]
+ compressionLevel:level];
+ STAssertNotNil(gzipped, @"failed to gzip data block");
+ STAssertGreaterThan([gzipped length],
+ (NSUInteger)0, @"failed to gzip data block");
+ STAssertTrue(HasGzipHeader(gzipped),
+ @"doesn't have gzip header on gzipped data");
+ dataPrime = [NSData gtm_dataByInflatingBytes:[gzipped bytes]
+ length:[gzipped length]];
+ STAssertNotNil(dataPrime, @"failed to inflate data block");
+ STAssertGreaterThan([dataPrime length],
+ (NSUInteger)0, @"failed to inflate data block");
+ STAssertEqualObjects(data, dataPrime,
+ @"failed to round trip via *Bytes apis");
+
+ // w/ *Data apis, using our level
+ gzipped = [NSData gtm_dataByGzippingData:data compressionLevel:level];
+ STAssertNotNil(gzipped, @"failed to gzip data block");
+ STAssertGreaterThan([gzipped length],
+ (NSUInteger)0, @"failed to gzip data block");
+ STAssertTrue(HasGzipHeader(gzipped),
+ @"doesn't have gzip header on gzipped data");
+ dataPrime = [NSData gtm_dataByInflatingData:gzipped];
+ STAssertNotNil(dataPrime, @"failed to inflate data block");
+ STAssertGreaterThan([dataPrime length],
+ (NSUInteger)0, @"failed to inflate data block");
+ STAssertEqualObjects(data,
+ dataPrime, @"failed to round trip via *Data apis");
}
}
diff --git a/Foundation/GTMNSString+Replace.h b/Foundation/GTMNSString+Replace.h
index 3a88c80..edc8478 100644
--- a/Foundation/GTMNSString+Replace.h
+++ b/Foundation/GTMNSString+Replace.h
@@ -21,6 +21,9 @@
/// Give easy search-n-replace functionality to NSString.
@interface NSString (GTMStringReplaceAdditions)
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
+// 10.5 has stringByReplacingOccurrencesOfString:withString:, use that directly.
+
/// Returns a new autoreleased string by replacing all occurrences of
// |oldString| with |newString| (case sensitive). If |oldString| is nil or
// @"" nothing is done and |self| is returned. If |newString| is nil, it's
@@ -37,4 +40,6 @@
- (NSString *)gtm_stringByReplacingString:(NSString *)target
withString:(NSString *)replacement;
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050
+
@end
diff --git a/Foundation/GTMNSString+Replace.m b/Foundation/GTMNSString+Replace.m
index 4e8195c..f617945 100644
--- a/Foundation/GTMNSString+Replace.m
+++ b/Foundation/GTMNSString+Replace.m
@@ -21,6 +21,9 @@
@implementation NSString (GTMStringReplaceAdditions)
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
+// 10.5 has stringByReplacingOccurrencesOfString:withString:, use that directly.
+
- (NSString *)gtm_stringByReplacingString:(NSString *)target
withString:(NSString *)replacement {
// If |target| was nil, then do nothing and return |self|
@@ -45,4 +48,6 @@
return result;
}
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050
+
@end
diff --git a/Foundation/GTMNSString+ReplaceTest.m b/Foundation/GTMNSString+ReplaceTest.m
index e814040..805136c 100644
--- a/Foundation/GTMNSString+ReplaceTest.m
+++ b/Foundation/GTMNSString+ReplaceTest.m
@@ -24,6 +24,8 @@
@implementation GTMNSString_ReplaceTest
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
+
- (void)testStringByReplacingStringWithString {
NSString *testString = @"a bc debc gh";
NSString *result;
@@ -52,4 +54,6 @@
@"replacing '' with anything should yield the original string");
}
+#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1050
+
@end
diff --git a/Foundation/GTMPathTest.m b/Foundation/GTMPathTest.m
index 3130c4f..0570bb3 100644
--- a/Foundation/GTMPathTest.m
+++ b/Foundation/GTMPathTest.m
@@ -46,7 +46,11 @@
// Make sure it's safe to remove this directory before nuking it.
STAssertNotNil(testDirectory_, nil);
STAssertNotEqualObjects(testDirectory_, @"/", nil);
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
[[NSFileManager defaultManager] removeFileAtPath:testDirectory_ handler:nil];
+#else
+ [[NSFileManager defaultManager] removeItemAtPath:testDirectory_ error:NULL];
+#endif
}
- (void)testBasicCreation {
diff --git a/Foundation/GTMValidatingContainersTest.m b/Foundation/GTMValidatingContainersTest.m
index ed17327..f9cc377 100644
--- a/Foundation/GTMValidatingContainersTest.m
+++ b/Foundation/GTMValidatingContainersTest.m
@@ -292,6 +292,9 @@
[dictionary removeObjectForKey:@"Key2"];
[dictionary removeObjectForKey:@"Key1"];
STAssertEquals([dictionary count], (NSUInteger)0, @"should have no objects left");
+
+ // So we get full code coverage
+ [testSubClass_ foo];
#if !(GTM_CONTAINERS_VALIDATE && GTM_CONTAINERS_VALIDATION_FAILED_LOG && !GTM_CONTAINERS_VALIDATION_FAILED_ASSERT)
// If we're not validating, we don't expect any logs
[GTMUnitTestDevLog resetExpectedLogs];
diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj
index c51faa8..9fb14d1 100644
--- a/GTM.xcodeproj/project.pbxproj
+++ b/GTM.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 44;
+ objectVersion = 45;
objects = {
/* Begin PBXAggregateTarget section */
@@ -42,13 +42,20 @@
33C374380DD8D44800E97817 /* GTMNSDictionary+URLArguments.h in Headers */ = {isa = PBXBuildFile; fileRef = 33C374360DD8D44800E97817 /* GTMNSDictionary+URLArguments.h */; settings = {ATTRIBUTES = (Public, ); }; };
33C374390DD8D44800E97817 /* GTMNSDictionary+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 33C374370DD8D44800E97817 /* GTMNSDictionary+URLArguments.m */; };
33C3745F0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 33C3745E0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m */; };
+ 7F3EB38E0E5E09C700A7A75E /* GTMNSImage+Scaling.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F3EB38C0E5E09C700A7A75E /* GTMNSImage+Scaling.h */; };
+ 7F3EB38F0E5E09C700A7A75E /* GTMNSImage+Scaling.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F3EB38D0E5E09C700A7A75E /* GTMNSImage+Scaling.m */; };
+ 7F3EB3940E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F3EB3930E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m */; };
+ 7F3EB5540E5F0B0400A7A75E /* GTMUnitTestingImage.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 8BEEA90A0DA7446300894774 /* GTMUnitTestingImage.tiff */; };
+ 7F3EB5870E5F0CBB00A7A75E /* GTMLargeTypeWindowTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1801A40E2533DB00280961 /* GTMLargeTypeWindowTest.m */; };
8B1801A20E2533D500280961 /* GTMLargeTypeWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1801A00E2533D500280961 /* GTMLargeTypeWindow.m */; };
8B1801A30E2533D500280961 /* GTMLargeTypeWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B1801A10E2533D500280961 /* GTMLargeTypeWindow.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 8B1801A50E2533DB00280961 /* GTMLargeTypeWindowTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1801A40E2533DB00280961 /* GTMLargeTypeWindowTest.m */; };
8B1801AE0E25341B00280961 /* GTMLargeTypeWindowImageTest.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B1801A80E25341B00280961 /* GTMLargeTypeWindowImageTest.gtmUTState */; };
8B1801B20E25341B00280961 /* GTMLargeTypeWindowLongTextTest.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B1801AC0E25341B00280961 /* GTMLargeTypeWindowLongTextTest.gtmUTState */; };
8B1801B30E25341B00280961 /* GTMLargeTypeWindowShortTextTest.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B1801AD0E25341B00280961 /* GTMLargeTypeWindowShortTextTest.gtmUTState */; };
8B1802420E25592200280961 /* GTMLargeTypeWindowMediumTextTest.gtmUTState in Resources */ = {isa = PBXBuildFile; fileRef = 8B1802410E25592200280961 /* GTMLargeTypeWindowMediumTextTest.gtmUTState */; };
+ 8B1B49180E5F8E2100A08972 /* GTMExceptionalInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B1B49160E5F8E2100A08972 /* GTMExceptionalInlines.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8B1B49190E5F8E2100A08972 /* GTMExceptionalInlines.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1B49170E5F8E2100A08972 /* GTMExceptionalInlines.m */; };
+ 8B1B49260E5F97C800A08972 /* GTMExceptionalInlinesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B1B491B0E5F904C00A08972 /* GTMExceptionalInlinesTest.m */; };
8B3344210DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3344170DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m */; };
8B3344230DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B33441A0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+HandlerTest.m */; };
8B3344250DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B33441D0DBF7A36009FD32C /* GTMNSAppleEventDescriptor+FoundationTest.m */; };
@@ -291,6 +298,9 @@
33C374360DD8D44800E97817 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = "<group>"; };
33C374370DD8D44800E97817 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = "<group>"; };
33C3745E0DD8D85B00E97817 /* GTMNSDictionary+URLArgumentsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArgumentsTest.m"; sourceTree = "<group>"; };
+ 7F3EB38C0E5E09C700A7A75E /* GTMNSImage+Scaling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSImage+Scaling.h"; sourceTree = "<group>"; };
+ 7F3EB38D0E5E09C700A7A75E /* GTMNSImage+Scaling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSImage+Scaling.m"; sourceTree = "<group>"; };
+ 7F3EB3930E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSImage+ScalingTest.m"; sourceTree = "<group>"; };
8B1801A00E2533D500280961 /* GTMLargeTypeWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLargeTypeWindow.m; sourceTree = "<group>"; };
8B1801A10E2533D500280961 /* GTMLargeTypeWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLargeTypeWindow.h; sourceTree = "<group>"; };
8B1801A40E2533DB00280961 /* GTMLargeTypeWindowTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLargeTypeWindowTest.m; sourceTree = "<group>"; };
@@ -301,6 +311,9 @@
8B1A14E90D900BC800CA1E8E /* GTMNSObject+BindingUnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSObject+BindingUnitTesting.m"; sourceTree = "<group>"; };
8B1A14EA0D900BC800CA1E8E /* GTMNSObject+BindingUnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSObject+BindingUnitTesting.h"; sourceTree = "<group>"; };
8B1A16050D90344B00CA1E8E /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = "<group>"; };
+ 8B1B49160E5F8E2100A08972 /* GTMExceptionalInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMExceptionalInlines.h; sourceTree = "<group>"; };
+ 8B1B49170E5F8E2100A08972 /* GTMExceptionalInlines.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMExceptionalInlines.m; sourceTree = "<group>"; };
+ 8B1B491B0E5F904C00A08972 /* GTMExceptionalInlinesTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMExceptionalInlinesTest.m; sourceTree = "<group>"; };
8B3344170DBF7A36009FD32C /* GTMNSAppleScript+HandlerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleScript+HandlerTest.m"; sourceTree = "<group>"; };
8B3344180DBF7A36009FD32C /* GTMNSAppleScript+Handler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSAppleScript+Handler.m"; sourceTree = "<group>"; };
8B3344190DBF7A36009FD32C /* GTMNSAppleScript+Handler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSAppleScript+Handler.h"; sourceTree = "<group>"; };
@@ -674,6 +687,9 @@
F47F1C0D0D490BC000925B8F /* GTMNSBezierPath+Shading.h */,
F47F1C0E0D490BC000925B8F /* GTMNSBezierPath+Shading.m */,
F47F1C110D490BC000925B8F /* GTMNSBezierPath+ShadingTest.m */,
+ 7F3EB38C0E5E09C700A7A75E /* GTMNSImage+Scaling.h */,
+ 7F3EB38D0E5E09C700A7A75E /* GTMNSImage+Scaling.m */,
+ 7F3EB3930E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m */,
F47F1C740D490E5C00925B8F /* GTMShading.h */,
F435E4840DC8F3DC0069CDE8 /* TestData */,
);
@@ -696,6 +712,9 @@
F41D25880DBD21A300774EEB /* GTMBase64.h */,
F41D25890DBD21A300774EEB /* GTMBase64.m */,
F41D258A0DBD21A300774EEB /* GTMBase64Test.m */,
+ 8B1B49160E5F8E2100A08972 /* GTMExceptionalInlines.h */,
+ 8B1B49170E5F8E2100A08972 /* GTMExceptionalInlines.m */,
+ 8B1B491B0E5F904C00A08972 /* GTMExceptionalInlinesTest.m */,
8BE2836B0DED0F130035B3F8 /* GTMFourCharCode.m */,
8BE2836C0DED0F130035B3F8 /* GTMFourCharCodeTest.m */,
8BE2836D0DED0F130035B3F8 /* GTMFourCharCode.h */,
@@ -899,6 +918,8 @@
F92B9FA80E2E64B900A2FE61 /* GTMLogger.h in Headers */,
F92B9FA90E2E64BC00A2FE61 /* GTMLogger+ASL.h in Headers */,
F95803FA0E2FB08F0049A088 /* GTMLoggerRingBufferWriter.h in Headers */,
+ 8B1B49180E5F8E2100A08972 /* GTMExceptionalInlines.h in Headers */,
+ 7F3EB38E0E5E09C700A7A75E /* GTMNSImage+Scaling.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1040,7 +1061,7 @@
BuildIndependentTargetsInParallel = YES;
};
buildConfigurationList = 1DEB918108733D990010E9CD /* Build configuration list for PBXProject "GTM" */;
- compatibilityVersion = "Xcode 3.0";
+ compatibilityVersion = "Xcode 3.1";
hasScannedForEncodings = 1;
mainGroup = 0867D691FE84028FC02AAC07 /* GTM */;
productRefGroup = 034768DFFF38A50411DB9C8B /* Products */;
@@ -1112,6 +1133,7 @@
8B1801B20E25341B00280961 /* GTMLargeTypeWindowLongTextTest.gtmUTState in Resources */,
8B1801B30E25341B00280961 /* GTMLargeTypeWindowShortTextTest.gtmUTState in Resources */,
8B1802420E25592200280961 /* GTMLargeTypeWindowMediumTextTest.gtmUTState in Resources */,
+ 7F3EB5540E5F0B0400A7A75E /* GTMUnitTestingImage.tiff in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1228,6 +1250,7 @@
F98681960E2C20C100CEE8BF /* GTMLogger+ASLTest.m in Sources */,
F95803FB0E2FB0A80049A088 /* GTMLoggerRingBufferWriterTest.m in Sources */,
F427EFC40E4023DD00ADD2AA /* GTMProgressMonitorInputStreamTest.m in Sources */,
+ 8B1B49260E5F97C800A08972 /* GTMExceptionalInlinesTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1275,6 +1298,8 @@
F95803F90E2FB0850049A088 /* GTMLoggerRingBufferWriter.m in Sources */,
8B61FDC00E4CDB8000FF9C21 /* GTMStackTrace.m in Sources */,
8B58E9950E547EB000A0E02E /* GTMGetURLHandler.m in Sources */,
+ 8B1B49190E5F8E2100A08972 /* GTMExceptionalInlines.m in Sources */,
+ 7F3EB38F0E5E09C700A7A75E /* GTMNSImage+Scaling.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1292,7 +1317,8 @@
8B7DCBC10DFF0F7F0017E983 /* GTMMethodCheck.m in Sources */,
8B7DCBED0DFF1A4F0017E983 /* GTMUnitTestDevLog.m in Sources */,
8B7DCE190DFF39850017E983 /* GTMSenTestCase.m in Sources */,
- 8B1801A50E2533DB00280961 /* GTMLargeTypeWindowTest.m in Sources */,
+ 7F3EB3940E5E0A2100A7A75E /* GTMNSImage+ScalingTest.m in Sources */,
+ 7F3EB5870E5F0CBB00A7A75E /* GTMLargeTypeWindowTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj
index 5cc5cf0..bf1133b 100644
--- a/GTMiPhone.xcodeproj/project.pbxproj
+++ b/GTMiPhone.xcodeproj/project.pbxproj
@@ -29,9 +29,7 @@
8B3AA8F30E032FC7007E31B5 /* GTMNSString+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA8F10E032FC7007E31B5 /* GTMNSString+URLArguments.m */; };
8B3AA8F40E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA8F20E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m */; };
8B3AA9220E033624007E31B5 /* GTMHTTPFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA91D0E033624007E31B5 /* GTMHTTPFetcher.m */; };
- 8B3AA9230E033624007E31B5 /* GTMHTTPFetcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA91E0E033624007E31B5 /* GTMHTTPFetcherTest.m */; };
8B3AA9240E033624007E31B5 /* GTMHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9200E033624007E31B5 /* GTMHTTPServer.m */; };
- 8B3AA9250E033624007E31B5 /* GTMHTTPServerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9210E033624007E31B5 /* GTMHTTPServerTest.m */; };
8B3AA9290E033647007E31B5 /* GTMTestHTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9280E033647007E31B5 /* GTMTestHTTPServer.m */; };
8B3AA9340E0336AC007E31B5 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B3AA9330E0336AC007E31B5 /* CFNetwork.framework */; };
8B3AA96A0E0337E4007E31B5 /* GTMHTTPFetcherTestPage.html in Resources */ = {isa = PBXBuildFile; fileRef = 8B3AA9690E0337E4007E31B5 /* GTMHTTPFetcherTestPage.html */; };
@@ -39,12 +37,15 @@
8B41EC100E0711D40040CF9F /* GTMValidatingContainers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B41EC0D0E0711D40040CF9F /* GTMValidatingContainers.m */; };
8B5547CA0DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5547C70DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m */; };
8B5547CB0DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B5547C90DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m */; };
+ 8B5A9E200E71CB6C005DA441 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B5A9E1F0E71CB6C005DA441 /* AddressBook.framework */; };
8B7DCEAA0DFF4C760017E983 /* GTMDevLogUnitTestingBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCEA90DFF4C760017E983 /* GTMDevLogUnitTestingBridge.m */; };
8B7DCEAD0DFF4CA60017E983 /* GTMUnitTestDevLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B7DCEAC0DFF4CA60017E983 /* GTMUnitTestDevLog.m */; };
+ 8BA5F40B0E75669000798036 /* GTMABAddressBook.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BA5F4080E75669000798036 /* GTMABAddressBook.m */; };
+ 8BA5F40C0E75669000798036 /* phone.png in Resources */ = {isa = PBXBuildFile; fileRef = 8BA5F40A0E75669000798036 /* phone.png */; };
+ 8BA5F52C0E7567AB00798036 /* GTMABAddressBookTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BA5F52B0E7567AB00798036 /* GTMABAddressBookTest.m */; };
8BC0480F0DAE928A00C2D1CA /* GTMCalculatedRange.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */; };
8BC048100DAE928A00C2D1CA /* GTMCalculatedRangeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047790DAE928A00C2D1CA /* GTMCalculatedRangeTest.m */; };
8BC048130DAE928A00C2D1CA /* GTMNSData+zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0477F0DAE928A00C2D1CA /* GTMNSData+zlib.m */; };
- 8BC048140DAE928A00C2D1CA /* GTMNSData+zlibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047800DAE928A00C2D1CA /* GTMNSData+zlibTest.m */; };
8BC048150DAE928A00C2D1CA /* GTMNSEnumerator+Filter.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047820DAE928A00C2D1CA /* GTMNSEnumerator+Filter.m */; };
8BC048160DAE928A00C2D1CA /* GTMNSEnumerator+FilterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047830DAE928A00C2D1CA /* GTMNSEnumerator+FilterTest.m */; };
8BC048170DAE928A00C2D1CA /* GTMNSFileManager+Path.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047850DAE928A00C2D1CA /* GTMNSFileManager+Path.m */; };
@@ -67,6 +68,21 @@
8BC04A750DAF145200C2D1CA /* GTMSystemVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC04A740DAF145200C2D1CA /* GTMSystemVersion.m */; };
8BC04D480DB0088500C2D1CA /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BC04D470DB0088500C2D1CA /* libz.dylib */; };
8BC04DE80DB023D400C2D1CA /* ReleaseNotes.txt in Resources */ = {isa = PBXBuildFile; fileRef = 8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */; };
+ 8BDA25120E759A6300C9769D /* GTMHTTPFetcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA91E0E033624007E31B5 /* GTMHTTPFetcherTest.m */; };
+ 8BDA25130E759A6400C9769D /* GTMHTTPServerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B3AA9210E033624007E31B5 /* GTMHTTPServerTest.m */; };
+ 8BDA25140E759A6500C9769D /* GTMNSData+zlibTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC047800DAE928A00C2D1CA /* GTMNSData+zlibTest.m */; };
+ F418AF990E7558EC004FB565 /* GTMExceptionalInlines.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AF940E7558DC004FB565 /* GTMExceptionalInlines.m */; };
+ F418AF9A0E7558EC004FB565 /* GTMExceptionalInlinesTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AF950E7558DC004FB565 /* GTMExceptionalInlinesTest.m */; };
+ F418AFA50E7559C7004FB565 /* GTMLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFA30E7559C7004FB565 /* GTMLogger.m */; };
+ F418AFA60E7559C7004FB565 /* GTMLoggerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFA40E7559C7004FB565 /* GTMLoggerTest.m */; };
+ F418AFB40E755B4D004FB565 /* GTMLoggerRingBufferWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFB20E755B4D004FB565 /* GTMLoggerRingBufferWriter.m */; };
+ F418AFB50E755B4D004FB565 /* GTMLoggerRingBufferWriterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFB30E755B4D004FB565 /* GTMLoggerRingBufferWriterTest.m */; };
+ F418AFCD0E755C94004FB565 /* GTMNSDictionary+URLArguments.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFCB0E755C94004FB565 /* GTMNSDictionary+URLArguments.m */; };
+ F418AFCE0E755C94004FB565 /* GTMNSDictionary+URLArgumentsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFCC0E755C94004FB565 /* GTMNSDictionary+URLArgumentsTest.m */; };
+ F418AFD70E755D44004FB565 /* GTMPath.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFD50E755D44004FB565 /* GTMPath.m */; };
+ F418AFD80E755D44004FB565 /* GTMPathTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFD60E755D44004FB565 /* GTMPathTest.m */; };
+ F418AFEB0E755F21004FB565 /* GTMProgressMonitorInputStream.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFE90E755F21004FB565 /* GTMProgressMonitorInputStream.m */; };
+ F418AFEC0E755F21004FB565 /* GTMProgressMonitorInputStreamTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F418AFEA0E755F21004FB565 /* GTMProgressMonitorInputStreamTest.m */; };
F439ADEB0DBD3C0000BE9B91 /* GTMBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = F439ADE90DBD3C0000BE9B91 /* GTMBase64.m */; };
F439ADEC0DBD3C0000BE9B91 /* GTMBase64Test.m in Sources */ = {isa = PBXBuildFile; fileRef = F439ADEA0DBD3C0000BE9B91 /* GTMBase64Test.m */; };
F439ADF00DBD3C4000BE9B91 /* GTMGeometryUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = F439ADEE0DBD3C4000BE9B91 /* GTMGeometryUtils.m */; };
@@ -91,7 +107,6 @@
32CA4F630368D1EE00C91783 /* GTM_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTM_Prefix.pch; sourceTree = "<group>"; };
67A7820A0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMIPhoneUnitTestDelegate.h; sourceTree = "<group>"; };
67A7820B0E00927400EBF506 /* GTMIPhoneUnitTestDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMIPhoneUnitTestDelegate.m; sourceTree = "<group>"; };
- 8B308AF40DAD070C00183556 /* RunIPhoneUnitTest.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = RunIPhoneUnitTest.sh; path = UnitTesting/RunIPhoneUnitTest.sh; sourceTree = "<group>"; };
8B308BCD0DAD0B8400183556 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
8B3AA8F00E032FC7007E31B5 /* GTMNSString+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSString+URLArguments.h"; sourceTree = "<group>"; };
8B3AA8F10E032FC7007E31B5 /* GTMNSString+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSString+URLArguments.m"; sourceTree = "<group>"; };
@@ -112,9 +127,14 @@
8B5547C70DB3BBF20014CC1C /* GTMUIKit+UnitTesting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIKit+UnitTesting.m"; sourceTree = "<group>"; };
8B5547C80DB3BBF20014CC1C /* GTMUIKit+UnitTesting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMUIKit+UnitTesting.h"; sourceTree = "<group>"; };
8B5547C90DB3BBF20014CC1C /* GTMUIKit+UnitTestingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIKit+UnitTestingTest.m"; sourceTree = "<group>"; };
+ 8B5A9E1F0E71CB6C005DA441 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; };
8B7DCEA90DFF4C760017E983 /* GTMDevLogUnitTestingBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMDevLogUnitTestingBridge.m; sourceTree = "<group>"; };
8B7DCEAB0DFF4CA60017E983 /* GTMUnitTestDevLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMUnitTestDevLog.h; sourceTree = "<group>"; };
8B7DCEAC0DFF4CA60017E983 /* GTMUnitTestDevLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMUnitTestDevLog.m; sourceTree = "<group>"; };
+ 8BA5F4070E75669000798036 /* GTMABAddressBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMABAddressBook.h; sourceTree = "<group>"; };
+ 8BA5F4080E75669000798036 /* GTMABAddressBook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBook.m; sourceTree = "<group>"; };
+ 8BA5F40A0E75669000798036 /* phone.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = phone.png; sourceTree = "<group>"; };
+ 8BA5F52B0E7567AB00798036 /* GTMABAddressBookTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMABAddressBookTest.m; sourceTree = "<group>"; };
8BC047750DAE926E00C2D1CA /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMDefines.h; sourceTree = "<group>"; };
8BC047770DAE928A00C2D1CA /* GTMCalculatedRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMCalculatedRange.h; sourceTree = "<group>"; };
8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMCalculatedRange.m; sourceTree = "<group>"; };
@@ -162,6 +182,26 @@
8BC04A740DAF145200C2D1CA /* GTMSystemVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMSystemVersion.m; sourceTree = "<group>"; };
8BC04D470DB0088500C2D1CA /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ReleaseNotes.txt; sourceTree = "<group>"; };
+ F418AF6D0E755732004FB565 /* GTMiPhone-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GTMiPhone-Info.plist"; sourceTree = "<group>"; };
+ F418AF910E755893004FB565 /* xcconfigs-readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "xcconfigs-readme.txt"; sourceTree = "<group>"; };
+ F418AF930E7558DC004FB565 /* GTMExceptionalInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMExceptionalInlines.h; sourceTree = "<group>"; };
+ F418AF940E7558DC004FB565 /* GTMExceptionalInlines.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMExceptionalInlines.m; sourceTree = "<group>"; };
+ F418AF950E7558DC004FB565 /* GTMExceptionalInlinesTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMExceptionalInlinesTest.m; sourceTree = "<group>"; };
+ F418AFA20E7559C7004FB565 /* GTMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLogger.h; sourceTree = "<group>"; };
+ F418AFA30E7559C7004FB565 /* GTMLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLogger.m; sourceTree = "<group>"; };
+ F418AFA40E7559C7004FB565 /* GTMLoggerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLoggerTest.m; sourceTree = "<group>"; };
+ F418AFB10E755B4D004FB565 /* GTMLoggerRingBufferWriter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLoggerRingBufferWriter.h; sourceTree = "<group>"; };
+ F418AFB20E755B4D004FB565 /* GTMLoggerRingBufferWriter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLoggerRingBufferWriter.m; sourceTree = "<group>"; };
+ F418AFB30E755B4D004FB565 /* GTMLoggerRingBufferWriterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLoggerRingBufferWriterTest.m; sourceTree = "<group>"; };
+ F418AFCA0E755C94004FB565 /* GTMNSDictionary+URLArguments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+URLArguments.h"; sourceTree = "<group>"; };
+ F418AFCB0E755C94004FB565 /* GTMNSDictionary+URLArguments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArguments.m"; sourceTree = "<group>"; };
+ F418AFCC0E755C94004FB565 /* GTMNSDictionary+URLArgumentsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+URLArgumentsTest.m"; sourceTree = "<group>"; };
+ F418AFD40E755D44004FB565 /* GTMPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMPath.h; sourceTree = "<group>"; };
+ F418AFD50E755D44004FB565 /* GTMPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMPath.m; sourceTree = "<group>"; };
+ F418AFD60E755D44004FB565 /* GTMPathTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMPathTest.m; sourceTree = "<group>"; };
+ F418AFE80E755F21004FB565 /* GTMProgressMonitorInputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMProgressMonitorInputStream.h; sourceTree = "<group>"; };
+ F418AFE90E755F21004FB565 /* GTMProgressMonitorInputStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMProgressMonitorInputStream.m; sourceTree = "<group>"; };
+ F418AFEA0E755F21004FB565 /* GTMProgressMonitorInputStreamTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMProgressMonitorInputStreamTest.m; sourceTree = "<group>"; };
F439ADE80DBD3C0000BE9B91 /* GTMBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMBase64.h; sourceTree = "<group>"; };
F439ADE90DBD3C0000BE9B91 /* GTMBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMBase64.m; sourceTree = "<group>"; };
F439ADEA0DBD3C0000BE9B91 /* GTMBase64Test.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMBase64Test.m; sourceTree = "<group>"; };
@@ -181,6 +221,7 @@
8B308BCE0DAD0B8400183556 /* QuartzCore.framework in Frameworks */,
8BC04D480DB0088500C2D1CA /* libz.dylib in Frameworks */,
8B3AA9340E0336AC007E31B5 /* CFNetwork.framework in Frameworks */,
+ 8B5A9E200E71CB6C005DA441 /* AddressBook.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -200,14 +241,15 @@
children = (
8BC04DE70DB023D400C2D1CA /* ReleaseNotes.txt */,
8BC047750DAE926E00C2D1CA /* GTMDefines.h */,
+ 8BA5F4060E75669000798036 /* iPhone */,
8BC047760DAE928A00C2D1CA /* Foundation */,
8BC0479A0DAE928A00C2D1CA /* DebugUtils */,
8BC0479F0DAE928A00C2D1CA /* UnitTesting */,
8BC049840DAEC59100C2D1CA /* XcodeConfig */,
- 32CA4F630368D1EE00C91783 /* GTM_Prefix.pch */,
- 8B308AF40DAD070C00183556 /* RunIPhoneUnitTest.sh */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
+ 32CA4F630368D1EE00C91783 /* GTM_Prefix.pch */,
+ F418AF6D0E755732004FB565 /* GTMiPhone-Info.plist */,
);
name = CustomTemplate;
sourceTree = "<group>";
@@ -216,6 +258,7 @@
isa = PBXGroup;
children = (
8B3AA9330E0336AC007E31B5 /* CFNetwork.framework */,
+ 8B5A9E1F0E71CB6C005DA441 /* AddressBook.framework */,
8BC04D470DB0088500C2D1CA /* libz.dylib */,
8B308BCD0DAD0B8400183556 /* QuartzCore.framework */,
1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */,
@@ -233,15 +276,40 @@
path = TestData;
sourceTree = "<group>";
};
+ 8BA5F4060E75669000798036 /* iPhone */ = {
+ isa = PBXGroup;
+ children = (
+ 8BA5F4070E75669000798036 /* GTMABAddressBook.h */,
+ 8BA5F4080E75669000798036 /* GTMABAddressBook.m */,
+ 8BA5F52B0E7567AB00798036 /* GTMABAddressBookTest.m */,
+ 8BA5F4090E75669000798036 /* TestData */,
+ );
+ path = iPhone;
+ sourceTree = "<group>";
+ };
+ 8BA5F4090E75669000798036 /* TestData */ = {
+ isa = PBXGroup;
+ children = (
+ 8BA5F40A0E75669000798036 /* phone.png */,
+ );
+ path = TestData;
+ sourceTree = "<group>";
+ };
8BC047760DAE928A00C2D1CA /* Foundation */ = {
isa = PBXGroup;
children = (
+ F418AFD40E755D44004FB565 /* GTMPath.h */,
+ F418AFD50E755D44004FB565 /* GTMPath.m */,
+ F418AFD60E755D44004FB565 /* GTMPathTest.m */,
F439ADE80DBD3C0000BE9B91 /* GTMBase64.h */,
F439ADE90DBD3C0000BE9B91 /* GTMBase64.m */,
F439ADEA0DBD3C0000BE9B91 /* GTMBase64Test.m */,
8BC047770DAE928A00C2D1CA /* GTMCalculatedRange.h */,
8BC047780DAE928A00C2D1CA /* GTMCalculatedRange.m */,
8BC047790DAE928A00C2D1CA /* GTMCalculatedRangeTest.m */,
+ F418AF930E7558DC004FB565 /* GTMExceptionalInlines.h */,
+ F418AF940E7558DC004FB565 /* GTMExceptionalInlines.m */,
+ F418AF950E7558DC004FB565 /* GTMExceptionalInlinesTest.m */,
8BC0477A0DAE928A00C2D1CA /* GTMGarbageCollection.h */,
F439ADED0DBD3C4000BE9B91 /* GTMGeometryUtils.h */,
F439ADEE0DBD3C4000BE9B91 /* GTMGeometryUtils.m */,
@@ -252,9 +320,18 @@
8B3AA91F0E033624007E31B5 /* GTMHTTPServer.h */,
8B3AA9200E033624007E31B5 /* GTMHTTPServer.m */,
8B3AA9210E033624007E31B5 /* GTMHTTPServerTest.m */,
+ F418AFA20E7559C7004FB565 /* GTMLogger.h */,
+ F418AFA30E7559C7004FB565 /* GTMLogger.m */,
+ F418AFA40E7559C7004FB565 /* GTMLoggerTest.m */,
+ F418AFB10E755B4D004FB565 /* GTMLoggerRingBufferWriter.h */,
+ F418AFB20E755B4D004FB565 /* GTMLoggerRingBufferWriter.m */,
+ F418AFB30E755B4D004FB565 /* GTMLoggerRingBufferWriterTest.m */,
8BC0477E0DAE928A00C2D1CA /* GTMNSData+zlib.h */,
8BC0477F0DAE928A00C2D1CA /* GTMNSData+zlib.m */,
8BC047800DAE928A00C2D1CA /* GTMNSData+zlibTest.m */,
+ F418AFCA0E755C94004FB565 /* GTMNSDictionary+URLArguments.h */,
+ F418AFCB0E755C94004FB565 /* GTMNSDictionary+URLArguments.m */,
+ F418AFCC0E755C94004FB565 /* GTMNSDictionary+URLArgumentsTest.m */,
8BC047810DAE928A00C2D1CA /* GTMNSEnumerator+Filter.h */,
8BC047820DAE928A00C2D1CA /* GTMNSEnumerator+Filter.m */,
8BC047830DAE928A00C2D1CA /* GTMNSEnumerator+FilterTest.m */,
@@ -272,6 +349,9 @@
8BC0478C0DAE928A00C2D1CA /* GTMNSString+XMLTest.m */,
8BC0478D0DAE928A00C2D1CA /* GTMObjC2Runtime.h */,
8BC047900DAE928A00C2D1CA /* GTMObjectSingleton.h */,
+ F418AFE80E755F21004FB565 /* GTMProgressMonitorInputStream.h */,
+ F418AFE90E755F21004FB565 /* GTMProgressMonitorInputStream.m */,
+ F418AFEA0E755F21004FB565 /* GTMProgressMonitorInputStreamTest.m */,
8BC047910DAE928A00C2D1CA /* GTMRegex.h */,
8BC047920DAE928A00C2D1CA /* GTMRegex.m */,
8BC047930DAE928A00C2D1CA /* GTMRegexTest.m */,
@@ -327,6 +407,7 @@
8BC049840DAEC59100C2D1CA /* XcodeConfig */ = {
isa = PBXGroup;
children = (
+ F418AF910E755893004FB565 /* xcconfigs-readme.txt */,
8BC04F020DB15A5300C2D1CA /* Project */,
8BC0498E0DAEC59100C2D1CA /* subconfig */,
);
@@ -406,6 +487,7 @@
8BC0486C0DAE928A00C2D1CA /* GTMUIViewUnitTestingTest.png in Resources */,
8BC04DE80DB023D400C2D1CA /* ReleaseNotes.txt in Resources */,
8B3AA96A0E0337E4007E31B5 /* GTMHTTPFetcherTestPage.html in Resources */,
+ 8BA5F40C0E75669000798036 /* phone.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -435,7 +517,6 @@
8BC0480F0DAE928A00C2D1CA /* GTMCalculatedRange.m in Sources */,
8BC048100DAE928A00C2D1CA /* GTMCalculatedRangeTest.m in Sources */,
8BC048130DAE928A00C2D1CA /* GTMNSData+zlib.m in Sources */,
- 8BC048140DAE928A00C2D1CA /* GTMNSData+zlibTest.m in Sources */,
8BC048150DAE928A00C2D1CA /* GTMNSEnumerator+Filter.m in Sources */,
8BC048160DAE928A00C2D1CA /* GTMNSEnumerator+FilterTest.m in Sources */,
8BC048170DAE928A00C2D1CA /* GTMNSFileManager+Path.m in Sources */,
@@ -466,12 +547,27 @@
8B3AA8F30E032FC7007E31B5 /* GTMNSString+URLArguments.m in Sources */,
8B3AA8F40E032FC7007E31B5 /* GTMNSString+URLArgumentsTest.m in Sources */,
8B3AA9220E033624007E31B5 /* GTMHTTPFetcher.m in Sources */,
- 8B3AA9230E033624007E31B5 /* GTMHTTPFetcherTest.m in Sources */,
8B3AA9240E033624007E31B5 /* GTMHTTPServer.m in Sources */,
- 8B3AA9250E033624007E31B5 /* GTMHTTPServerTest.m in Sources */,
8B3AA9290E033647007E31B5 /* GTMTestHTTPServer.m in Sources */,
8B41EC0F0E0711D40040CF9F /* GTMValidatingContainersTest.m in Sources */,
8B41EC100E0711D40040CF9F /* GTMValidatingContainers.m in Sources */,
+ F418AF990E7558EC004FB565 /* GTMExceptionalInlines.m in Sources */,
+ F418AF9A0E7558EC004FB565 /* GTMExceptionalInlinesTest.m in Sources */,
+ F418AFA50E7559C7004FB565 /* GTMLogger.m in Sources */,
+ F418AFA60E7559C7004FB565 /* GTMLoggerTest.m in Sources */,
+ F418AFB40E755B4D004FB565 /* GTMLoggerRingBufferWriter.m in Sources */,
+ F418AFB50E755B4D004FB565 /* GTMLoggerRingBufferWriterTest.m in Sources */,
+ F418AFCD0E755C94004FB565 /* GTMNSDictionary+URLArguments.m in Sources */,
+ F418AFCE0E755C94004FB565 /* GTMNSDictionary+URLArgumentsTest.m in Sources */,
+ F418AFD70E755D44004FB565 /* GTMPath.m in Sources */,
+ F418AFD80E755D44004FB565 /* GTMPathTest.m in Sources */,
+ F418AFEB0E755F21004FB565 /* GTMProgressMonitorInputStream.m in Sources */,
+ F418AFEC0E755F21004FB565 /* GTMProgressMonitorInputStreamTest.m in Sources */,
+ 8BA5F40B0E75669000798036 /* GTMABAddressBook.m in Sources */,
+ 8BA5F52C0E7567AB00798036 /* GTMABAddressBookTest.m in Sources */,
+ 8BDA25120E759A6300C9769D /* GTMHTTPFetcherTest.m in Sources */,
+ 8BDA25130E759A6400C9769D /* GTMHTTPServerTest.m in Sources */,
+ 8BDA25140E759A6500C9769D /* GTMNSData+zlibTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -506,8 +602,8 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 8BC049850DAEC59100C2D1CA /* DebugiPhone.xcconfig */;
buildSettings = {
- "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ SDKROOT = iphonesimulator2.0;
};
name = "Debug-gcov";
};
@@ -525,6 +621,7 @@
baseConfigurationReference = 8BC049850DAEC59100C2D1CA /* DebugiPhone.xcconfig */;
buildSettings = {
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ SDKROOT = iphonesimulator2.0;
};
name = Debug;
};
@@ -533,6 +630,7 @@
baseConfigurationReference = 8BC049890DAEC59100C2D1CA /* ReleaseiPhone.xcconfig */;
buildSettings = {
GCC_PREFIX_HEADER = GTM_Prefix.pch;
+ SDKROOT = iphonesimulator2.0;
};
name = Release;
};
diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
index 047935d..5dbfe96 100644
--- a/ReleaseNotes.txt
+++ b/ReleaseNotes.txt
@@ -85,6 +85,21 @@ Changes since 1.5.1
Allows you to index .xcodeproj, .scpt, .scptd, .xib, .nib, and
.aib files. See Readmes beside individual projects in SpotlightPlugins.
+- Added GTMExceptionalInlines for dealing with cases where you get
+ warning: variable 'r' might be clobbered by 'longjmp' or 'vfork'
+ when using certain Apple inlined functions in @synchronized/@try blocks.
+
+- Updated to Xcode 3.1 so the GTM and iPhone project have the same baseline.
+ The code should work in other version of xcode, but the projects and
+ xcconfig files now use 3.1 features.
+
+- Added GTMABAddressBook which is a cocoa wrapper for the 'C' AddressBook
+ APIs on the iPhone.
+
+- Added several set environment variable statements to RunIPhoneUnitTest.sh
+ to encourage bugs to come out of the woodwork.
+
+
Release 1.5.1
Changes since 1.5.0
16-June-2008
diff --git a/UnitTesting/GTMIPhoneUnitTestDelegate.m b/UnitTesting/GTMIPhoneUnitTestDelegate.m
index f3df548..f93c472 100644
--- a/UnitTesting/GTMIPhoneUnitTestDelegate.m
+++ b/UnitTesting/GTMIPhoneUnitTestDelegate.m
@@ -161,7 +161,7 @@ static int MethodSort(const void *a, const void *b) {
NSDate *fixtureEndDate = [NSDate date];
NSTimeInterval fixtureEndTime = [fixtureEndDate timeIntervalSinceDate:fixtureStartDate];
NSString *fixtureEndString = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n"
- "Executed %d tests, with %d failures (%d unexpected) in %0.3f (%0.3f) seconds\n",
+ "Executed %d tests, with %d failures (%d unexpected) in %0.3f (%0.3f) seconds\n\n",
fixtureName, fixtureEndDate, fixtureTotal,
fixtureFailures, fixtureFailures,
fixtureEndTime, fixtureEndTime];
@@ -176,7 +176,7 @@ static int MethodSort(const void *a, const void *b) {
NSDate *suiteEndDate = [NSDate date];
NSTimeInterval suiteEndTime = [suiteEndDate timeIntervalSinceDate:suiteStartDate];
NSString *suiteEndString = [NSString stringWithFormat:@"Test Suite '%@' finished at %@.\n"
- "Executed %d tests, with %d failures (%d unexpected) in %0.3f (%0.3f) seconds\n",
+ "Executed %d tests, with %d failures (%d unexpected) in %0.3f (%0.3f) seconds\n\n",
suiteName, suiteEndDate, suiteTotal,
suiteFailures, suiteFailures,
suiteEndTime, suiteEndTime];
diff --git a/UnitTesting/GTMUIKit+UnitTesting.h b/UnitTesting/GTMUIKit+UnitTesting.h
index 578ee4d..bb78a7a 100644
--- a/UnitTesting/GTMUIKit+UnitTesting.h
+++ b/UnitTesting/GTMUIKit+UnitTesting.h
@@ -85,6 +85,18 @@
- (BOOL)gtm_shouldEncodeStateForSubviews;
@end
+// Category to help UIImage testing. UIImage can be tested using
+// GTMAssertObjectImageEqualToImageNamed macro, which automatically creates
+// result images and diff images in case test fails.
+@interface UIImage (GTMUnitTestingAdditions) <GTMUnitTestingImaging>
+// Returns an image containing a representation suitable for use in comparing
+// against a master image.
+//
+// Returns:
+// an image of the object
+- (CGImageRef)gtm_createUnitTestImage;
+@end
+
// A view that allows you to delegate out drawing using the formal
// GTMUnitTestViewDelegate protocol
// This is useful when writing up unit tests for visual elements.
diff --git a/UnitTesting/GTMUIKit+UnitTesting.m b/UnitTesting/GTMUIKit+UnitTesting.m
index 7b98886..d1cf2b2 100644
--- a/UnitTesting/GTMUIKit+UnitTesting.m
+++ b/UnitTesting/GTMUIKit+UnitTesting.m
@@ -116,3 +116,11 @@
return NO;
}
@end
+
+@implementation UIImage (GTMUnitTestingAdditions)
+- (CGImageRef)gtm_createUnitTestImage {
+ CGImageRef imageRef = [self CGImage];
+ CGImageRetain(imageRef);
+ return imageRef;
+}
+@end
diff --git a/UnitTesting/GTMUIKit+UnitTestingTest.m b/UnitTesting/GTMUIKit+UnitTestingTest.m
index d39ba9e..8b3effa 100644
--- a/UnitTesting/GTMUIKit+UnitTestingTest.m
+++ b/UnitTesting/GTMUIKit+UnitTestingTest.m
@@ -40,6 +40,13 @@
GTMAssertObjectStateEqualToStateNamed(view, @"GTMUIViewUnitTestingTest", nil);
}
+- (void)testUIImage {
+ NSString* name = @"GTMUIViewUnitTestingTest";
+ UIImage* image =
+ [UIImage imageNamed:[name stringByAppendingPathExtension:@"png"]];
+ GTMAssertObjectImageEqualToImageNamed(image, name, nil);
+}
+
- (void)gtm_unitTestViewDrawRect:(CGRect)rect contextInfo:(void*)contextInfo {
UIApplication *app = [UIApplication sharedApplication];
STAssertEqualObjects(app,
diff --git a/UnitTesting/RunIPhoneUnitTest.sh b/UnitTesting/RunIPhoneUnitTest.sh
index 9c09622..0089032 100755
--- a/UnitTesting/RunIPhoneUnitTest.sh
+++ b/UnitTesting/RunIPhoneUnitTest.sh
@@ -17,11 +17,27 @@
# Runs all unittests through the iPhone simulator. We don't handle running them
# on the device. To run on the device just choose "run".
+
if [ "$IPHONEOS_DEPLOYMENT_TARGET" == "" ]; then
+ # We kill the iPhone simulator because otherwise we run into issues where
+ # the unittests fail becuase the simulator is currently running, and
+ # at this time the iPhone SDK won't allow two simulators running at the same
+ # time.
+ /usr/bin/killall "iPhone Simulator"
export DYLD_ROOT_PATH="$SDKROOT"
export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR"
export IPHONE_SIMULATOR_ROOT="$SDKROOT"
export CFFIXED_USER_HOME="$USER_LIBRARY_DIR/Application Support/iPhone Simulator/User"
+
+ # Encourage errors
+ export MallocScribble=YES
+ export MallocPreScribble=YES
+ export MallocGuardEdges=YES
+ export CFZombieLevel=3
+ export NSAutoreleaseFreedObjectCheckEnabled=YES
+ export NSZombieEnabled=YES
+ export OBJC_DEBUG_FRAGILE_SUPERCLASSES=YES
+
"$TARGET_BUILD_DIR/$EXECUTABLE_PATH" -RegisterForSystemEvents
else
echo "note: Skipping running of unittests for device build."
diff --git a/UnitTesting/RunMacOSUnitTests.sh b/UnitTesting/RunMacOSUnitTests.sh
index 2c8440a..45d0afc 100755
--- a/UnitTesting/RunMacOSUnitTests.sh
+++ b/UnitTesting/RunMacOSUnitTests.sh
@@ -1,5 +1,5 @@
#
-# RunUnitTests.sh
+# RunMacOSUnitTests.sh
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
diff --git a/XcodeConfig/Project/DebugLeopardOrLater.xcconfig b/XcodeConfig/Project/DebugLeopardOrLater.xcconfig
index 6aebf12..5bbd4a2 100644
--- a/XcodeConfig/Project/DebugLeopardOrLater.xcconfig
+++ b/XcodeConfig/Project/DebugLeopardOrLater.xcconfig
@@ -1,7 +1,8 @@
//
// DebugLeopardOrLater.xcconfig
//
-// Xcode configuration file for building a Debug target on Leopard or later.
+// Xcode configuration file for building a Debug configuration of a project
+// on Leopard or later.
//
// This is a _Configuration_ Xcode config file for use in the "Based on" popup
// of the project configuration editor. Do _not_ use this as the config base
diff --git a/XcodeConfig/Project/DebugTigerOrLater.xcconfig b/XcodeConfig/Project/DebugTigerOrLater.xcconfig
index 725ea49..b8893ce 100644
--- a/XcodeConfig/Project/DebugTigerOrLater.xcconfig
+++ b/XcodeConfig/Project/DebugTigerOrLater.xcconfig
@@ -1,7 +1,8 @@
//
// DebugTigerOrLater.xcconfig
//
-// Xcode configuration file for building a Debug target on Tiger or later.
+// Xcode configuration file for building a Debug configuration of a project
+// on Tiger or later.
//
// This is a _Configuration_ Xcode config file for use in the "Based on" popup
// of the project configuration editor. Do _not_ use this as the config base
diff --git a/XcodeConfig/Project/DebugiPhone.xcconfig b/XcodeConfig/Project/DebugiPhone.xcconfig
index 1ba99b4..4af17bd 100644
--- a/XcodeConfig/Project/DebugiPhone.xcconfig
+++ b/XcodeConfig/Project/DebugiPhone.xcconfig
@@ -1,7 +1,8 @@
//
// DebugiPhoneSimulator.xcconfig
//
-// Xcode configuration file for building a Debug target on iPhone
+// Xcode configuration file for building a Debug configuration of a project
+// for iPhone.
//
// Copyright 2006-2008 Google Inc.
//
diff --git a/XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig b/XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig
index 0188d10..f62b287 100644
--- a/XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig
+++ b/XcodeConfig/Project/ReleaseLeopardOrLater.xcconfig
@@ -1,7 +1,8 @@
//
// ReleaseLeopardOrLater.xcconfig
//
-// Xcode configuration file for building a Release target on Leopard or later.
+// Xcode configuration file for building a Release configuration of a project
+// on Leopard or later.
//
// This is a _Configuration_ Xcode config file for use in the "Based on" popup
// of the project configuration editor. Do _not_ use this as the config base
diff --git a/XcodeConfig/Project/ReleaseTigerOrLater.xcconfig b/XcodeConfig/Project/ReleaseTigerOrLater.xcconfig
index a024c4b..d604d88 100644
--- a/XcodeConfig/Project/ReleaseTigerOrLater.xcconfig
+++ b/XcodeConfig/Project/ReleaseTigerOrLater.xcconfig
@@ -1,7 +1,8 @@
//
// ReleaseTigerOrLater.xcconfig
//
-// Xcode configuration file for building a Release target on Tiger or later.
+// Xcode configuration file for building a Release configuration of a project
+// on Tiger or later.
//
// This is a _Configuration_ Xcode config file for use in the "Based on" popup
// of the project configuration editor. Do _not_ use this as the config base
diff --git a/XcodeConfig/Project/ReleaseiPhone.xcconfig b/XcodeConfig/Project/ReleaseiPhone.xcconfig
index d6c0127..1473a5a 100644
--- a/XcodeConfig/Project/ReleaseiPhone.xcconfig
+++ b/XcodeConfig/Project/ReleaseiPhone.xcconfig
@@ -1,7 +1,8 @@
//
// ReleaseAspenSimulator.xcconfig
//
-// Xcode configuration file for building a Release target on iPhone
+// Xcode configuration file for building a Release configuration of a project
+// for iPhone.
//
// Copyright 2006-2008 Google Inc.
//
diff --git a/XcodeConfig/Target/LoadableBundle.xcconfig b/XcodeConfig/Target/LoadableBundle.xcconfig
index 78ed39a..8b824d7 100644
--- a/XcodeConfig/Target/LoadableBundle.xcconfig
+++ b/XcodeConfig/Target/LoadableBundle.xcconfig
@@ -1,8 +1,8 @@
//
// LoadableBundle.xcconfig
//
-// Xcode configuration file for a loadable bundle. Usually a Cocoa plugin or
-// similar.
+// Xcode configuration file for a loadable bundle target. Usually a Cocoa
+// plugin or similar.
//
// This is a _Target_ config file, for use in the "Based on" popup of the
// settings dialog for a target. Do not attempt to apply this as the base
diff --git a/XcodeConfig/Target/LoadableBundleGCSupported.xcconfig b/XcodeConfig/Target/LoadableBundleGCSupported.xcconfig
index 861845a..aa8d5a7 100644
--- a/XcodeConfig/Target/LoadableBundleGCSupported.xcconfig
+++ b/XcodeConfig/Target/LoadableBundleGCSupported.xcconfig
@@ -1,7 +1,7 @@
//
// LoadableBundleGCSupported.xcconfig
//
-// Xcode configuration file for a loadable bundle that supports garbage
+// Xcode configuration file for a loadable bundle target that supports garbage
// collection. Usually a Cocoa plugin or similar.
//
// This is a _Target_ config file, for use in the "Based on" popup of the
diff --git a/XcodeConfig/Target/SharedLibrary.xcconfig b/XcodeConfig/Target/SharedLibrary.xcconfig
index 7593392..7c37d50 100644
--- a/XcodeConfig/Target/SharedLibrary.xcconfig
+++ b/XcodeConfig/Target/SharedLibrary.xcconfig
@@ -1,7 +1,7 @@
//
// SharedLibrary.xcconfig
//
-// Xcode configuration file for a shared library.
+// Xcode configuration file for a shared library target.
//
// This is a _Target_ config file, for use in the "Based on" popup of the
// settings dialog for a target. Do not attempt to apply this as the base
diff --git a/XcodeConfig/Target/SharedLibraryGCSupported.xcconfig b/XcodeConfig/Target/SharedLibraryGCSupported.xcconfig
index e6b56f7..74ab907 100644
--- a/XcodeConfig/Target/SharedLibraryGCSupported.xcconfig
+++ b/XcodeConfig/Target/SharedLibraryGCSupported.xcconfig
@@ -1,7 +1,7 @@
//
// SharedLibraryGCSupported.xcconfig
//
-// Xcode configuration file for a shared library that support garbage
+// Xcode configuration file for a shared library target that support garbage
// collection.
//
// This is a _Target_ config file, for use in the "Based on" popup of the
diff --git a/XcodeConfig/Target/StaticLibrary.xcconfig b/XcodeConfig/Target/StaticLibrary.xcconfig
index f459e83..df1016b 100644
--- a/XcodeConfig/Target/StaticLibrary.xcconfig
+++ b/XcodeConfig/Target/StaticLibrary.xcconfig
@@ -1,7 +1,7 @@
//
// StaticLibrary.xcconfig
//
-// Xcode configuration file for a static library.
+// Xcode configuration file for a static library target.
//
// This is a _Target_ config file, for use in the "Based on" popup of the
// settings dialog for a target. Do not attempt to apply this as the base
diff --git a/XcodeConfig/Target/StaticLibraryGCSupported.xcconfig b/XcodeConfig/Target/StaticLibraryGCSupported.xcconfig
index 6a57a0c..07ea1d5 100644
--- a/XcodeConfig/Target/StaticLibraryGCSupported.xcconfig
+++ b/XcodeConfig/Target/StaticLibraryGCSupported.xcconfig
@@ -1,7 +1,7 @@
//
// StaticLibraryGCSupported.xcconfig
//
-// Xcode configuration file for a static library that supports garbage
+// Xcode configuration file for a static library target that supports garbage
// collection.
//
// This is a _Target_ config file, for use in the "Based on" popup of the
diff --git a/XcodeConfig/subconfig/64bit.xcconfig b/XcodeConfig/subconfig/64bit.xcconfig
deleted file mode 100644
index bb9cae6..0000000
--- a/XcodeConfig/subconfig/64bit.xcconfig
+++ /dev/null
@@ -1,26 +0,0 @@
-//
-// 64bit.xcconfig
-//
-// Xcode configuration file to include for builds wanting 64bit support.
-//
-// 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.
-//
-
-// Compile all flavors: 32 & 64bit on both PPC and Intel
-ARCHS = i386 x86_64 ppc ppc64
-
-// Warn on implicit data conversions in 64bit builds
-GCC_WARN_64_TO_32_BIT_CONVERSION[arch=ppc64] = YES
-GCC_WARN_64_TO_32_BIT_CONVERSION[arch=x86_64] = YES
diff --git a/XcodeConfig/subconfig/General.xcconfig b/XcodeConfig/subconfig/General.xcconfig
index b6a9a02..6031f46 100644
--- a/XcodeConfig/subconfig/General.xcconfig
+++ b/XcodeConfig/subconfig/General.xcconfig
@@ -19,8 +19,22 @@
// the License.
//
-// Build for PPC and Intel
-ARCHS = i386 ppc
+// NOTE: as of Xcode 3.1, for iPhone development, the two SDKs you can match are:
+// SDK_NAME iphoneos2.0
+// SDK_NAME iphonesimulator2.0
+// for Mac OS developement, the values are:
+// SDK_NAME macosx10.4
+// SDK_NAME macosx10.5
+
+// Build for PPC and Intel (Leopard gets 64bit also)
+ARCHS[sdk=macosx10.4*] = i386 ppc
+ARCHS[sdk=macosx10.5*] = i386 x86_64 ppc ppc64
+// Build for arm for iPhone or Intel for the iPhone Simulator
+ARCHS[sdk=iphoneos*] = armv6
+ARCHS[sdk=iphonesimulator*] = i386
+
+// Build only the active architecture on iphone device targets
+ONLY_ACTIVE_ARCH[sdk=iphoneos*] = YES
// Zerolink prevents link warnings so turn it off
ZERO_LINK = NO
@@ -46,3 +60,9 @@ ALWAYS_SEARCH_USER_PATHS = NO
// Turn on position dependent code for most cases (overridden where appropriate)
GCC_DYNAMIC_NO_PIC = YES
+
+// Warn on implicit data conversions in 64bit builds
+GCC_WARN_64_TO_32_BIT_CONVERSION[arch=*64*] = YES
+
+// Use Obj-C fast dispatch (configs don't support 10.2 where you don't want it)
+GCC_FAST_OBJC_DISPATCH = YES
diff --git a/XcodeConfig/subconfig/LeopardOrLater.xcconfig b/XcodeConfig/subconfig/LeopardOrLater.xcconfig
index 6cc1a08..fa4dfce 100644
--- a/XcodeConfig/subconfig/LeopardOrLater.xcconfig
+++ b/XcodeConfig/subconfig/LeopardOrLater.xcconfig
@@ -23,10 +23,3 @@
SDKROOT = ${DEVELOPER_SDK_DIR}/MacOSX10.5.sdk
MACOSX_DEPLOYMENT_TARGET = 10.5
GCC_VERSION = 4.0
-
-// On Leopard use Obj-C fast dispatch
-GCC_FAST_OBJC_DISPATCH = YES
-
-// For Leopard, pull in 64bit support
-#include "64bit.xcconfig"
-
diff --git a/XcodeConfig/subconfig/TigerOrLater.xcconfig b/XcodeConfig/subconfig/TigerOrLater.xcconfig
index a693a58..bc0f8f4 100644
--- a/XcodeConfig/subconfig/TigerOrLater.xcconfig
+++ b/XcodeConfig/subconfig/TigerOrLater.xcconfig
@@ -23,6 +23,3 @@
SDKROOT = $(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk
MACOSX_DEPLOYMENT_TARGET = 10.4
GCC_VERSION = 4.0
-
-// On Tiger use Obj-C fast dispatch
-GCC_FAST_OBJC_DISPATCH = YES
diff --git a/XcodeConfig/subconfig/iPhone.xcconfig b/XcodeConfig/subconfig/iPhone.xcconfig
index 42445f1..0948db8 100644
--- a/XcodeConfig/subconfig/iPhone.xcconfig
+++ b/XcodeConfig/subconfig/iPhone.xcconfig
@@ -17,21 +17,10 @@
// License for the specific language governing permissions and limitations under
// the License.
-// Build for ARM
-ARCHS = armv6
-
-// Build only the active architecture
-ONLY_ACTIVE_ARCH = YES
-
// Code signing. Should be overridden if releasing
CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer
// Default SDK and minimum OS version is the iphone SDK.
SDKROOT = iphoneos2.0
-
MACOSX_DEPLOYMENT_TARGET = 10.5
GCC_VERSION = 4.0
-
-// On iPhone use Obj-C fast dispatch
-GCC_FAST_OBJC_DISPATCH = YES
-
diff --git a/iPhone/GTMABAddressBook.h b/iPhone/GTMABAddressBook.h
new file mode 100644
index 0000000..48e7f88
--- /dev/null
+++ b/iPhone/GTMABAddressBook.h
@@ -0,0 +1,319 @@
+//
+// GTMAddressBook.h
+//
+// 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.
+//
+
+// These classes wrap up the iPhone AddressBook 'C' API in a manner very
+// similar to that found on Mac OS X. They differ only in that none of these
+// routines throws, and some of the types are different as necessitated by
+// the APIs that they wrap. These wrappers also protect you from a number
+// of issues in the AddressBook API (as of iPhone SDK 2.0/2.1)
+//
+// Note that there is a strings file that you may want to localize
+// (GTMABAddressBook.strings).
+//
+// If things seem strange, it may be due to one of the following radars:
+// 6208390 Integer and real values don't work in ABMultiValueRefs
+// (and this isn't part of the title, but dictionaries don't work
+// either)
+// 6207605 RecordIDs for people and groups are not unique in AddressBook
+// 6204021 kABGroupNameProperty and kABPersonFirstNameProperty have the same
+// value
+// 6203982 ABPersonCopyLocalizedPropertyName returns name for
+// kABGroupNameProperty
+// 6203961 ABPersonGetTypeOfProperty returns a type for kABGroupNameProperty
+// 6203854 ABMultiValues hash to their address
+// 6203836 ABRecords hash to their address
+// 6203606 Need CFTypeIDs for AddressBook CFTypes
+// 6202868 ABPersonSetImageData should validate image data
+// 6202860 Passing nil person into ABGroupAddMember crashes
+// 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash
+// 6202807 ABMultiValueInsertValueAndLabelAtIndex allows you to insert values
+// past end
+// 6201276 Removing a NULL record using ABAddressBookRemoveRecord crashes
+// 6201258 Adding a NULL record using ABAddressBookAddRecord crashes
+// 6201046 ABRecordSetValue returns true even if you pass in a bad type for a
+// value
+// 6201032 ABRecordSetValue return "true" even if you pass nil as a value
+// 6201005 ABRecordRemoveValue returns true for value that aren't in the record
+// 6200703 ABAddressBookAddRecord doesn't add an item to the people array until
+// it's saved
+// 6200638 ABAddressBookHasUnsavedChanges doesn't work
+
+#import <UIKit/UIKit.h>
+#import <AddressBook/AddressBook.h>
+#import "GTMDefines.h"
+
+#if !GTM_IPHONE_SDK
+#error This file is for iPhone use only use ABAddressBook on Mac OS X
+#endif
+
+@class GTMABPerson;
+@class GTMABGroup;
+@class GTMABRecord;
+
+extern NSString *const kGTMABUnknownPropertyName;
+
+// Wrapper for an AddressBook on iPhone
+@interface GTMABAddressBook : NSObject {
+ @private
+ ABAddressBookRef addressBook_;
+}
+
+// Returns a new instance of an address book.
++ (GTMABAddressBook *)addressBook;
+
+// Return the address book reference
+- (ABAddressBookRef)addressBookRef;
+
+// Saves changes made since the last save
+// Return YES if successful (or there was no change)
+- (BOOL)save;
+
+// Saves changes made since the last save
+// Return YES if successful (or there was no change)
+- (BOOL)saveAndReturnError:(NSError **)error;
+
+// Returns YES if there are unsaved changes
+// The unsaved changes flag is automatically set when changes are made
+// As of iPhone 2.1, this does not work, and will always return NO.
+// Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work
+- (BOOL)hasUnsavedChanges;
+
+// Reverts any changes that have been made and resets the unsaved flag
+// Be sure to read notes for -hasUnsavedChanges and -people and -groups.
+- (void)revert;
+
+// Returns a GTMABPerson matching an ID
+// Returns nil if the record could not be found
+- (GTMABPerson *)personForId:(ABRecordID)uniqueId;
+
+// Returns a GTMABGroup matching an ID
+// Returns nil if the record could not be found
+- (GTMABGroup *)groupForId:(ABRecordID)uniqueId;
+
+// Adds a record (ABPerson or ABGroup) to the AddressBook database
+// Be sure to read notes for -people and -group.
+- (BOOL)addRecord:(GTMABRecord *)record;
+
+// Removes a record (ABPerson or ABGroup) from the AddressBook database
+- (BOOL)removeRecord:(GTMABRecord *)record;
+
+// Returns an array (GTMABPerson) of all the people in the AddressBook database
+// As of iPhone 2.1, this array will not contain new entries until you save
+// the address book.
+// Radar 6200703: ABAddressBookAddRecord doesn't add an item to the people array
+// until it's saved
+- (NSArray *)people;
+
+// Returns an array of all the groups (GTMABGroup) in the AddressBook database
+// As of iPhone 2.1, this array will not contain new entries until you save
+// the address book.
+// Radar 6200703: ABAddressBookAddRecord doesn't add an item to the people array
+// until it's saved
+- (NSArray *)groups;
+
+// Returns a localized name for a given label
++ (NSString *)localizedLabel:(CFStringRef)label;
+
+@end
+
+// Wrapper for a ABRecord on iPhone.
+// A abstract class. Instantiate one of the concrete subclasses, GTMABPerson or
+// GTMABGroup.
+@interface GTMABRecord : NSObject {
+ @private
+ ABRecordRef record_;
+}
+
+// Create a record with a recordRef.
+// Since GTMABRecord is an abstract base class, attempting to create one
+// of these directly will throw an exception. Use with one of the concrete
+// subclasses.
++ (id)recordWithRecord:(ABRecordRef)record;
+
+// Designated initializer
+// Since GTMABRecord is an abstract base class, attempting to create one
+// of these directly will throw an exception. Use with one of the concrete
+// subclasses.
+- (id)initWithRecord:(ABRecordRef)record;
+
+// Return our recordRef
+- (ABRecordRef)recordRef;
+
+// Return the recordID for the record
+- (ABRecordID)recordID;
+
+// Returns the value of a given property.
+// The type of the value depends on the property type.
+- (id)valueForProperty:(ABPropertyID)property;
+
+// Set the value of a given property.
+// The type of the value must match the property type.
+// Returns YES if value set properly
+- (BOOL)setValue:(id)value forProperty:(ABPropertyID)property;
+
+// Removes the value for the property
+// Returns yes if value removed
+- (BOOL)removeValueForProperty:(ABPropertyID)property;
+
+// returns a human friendly name for the record
+- (NSString *)compositeName;
+
+// returns the type of a property
++ (ABPropertyType)typeOfProperty:(ABPropertyID)property;
+
+// returns a human friendly localized name for a property
++ (NSString *)localizedPropertyName:(ABPropertyID)property;
+@end
+
+// Wrapper for an ABPerson on iPhone
+@interface GTMABPerson : GTMABRecord
+
+// Creates a person with a first name and a last name.
++ (GTMABPerson *)personWithFirstName:(NSString *)first
+ lastName:(NSString *)last;
+
+// Sets image data for a person. Data must be to a block of data that
+// will create a valid UIImage.
+- (BOOL)setImageData:(NSData *)data;
+
+// Returns the image data.
+- (NSData *)imageData;
+
+// Returns the image for a person
+- (UIImage *)image;
+
+// Sets a the image for a person
+- (BOOL)setImage:(UIImage *)image;
+
+// Returns the format in with names are composited
++ (ABPersonCompositeNameFormat)compositeNameFormat;
+@end
+
+// Wrapper for a ABGroup on iPhone
+@interface GTMABGroup : GTMABRecord
+// Create a new group named |name|
++ (GTMABGroup *)groupNamed:(NSString *)name;
+
+// Return an array of members (GTMABPerson)
+- (NSArray *)members;
+
+// Add a member to a group
+- (BOOL)addMember:(GTMABPerson *)person;
+
+// Remove a member from a group
+- (BOOL)removeMember:(GTMABPerson *)person;
+@end
+
+// GTMABMultiValue does not supprt NSFastEnumeration because in
+// the Apple frameworks it returns identifiers which are already NSStrings.
+// In our case identifiers aren't NS types, and it doesn't make sense
+// to convert them to NSNumbers just to convert them back so you can
+// actually get at the values and labels.
+// Instead we supply valueEnumerator and labelEnumerator which you can
+// fast enumerate on to get values and labels directly.
+@interface GTMABMultiValue : NSObject <NSCopying, NSMutableCopying> {
+ @protected
+ ABMultiValueRef multiValue_;
+}
+
+// Create a multi value
+- (id)initWithMultiValue:(ABMultiValueRef)multiValue;
+
+// return it's ref
+- (ABMultiValueRef)multiValueRef;
+
+// Returns the number of value/label pairs
+- (NSUInteger)count;
+
+// Returns a value at a given index
+// Returns nil if index is out of bounds
+- (id)valueAtIndex:(NSUInteger)idx;
+
+// Returns a label at a given index
+// Returns nil if index is out of bounds
+- (NSString *)labelAtIndex:(NSUInteger)idx;
+
+// Returns an identifier at a given index
+// Returns kABMultiValueInvalidIdentifier if index is out of bounds
+- (ABMultiValueIdentifier)identifierAtIndex:(NSUInteger)idx;
+
+// Returns the index of a given identifier
+// Returns NSNotFound if not found
+- (NSUInteger)indexForIdentifier:(ABMultiValueIdentifier)identifier;
+
+// Type of the contents of this multivalue
+- (ABPropertyType)propertyType;
+
+// Returns the value for a given identifier
+// Returns nil if the identifier is not found
+- (id)valueForIdentifier:(ABMultiValueIdentifier)identifier;
+
+// Returns the value for a given identifier
+// Returns nil if the identifier is not found
+- (NSString *)labelForIdentifier:(ABMultiValueIdentifier)identifier;
+
+// Returns an enumerator for enumerating through values
+- (NSEnumerator *)valueEnumerator;
+
+// Returns an enumerator for enumerating through labels
+- (NSEnumerator *)labelEnumerator;
+
+@end
+
+@interface GTMABMutableMultiValue : GTMABMultiValue {
+ @private
+ // Use unsigned long here instead of NSUInteger because that's what
+ // NSFastEnumeration Protocol wants currently (iPhone 2.1)
+ unsigned long mutations_;
+}
+
+// Create a new mutable multivalue with a given type
++ (id)valueWithPropertyType:(ABPropertyType)type;
+
+// Create a new mutable multivalue with a given type
+- (id)initWithPropertyType:(ABPropertyType)type;
+
+// Create a new mutable multivalue based on |multiValue|
+- (id)initWithMutableMultiValue:(ABMutableMultiValueRef)multiValue;
+
+// Adds a value with its label
+// Returns the identifier if successful, kABMultiValueInvalidIdentifier
+// otherwise.
+- (ABMultiValueIdentifier)addValue:(id)value withLabel:(CFStringRef)label;
+
+// Insert a value/label pair at a given index
+// Returns the identifier if successful. kABMultiValueInvalidIdentifier
+// otherwise
+// If index is out of bounds, returns kABMultiValueInvalidIdentifier.
+- (ABMultiValueIdentifier)insertValue:(id)value
+ withLabel:(CFStringRef)label
+ atIndex:(NSUInteger)index;
+
+// Removes a value/label pair at a given index
+// Returns NO if index out of bounds
+- (BOOL)removeValueAndLabelAtIndex:(NSUInteger)index;
+
+// Replaces a value at a given index
+// Returns NO if index out of bounds
+- (BOOL)replaceValueAtIndex:(NSUInteger)index withValue:(id)value;
+
+// Replaces a label at a given index
+// Returns NO if index out of bounds
+- (BOOL)replaceLabelAtIndex:(NSUInteger)index withLabel:(CFStringRef)label;
+
+@end
diff --git a/iPhone/GTMABAddressBook.m b/iPhone/GTMABAddressBook.m
new file mode 100644
index 0000000..0c54364
--- /dev/null
+++ b/iPhone/GTMABAddressBook.m
@@ -0,0 +1,902 @@
+//
+// GTMAddressBook.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 "GTMABAddressBook.h"
+#import "GTMGarbageCollection.h"
+
+NSString *const kGTMABUnknownPropertyName = @"UNKNOWN_PROPERTY";
+
+typedef struct {
+ ABPropertyType pType;
+ Class class;
+} TypeClassNameMap;
+
+@interface GTMABMultiValue ()
+- (unsigned long*)mutations;
+@end
+
+@interface GTMABMutableMultiValue ()
+// Checks to see if a value is a valid type to be stored in this multivalue
+- (BOOL)checkValueType:(id)value;
+@end
+
+@interface GTMABMultiValueEnumerator : NSEnumerator {
+ @private
+ __weak ABMultiValueRef ref_; // ref_ cached from enumeree_
+ GTMABMultiValue *enumeree_;
+ unsigned long mutations_;
+ NSUInteger count_;
+ NSUInteger index_;
+ BOOL useLabels_;
+}
++ (id)valueEnumeratorFor:(GTMABMultiValue*)enumeree;
++ (id)labelEnumeratorFor:(GTMABMultiValue*)enumeree;
+- (id)initWithEnumeree:(GTMABMultiValue*)enumeree useLabels:(BOOL)useLabels;
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+ objects:(id *)stackbuf
+ count:(NSUInteger)len;
+@end
+
+@implementation GTMABAddressBook
++ (GTMABAddressBook *)addressBook {
+ return [[[self alloc] init] autorelease];
+}
+
+- (id)init {
+ if ((self = [super init])) {
+ addressBook_ = ABAddressBookCreate();
+ if (!addressBook_) {
+ // COV_NF_START
+ [self release];
+ self = nil;
+ // COV_NF_END
+ }
+ }
+ return self;
+}
+
+- (void)dealloc {
+ if (addressBook_) {
+ CFRelease(addressBook_);
+ }
+ [super dealloc];
+}
+
+- (BOOL)save {
+ return [self saveAndReturnError:NULL];
+}
+
+- (BOOL)saveAndReturnError:(NSError **)error {
+ CFErrorRef cfError = NULL;
+ bool wasGood = ABAddressBookSave(addressBook_, &cfError);
+ GTMCFAutorelease(cfError);
+ if (error) {
+ *error = (NSError *)cfError; // COV_NF_LINE
+ }
+ return wasGood ? YES : NO;
+}
+
+- (BOOL)hasUnsavedChanges {
+ return ABAddressBookHasUnsavedChanges(addressBook_);
+}
+
+- (void)revert {
+ ABAddressBookRevert(addressBook_);
+}
+
+- (BOOL)addRecord:(GTMABRecord *)record {
+ // Note: we check for bad data here because of radar
+ // 6201258 Adding a NULL record using ABAddressBookAddRecord crashes
+ if (!record) return NO;
+ CFErrorRef cfError = NULL;
+ bool wasGood = ABAddressBookAddRecord(addressBook_,
+ [record recordRef], &cfError);
+ if (cfError) {
+ // COV_NF_START
+ _GTMDevLog(@"Error in [%@ %@]: %@",
+ [self class], NSStringFromSelector(_cmd), cfError);
+ CFRelease(cfError);
+ // COV_NF_END
+ }
+ return wasGood ? YES : NO;
+}
+
+- (BOOL)removeRecord:(GTMABRecord *)record {
+ // Note: we check for bad data here because of radar
+ // 6201276 Removing a NULL record using ABAddressBookRemoveRecord crashes
+ if (!record) return NO;
+ CFErrorRef cfError = NULL;
+ bool wasGood = ABAddressBookRemoveRecord(addressBook_,
+ [record recordRef], &cfError);
+ if (cfError) {
+ // COV_NF_START
+ _GTMDevLog(@"Error in [%@ %@]: %@",
+ [self class], NSStringFromSelector(_cmd), cfError);
+ CFRelease(cfError);
+ // COV_NF_END
+ }
+ return wasGood ? YES : NO;
+}
+
+- (NSArray *)people {
+ NSArray *people
+ = GTMCFAutorelease(ABAddressBookCopyArrayOfAllPeople(addressBook_));
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:[people count]];
+ for (id person in people) {
+ [result addObject:[GTMABPerson recordWithRecord:person]];
+ }
+ return result;
+}
+
+- (NSArray *)groups {
+ NSArray *groups
+ = GTMCFAutorelease(ABAddressBookCopyArrayOfAllGroups(addressBook_));
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:[groups count]];
+ for (id group in groups) {
+ [result addObject:[GTMABGroup recordWithRecord:group]];
+ }
+ return result;
+}
+
+- (ABAddressBookRef)addressBookRef {
+ return addressBook_;
+}
+
+- (GTMABPerson *)personForId:(ABRecordID)uniqueId {
+ GTMABPerson *person = nil;
+ ABRecordRef ref = ABAddressBookGetPersonWithRecordID(addressBook_, uniqueId);
+ if (ref) {
+ person = [GTMABPerson recordWithRecord:ref];
+ }
+ return person;
+}
+
+- (GTMABGroup *)groupForId:(ABRecordID)uniqueId {
+ GTMABGroup *group = nil;
+ ABRecordRef ref = ABAddressBookGetGroupWithRecordID(addressBook_, uniqueId);
+ if (ref) {
+ group = [GTMABGroup recordWithRecord:ref];
+ }
+ return group;
+}
+
++ (NSString *)localizedLabel:(CFStringRef)label {
+ return GTMCFAutorelease(ABAddressBookCopyLocalizedLabel(label));
+}
+
+@end
+
+@implementation GTMABRecord
++ (id)recordWithRecord:(ABRecordRef)record {
+ return [[[self alloc] initWithRecord:record] autorelease];
+}
+
+- (id)initWithRecord:(ABRecordRef)record {
+ if ((self = [super init])) {
+ if ([self class] == [GTMABRecord class]) {
+ [self doesNotRecognizeSelector:_cmd];
+ }
+ if (!record) {
+ [self release];
+ self = nil;
+ } else {
+ record_ = CFRetain(record);
+ }
+ }
+ return self;
+}
+
+- (NSUInteger)hash {
+ // This really isn't completely valid due to
+ // 6203836 ABRecords hash to their address
+ // but it's the best we can do without knowing what properties
+ // are in a record, and we don't have an API for that.
+ return CFHash(record_);
+}
+
+- (BOOL)isEqual:(id)object {
+ // This really isn't completely valid due to
+ // 6203836 ABRecords hash to their address
+ // but it's the best we can do without knowing what properties
+ // are in a record, and we don't have an API for that.
+ return [object respondsToSelector:@selector(recordRef)]
+ && CFEqual(record_, [object recordRef]);
+}
+
+- (void)dealloc {
+ if (record_) {
+ CFRelease(record_);
+ }
+ [super dealloc];
+}
+
+- (ABRecordRef)recordRef {
+ return record_;
+}
+
+- (ABRecordID)recordID {
+ return ABRecordGetRecordID(record_);
+}
+
+- (id)valueForProperty:(ABPropertyID)property {
+ id value = GTMCFAutorelease(ABRecordCopyValue(record_, property));
+ if (value) {
+ if ([[self class] typeOfProperty:property] & kABMultiValueMask) {
+ value = [[[GTMABMultiValue alloc] initWithMultiValue:value] autorelease];
+ }
+ }
+ return value;
+}
+
+- (BOOL)setValue:(id)value forProperty:(ABPropertyID)property {
+ if (!value) return NO;
+ // We check the type here because of
+ // Radar 6201046 ABRecordSetValue returns true even if you pass in a bad type
+ // for a value
+ TypeClassNameMap fullTypeMap[] = {
+ { kABStringPropertyType, [NSString class] },
+ { kABIntegerPropertyType, [NSNumber class] },
+ { kABRealPropertyType, [NSNumber class] },
+ { kABDateTimePropertyType, [NSDate class] },
+ { kABDictionaryPropertyType, [NSDictionary class] },
+ { kABMultiStringPropertyType, [GTMABMultiValue class] },
+ { kABMultiRealPropertyType, [GTMABMultiValue class] },
+ { kABMultiDateTimePropertyType, [GTMABMultiValue class] },
+ { kABMultiDictionaryPropertyType, [GTMABMultiValue class] }
+ };
+ ABPropertyType type = [[self class] typeOfProperty:property];
+ BOOL wasFound = NO;
+ for (size_t i = 0; i < sizeof(fullTypeMap) / sizeof(TypeClassNameMap); ++i) {
+ if (fullTypeMap[i].pType == type) {
+ wasFound = YES;
+ if (![[value class] isSubclassOfClass:fullTypeMap[i].class]) {
+ return NO;
+ }
+ }
+ }
+ if (!wasFound) {
+ return NO;
+ }
+ if (type & kABMultiValueMask) {
+ value = (id)[value multiValueRef];
+ }
+ CFErrorRef cfError = nil;
+ bool wasGood = ABRecordSetValue(record_, property, (CFTypeRef)value, &cfError);
+ if (cfError) {
+ // COV_NF_START
+ _GTMDevLog(@"Error in [%@ %@]: %@",
+ [self class], NSStringFromSelector(_cmd), cfError);
+ CFRelease(cfError);
+ // COV_NF_END
+ }
+ return wasGood ? YES : NO;
+}
+
+- (BOOL)removeValueForProperty:(ABPropertyID)property {
+ CFErrorRef cfError = nil;
+ // We check to see if the value is in the property because of:
+ // Radar 6201005 ABRecordRemoveValue returns true for value that aren't
+ // in the record
+ id value = [self valueForProperty:property];
+ bool wasGood = value && ABRecordRemoveValue(record_, property, &cfError);
+ if (cfError) {
+ // COV_NF_START
+ _GTMDevLog(@"Error in [%@ %@]: %@",
+ [self class], NSStringFromSelector(_cmd), cfError);
+ CFRelease(cfError);
+ // COV_NF_END
+ }
+ return wasGood ? YES : NO;
+}
+
+- (NSString *)compositeName {
+ return GTMCFAutorelease(ABRecordCopyCompositeName(record_));
+}
+
+// COV_NF_START
+// Both of these methods are to be overridden by their subclasses
++ (ABPropertyType)typeOfProperty:(ABPropertyID)property {
+ [self doesNotRecognizeSelector:_cmd];
+ return kABInvalidPropertyType;
+}
+
++ (NSString *)localizedPropertyName:(ABPropertyID)property {
+ [self doesNotRecognizeSelector:_cmd];
+ return nil;
+}
+// COV_NF_END
+@end
+
+@implementation GTMABPerson
+
++ (GTMABPerson *)personWithFirstName:(NSString *)first
+ lastName:(NSString *)last {
+ GTMABPerson *person = [[[self alloc] init] autorelease];
+ if (person) {
+ BOOL isGood = YES;
+ if (first) {
+ isGood = [person setValue:first forProperty:kABPersonFirstNameProperty];
+ }
+ if (isGood && last) {
+ isGood = [person setValue:last forProperty:kABPersonLastNameProperty];
+ }
+ if (!isGood) {
+ // COV_NF_START
+ // Marked as NF because I don't know how to force an error
+ [person release];
+ person = nil;
+ // COV_NF_END
+ }
+ }
+ return person;
+}
+
+- (id)init {
+ ABRecordRef person = ABPersonCreate();
+ self = [super initWithRecord:person];
+ if (person) {
+ CFRelease(person);
+ }
+ return self;
+}
+
+- (BOOL)setImageData:(NSData *)data {
+ CFErrorRef cfError = NULL;
+ bool wasGood = NO;
+ if (!data) {
+ wasGood = ABPersonRemoveImageData([self recordRef], &cfError);
+ } else {
+ // We verify that the data is good because of:
+ // Radar 6202868 ABPersonSetImageData should validate image data
+ UIImage *image = [UIImage imageWithData:data];
+ wasGood = image && ABPersonSetImageData([self recordRef],
+ (CFDataRef)data, &cfError);
+ }
+ if (cfError) {
+ // COV_NF_START
+ _GTMDevLog(@"Error in [%@ %@]: %@",
+ [self class], NSStringFromSelector(_cmd), cfError);
+ CFRelease(cfError);
+ // COV_NF_END
+ }
+ return wasGood ? YES : NO;
+}
+
+- (UIImage *)image {
+ return [UIImage imageWithData:[self imageData]];
+}
+
+- (BOOL)setImage:(UIImage *)image {
+ NSData *data = UIImagePNGRepresentation(image);
+ return [self setImageData:data];
+}
+
+- (NSData *)imageData {
+ return GTMCFAutorelease(ABPersonCopyImageData([self recordRef]));
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"%@ %@ %@ %d",
+ [self class],
+ [self valueForProperty:kABPersonFirstNameProperty],
+ [self valueForProperty:kABPersonLastNameProperty],
+ [self recordID]];
+}
+
++ (NSString *)localizedPropertyName:(ABPropertyID)property {
+ return GTMCFAutorelease(ABPersonCopyLocalizedPropertyName(property));
+}
+
++ (ABPersonCompositeNameFormat)compositeNameFormat {
+ return ABPersonGetCompositeNameFormat();
+}
+
++ (ABPropertyType)typeOfProperty:(ABPropertyID)property {
+ return ABPersonGetTypeOfProperty(property);
+}
+@end
+
+@implementation GTMABGroup
+
++ (GTMABGroup *)groupNamed:(NSString *)name {
+ GTMABGroup *group = [[[self alloc] init] autorelease];
+ if (group) {
+ if (![group setValue:name forProperty:kABGroupNameProperty]) {
+ // COV_NF_START
+ // Can't get setValue to fail for me
+ [group release];
+ group = nil;
+ // COV_NF_END
+ }
+ }
+ return group;
+}
+
+- (id)init {
+ ABRecordRef group = ABGroupCreate();
+ self = [super initWithRecord:group];
+ if (group) {
+ CFRelease(group);
+ }
+ return self;
+}
+
+- (NSArray *)members {
+ NSArray *people
+ = GTMCFAutorelease(ABGroupCopyArrayOfAllMembers([self recordRef]));
+ NSMutableArray *gtmPeople = [NSMutableArray arrayWithCapacity:[people count]];
+ NSEnumerator *enumerator = [people objectEnumerator];
+ ABRecordRef person;
+ while ((person = [enumerator nextObject])) {
+ [gtmPeople addObject:[GTMABPerson recordWithRecord:person]];
+ }
+ return gtmPeople;
+}
+
+- (BOOL)addMember:(GTMABPerson *)person {
+ CFErrorRef cfError = nil;
+ // We check for person because of
+ // Radar 6202860 Passing nil person into ABGroupAddMember crashes
+ bool wasGood = person && ABGroupAddMember([self recordRef],
+ [person recordRef], &cfError);
+ if (cfError) {
+ // COV_NF_START
+ _GTMDevLog(@"Error in [%@ %@]: %@",
+ [self class], NSStringFromSelector(_cmd), cfError);
+ CFRelease(cfError);
+ // COV_NF_END
+ }
+ return wasGood ? YES : NO;
+}
+
+- (BOOL)removeMember:(GTMABPerson *)person {
+ CFErrorRef cfError = nil;
+ // We check for person because of
+ // Radar 6202860 Passing nil person into ABGroupAddMember crashes
+ // (I know this is remove, but it crashes there too)
+ bool wasGood = person && ABGroupRemoveMember([self recordRef],
+ [person recordRef], &cfError);
+ if (cfError) {
+ // COV_NF_START
+ _GTMDevLog(@"Error in [%@ %@]: %@",
+ [self class], NSStringFromSelector(_cmd), cfError);
+ CFRelease(cfError);
+ // COV_NF_END
+ }
+ return wasGood ? YES : NO;
+}
+
++ (ABPropertyType)typeOfProperty:(ABPropertyID)property {
+ ABPropertyType type = kABInvalidPropertyType;
+ if (property == kABGroupNameProperty) {
+ type = kABStringPropertyType;
+ }
+ return type;
+}
+
++ (NSString *)localizedPropertyName:(ABPropertyID)property {
+ NSString *name = kGTMABUnknownPropertyName;
+ if (property == kABGroupNameProperty) {
+ name = NSLocalizedStringFromTable(@"Name",
+ @"GTMABAddressBook",
+ @"name property");
+ }
+ return name;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"%@ %@ %d",
+ [self class],
+ [self valueForProperty:kABGroupNameProperty],
+ [self recordID]];
+}
+@end
+
+@implementation GTMABMultiValue
+- (id)init {
+ [self doesNotRecognizeSelector:_cmd];
+ return nil; // COV_NF_LINE
+}
+
+- (id)initWithMultiValue:(ABMultiValueRef)multiValue {
+ if ((self = [super init])) {
+ if (!multiValue) {
+ [self release];
+ self = nil;
+ } else {
+ multiValue_ = CFRetain(multiValue);
+ }
+ }
+ return self;
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+ return [[GTMABMultiValue alloc] initWithMultiValue:multiValue_];
+}
+
+- (id)mutableCopyWithZone:(NSZone *)zone {
+ return [[GTMABMutableMultiValue alloc] initWithMultiValue:multiValue_];
+}
+
+- (NSUInteger)hash {
+ // I'm implementing hash instead of using CFHash(multiValue_) because
+ // 6203854 ABMultiValues hash to their address
+ NSUInteger count = [self count];
+ NSUInteger hash = 0;
+ for (NSUInteger i = 0; i < count; ++i) {
+ NSString *label = [self labelAtIndex:i];
+ id value = [self valueAtIndex:i];
+ hash += [label hash];
+ hash += [value hash];
+ }
+ return hash;
+}
+
+- (BOOL)isEqual:(id)object {
+ // I'm implementing isEqual instea of using CFEquals(multiValue,...) because
+ // 6203854 ABMultiValues hash to their address
+ // and it appears CFEquals just calls through to hash to compare them.
+ BOOL isEqual = NO;
+ if ([object respondsToSelector:@selector(multiValueRef)]) {
+ isEqual = multiValue_ == [object multiValueRef];
+ if (!isEqual) {
+ NSUInteger count = [self count];
+ NSUInteger objCount = [object count];
+ isEqual = count == objCount;
+ for (NSUInteger i = 0; isEqual && i < count; ++i) {
+ NSString *label = [self labelAtIndex:i];
+ NSString *objLabel = [object labelAtIndex:i];
+ isEqual = [label isEqual:objLabel];
+ if (isEqual) {
+ id value = [self valueAtIndex:i];
+ id objValue = [object valueAtIndex:i];
+ isEqual = [value isEqual:objValue];
+ }
+ }
+ }
+ }
+ return isEqual;
+}
+
+- (void)dealloc {
+ if (multiValue_) {
+ CFRelease(multiValue_);
+ }
+ [super dealloc];
+}
+
+- (ABMultiValueRef)multiValueRef {
+ return multiValue_;
+}
+
+- (NSUInteger)count {
+ return ABMultiValueGetCount(multiValue_);
+}
+
+- (id)valueAtIndex:(NSUInteger)idx {
+ id value = nil;
+ if (idx < [self count]) {
+ value = GTMCFAutorelease(ABMultiValueCopyValueAtIndex(multiValue_, idx));
+ ABPropertyType type = [self propertyType];
+ if (type == kABIntegerPropertyType
+ || type == kABRealPropertyType
+ || type == kABDictionaryPropertyType) {
+ // This is because of
+ // 6208390 Integer and real values don't work in ABMultiValueRefs
+ // Apparently they forget to add a ref count on int, real and
+ // dictionary values in ABMultiValueCopyValueAtIndex, although they do
+ // remember them for all other types.
+ // Once they fix this, this will lead to a leak, but I figure the leak
+ // is better than the crash. Our unittests will test to make sure that
+ // this is the case, and once we find a system that has this fixed, we
+ // can conditionalize this code. Look for testRadar6208390 in
+ // GTMABAddressBookTest.m
+ // Also, search for 6208390 below and fix the fast enumerator to actually
+ // be somewhat performant when this is fixed.
+ [value retain];
+ }
+ }
+ return value;
+}
+
+- (NSString *)labelAtIndex:(NSUInteger)idx {
+ NSString *label = nil;
+ if (idx < [self count]) {
+ label = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(multiValue_, idx));
+ }
+ return label;
+}
+
+- (ABMultiValueIdentifier)identifierAtIndex:(NSUInteger)idx {
+ ABMultiValueIdentifier identifier = kABMultiValueInvalidIdentifier;
+ if (idx < [self count]) {
+ identifier = ABMultiValueGetIdentifierAtIndex(multiValue_, idx);
+ }
+ return identifier;
+}
+
+- (NSUInteger)indexForIdentifier:(ABMultiValueIdentifier)identifier {
+ NSUInteger idx = ABMultiValueGetIndexForIdentifier(multiValue_, identifier);
+ return idx == (NSUInteger)kCFNotFound ? (NSUInteger)NSNotFound : idx;
+}
+
+- (ABPropertyType)propertyType {
+ return ABMultiValueGetPropertyType(multiValue_);
+}
+
+- (id)valueForIdentifier:(ABMultiValueIdentifier)identifier {
+ return [self valueAtIndex:[self indexForIdentifier:identifier]];
+}
+
+- (NSString *)labelForIdentifier:(ABMultiValueIdentifier)identifier {
+ return [self labelAtIndex:[self indexForIdentifier:identifier]];
+}
+
+- (unsigned long*)mutations {
+ // We just need some constant non-zero value here so fast enumeration works.
+ // Dereferencing self should give us the isa which will stay constant
+ // over the enumeration.
+ return (unsigned long*)self;
+}
+
+- (NSEnumerator *)valueEnumerator {
+ return [GTMABMultiValueEnumerator valueEnumeratorFor:self];
+}
+
+- (NSEnumerator *)labelEnumerator {
+ return [GTMABMultiValueEnumerator labelEnumeratorFor:self];
+}
+
+@end
+
+@implementation GTMABMutableMultiValue
++ (id)valueWithPropertyType:(ABPropertyType)type {
+ return [[[self alloc] initWithPropertyType:type] autorelease];
+}
+
+- (id)initWithPropertyType:(ABPropertyType)type {
+ ABMutableMultiValueRef ref = nil;
+ if (!(type & kABMultiValueMask) && (type != kABInvalidPropertyType)) {
+ ref = ABMultiValueCreateMutable(type);
+ }
+ self = [super initWithMultiValue:ref];
+ if (ref) {
+ CFRelease(ref);
+ }
+ return self;
+}
+
+- (id)initWithMultiValue:(ABMultiValueRef)multiValue {
+ ABMutableMultiValueRef ref = nil;
+ if (multiValue) {
+ ref = ABMultiValueCreateMutableCopy(multiValue);
+ }
+ self = [super initWithMultiValue:ref];
+ if (ref) {
+ CFRelease(ref);
+ }
+ return self;
+}
+
+- (id)initWithMutableMultiValue:(ABMutableMultiValueRef)multiValue {
+ return [super initWithMultiValue:multiValue];
+}
+
+- (BOOL)checkValueType:(id)value {
+ BOOL isGood = NO;
+ if (value) {
+ TypeClassNameMap singleValueTypeMap[] = {
+ { kABStringPropertyType, [NSString class] },
+ { kABIntegerPropertyType, [NSNumber class] },
+ { kABRealPropertyType, [NSNumber class] },
+ { kABDateTimePropertyType, [NSDate class] },
+ { kABDictionaryPropertyType, [NSDictionary class] },
+ };
+ ABPropertyType type = [self propertyType];
+ for (size_t i = 0;
+ i < sizeof(singleValueTypeMap) / sizeof(TypeClassNameMap); ++i) {
+ if (singleValueTypeMap[i].pType == type) {
+ if ([[value class] isSubclassOfClass:singleValueTypeMap[i].class]) {
+ isGood = YES;
+ break;
+ }
+ }
+ }
+ }
+ return isGood;
+}
+
+- (ABMultiValueIdentifier)addValue:(id)value withLabel:(CFStringRef)label {
+ ABMultiValueIdentifier identifier = kABMultiValueInvalidIdentifier;
+ // We check label and value here because of
+ // radar 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash
+ if (!label
+ || ![self checkValueType:value]
+ || !ABMultiValueAddValueAndLabel(multiValue_,
+ value,
+ label,
+ &identifier)) {
+ identifier = kABMultiValueInvalidIdentifier;
+ } else {
+ mutations_++;
+ }
+ return identifier;
+}
+
+- (ABMultiValueIdentifier)insertValue:(id)value
+ withLabel:(CFStringRef)label
+ atIndex:(NSUInteger)idx {
+ ABMultiValueIdentifier identifier = kABMultiValueInvalidIdentifier;
+ // We perform a check here to ensure that we don't get bitten by
+ // Radar 6202807 ABMultiValueInsertValueAndLabelAtIndex allows you to insert
+ // values past end
+ NSUInteger count = [self count];
+ // We check label and value here because of
+ // radar 6202827 Passing nil info ABMultiValueAddValueAndLabel causes crash
+ if (idx > count
+ || !label
+ || ![self checkValueType:value]
+ || !ABMultiValueInsertValueAndLabelAtIndex(multiValue_,
+ value,
+ label,
+ idx,
+ &identifier)) {
+ identifier = kABMultiValueInvalidIdentifier;
+ } else {
+ mutations_++;
+ }
+ return identifier;
+}
+
+- (BOOL)removeValueAndLabelAtIndex:(NSUInteger)idx {
+ BOOL isGood = NO;
+ NSUInteger count = [self count];
+ if (idx < count) {
+ if (ABMultiValueRemoveValueAndLabelAtIndex(multiValue_,
+ idx)) {
+ mutations_++;
+ isGood = YES;
+ }
+ }
+ return isGood;
+}
+
+- (BOOL)replaceValueAtIndex:(NSUInteger)idx withValue:(id)value {
+ BOOL isGood = NO;
+ NSUInteger count = [self count];
+ if (idx < count && [self checkValueType:value]) {
+ if (ABMultiValueReplaceValueAtIndex(multiValue_,
+ value, idx)) {
+ mutations_++;
+ isGood = YES;
+ }
+ }
+ return isGood;
+}
+
+- (BOOL)replaceLabelAtIndex:(NSUInteger)idx withLabel:(CFStringRef)label{
+ BOOL isGood = NO;
+ NSUInteger count = [self count];
+ if (idx < count) {
+ if (ABMultiValueReplaceLabelAtIndex(multiValue_,
+ label,
+ idx)) {
+ mutations_++;
+ isGood = YES;
+ }
+ }
+ return isGood;
+}
+
+- (unsigned long*)mutations {
+ return &mutations_;
+}
+@end
+
+
+@implementation GTMABMultiValueEnumerator
+
++ (id)valueEnumeratorFor:(GTMABMultiValue*)enumeree {
+ return [[[self alloc] initWithEnumeree:enumeree useLabels:NO] autorelease];
+}
+
++ (id)labelEnumeratorFor:(GTMABMultiValue*)enumeree {
+ return [[[self alloc] initWithEnumeree:enumeree useLabels:YES] autorelease];
+}
+
+- (id)initWithEnumeree:(GTMABMultiValue*)enumeree useLabels:(BOOL)useLabels {
+ if ((self = [super init])) {
+ if (enumeree) {
+ enumeree_ = [enumeree retain];
+ useLabels_ = useLabels;
+ } else {
+ // COV_NF_START
+ // Since this is a private class where the enumeree creates us
+ // there is no way we should ever get here.
+ [self release];
+ self = nil;
+ // COV_NF_END
+ }
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [enumeree_ release];
+ [super dealloc];
+}
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+ objects:(id *)stackbuf
+ count:(NSUInteger)len {
+ NSUInteger i;
+ if (!ref_) {
+ count_ = [enumeree_ count];
+ ref_ = [enumeree_ multiValueRef];
+ }
+
+ for (i = 0; state->state < count_ && i < len; ++i, ++state->state) {
+ if (useLabels_) {
+ stackbuf[i] = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(ref_,
+ state->state));
+ } else {
+ // Yes this is slow, but necessary in light of radar 6208390
+ // Once this is fixed we can go to something similar to the label
+ // case which should speed stuff up again. Hopefully anybody who wants
+ // real performance is willing to move down to the C API anyways.
+ stackbuf[i] = [enumeree_ valueAtIndex:state->state];
+ }
+ }
+
+ state->itemsPtr = stackbuf;
+ state->mutationsPtr = [enumeree_ mutations];
+ return i;
+}
+
+- (id)nextObject {
+ id value = nil;
+ if (!ref_) {
+ count_ = [enumeree_ count];
+ mutations_ = *[enumeree_ mutations];
+ ref_ = [enumeree_ multiValueRef];
+
+ }
+ if (mutations_ != *[enumeree_ mutations]) {
+ NSString *reason = [NSString stringWithFormat:@"*** Collection <%@> was "
+ "mutated while being enumerated", enumeree_];
+ [[NSException exceptionWithName:NSGenericException
+ reason:reason
+ userInfo:nil] raise];
+ }
+ if (index_ < count_) {
+ if (useLabels_) {
+ value = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(ref_,
+ index_));
+ } else {
+ // Yes this is slow, but necessary in light of radar 6208390
+ // Once this is fixed we can go to something similar to the label
+ // case which should speed stuff up again. Hopefully anybody who wants
+ // real performance is willing to move down to the C API anyways.
+ value = [enumeree_ valueAtIndex:index_];
+ }
+ index_ += 1;
+ }
+ return value;
+}
+@end
+
diff --git a/iPhone/GTMABAddressBook.strings b/iPhone/GTMABAddressBook.strings
new file mode 100644
index 0000000..b5e010e
--- /dev/null
+++ b/iPhone/GTMABAddressBook.strings
Binary files differ
diff --git a/iPhone/GTMABAddressBookTest.m b/iPhone/GTMABAddressBookTest.m
new file mode 100644
index 0000000..3ecb796
--- /dev/null
+++ b/iPhone/GTMABAddressBookTest.m
@@ -0,0 +1,558 @@
+//
+// GTMAddressBookTest.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 "GTMSenTestCase.h"
+#import "GTMABAddressBook.h"
+
+@interface GTMABAddressBookTest : GTMTestCase {
+ @private
+ GTMABAddressBook *book_;
+}
+@end
+
+
+@implementation GTMABAddressBookTest
+- (void)setUp {
+ // Create a book forcing it out of it's autorelease pool.
+ // I force it out of the release pool, so that we will see any errors
+ // for it immediately at teardown, and it will be clear which release
+ // caused us problems.
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ book_ = [[GTMABAddressBook addressBook] retain];
+ [pool release];
+ STAssertNotNil(book_, nil);
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+}
+
+- (void)tearDown {
+ [book_ release];
+}
+
+- (void)testGenericAddressBook {
+ STAssertEqualObjects([GTMABAddressBook localizedLabel:kABHomeLabel],
+ @"home",
+ nil);
+ STAssertThrows([GTMABRecord recordWithRecord:nil], nil);
+}
+
+- (void)testAddingAndRemovingPerson {
+ // Create a person
+ GTMABPerson *person = [GTMABPerson personWithFirstName:@"Bart"
+ lastName:@"Simpson"];
+ STAssertNotNil(person, nil);
+
+ // Add person
+ NSArray *people = [book_ people];
+ STAssertFalse([people containsObject:person], nil);
+ STAssertTrue([book_ addRecord:person], nil);
+
+ // Normally this next line would be STAssertTrue, however due to
+ // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work
+ // We will check to make sure it stays broken ;-)
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+
+ people = [book_ people];
+ STAssertNotNil(people, nil);
+ // Normally this next line would be STAssertTrue, however due to
+ // Radar 6200703: ABAddressBookAddRecord doesn't add an item to the people
+ // array until it's saved
+ // We will check to make sure it stays broken ;-)
+ STAssertFalse([people containsObject:person], nil);
+
+ // Save book_
+ STAssertTrue([book_ save], nil);
+ people = [book_ people];
+ STAssertNotNil(people, nil);
+ STAssertTrue([people containsObject:person], nil);
+
+ ABRecordID recordID = [person recordID];
+ STAssertNotEquals(recordID, kABRecordInvalidID, nil);
+
+ GTMABRecord *record = [book_ personForId:recordID];
+ STAssertEqualObjects(record, person, nil);
+
+ // Remove person
+ STAssertTrue([book_ removeRecord:person], nil);
+ // Normally this next line would be STAssertTrue, however due to
+ // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work
+ // We will check to make sure it stays broken ;-)
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+
+ // Normally this next line would be STAssertFalse, however due to
+ // Radar 6200703: ABAddressBookAddRecord doesn't add an item to the people
+ // array until it's saved
+ // We will check to make sure it stays broken ;-)
+ STAssertTrue([people containsObject:person], nil);
+
+ // Save Book
+ STAssertTrue([book_ save], nil);
+ people = [book_ people];
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+ STAssertFalse([people containsObject:person], nil);
+ record = [book_ personForId:recordID];
+ STAssertNil(record, nil);
+
+ // Revert book_
+ STAssertTrue([book_ addRecord:person], nil);
+ // Normally this next line would be STAssertTrue, however due to
+ // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work
+ // We will check to make sure it stays broken ;-)
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+
+ [book_ revert];
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+
+ // Bogus data
+ STAssertFalse([book_ addRecord:nil], nil);
+ STAssertFalse([book_ removeRecord:nil], nil);
+
+ STAssertNotNULL([book_ addressBookRef], nil);
+
+}
+
+- (void)testAddingAndRemovingGroup {
+ // Create a group
+ GTMABGroup *group = [GTMABGroup groupNamed:@"Test"];
+ STAssertNotNil(group, nil);
+
+ // Add group
+ NSArray *groups = [book_ groups];
+ STAssertFalse([groups containsObject:group], nil);
+ STAssertTrue([book_ addRecord:group], nil);
+
+ // Normally this next line would be STAssertTrue, however due to
+ // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work
+ // We will check to make sure it stays broken ;-)
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+
+ groups = [book_ groups];
+ STAssertNotNil(groups, nil);
+ // Normally this next line would be STAssertTrue, however due to
+ // Radar 6200703: ABAddressBookAddRecord doesn't add an item to the groups
+ // array until it's saved
+ // We will check to make sure it stays broken ;-)
+ STAssertFalse([groups containsObject:group], nil);
+
+ // Save book_
+ STAssertTrue([book_ save], nil);
+ groups = [book_ groups];
+ STAssertNotNil(groups, nil);
+ STAssertTrue([groups containsObject:group], nil);
+
+ ABRecordID recordID = [group recordID];
+ STAssertNotEquals(recordID, kABRecordInvalidID, nil);
+
+ GTMABRecord *record = [book_ groupForId:recordID];
+ STAssertEqualObjects(record, group, nil);
+
+ // Remove group
+ STAssertTrue([book_ removeRecord:group], nil);
+ // Normally this next line would be STAssertTrue, however due to
+ // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work
+ // We will check to make sure it stays broken ;-)
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+
+ // Normally this next line would be STAssertFalse, however due to
+ // Radar 6200703: ABAddressBookAddRecord doesn't add an item to the groups
+ // array until it's saved
+ // We will check to make sure it stays broken ;-)
+ STAssertTrue([groups containsObject:group], nil);
+
+ // Save Book
+ STAssertTrue([book_ save], nil);
+ groups = [book_ groups];
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+ STAssertFalse([groups containsObject:group], nil);
+ record = [book_ groupForId:recordID];
+ STAssertNil(record, nil);
+
+ // Revert book_
+ STAssertTrue([book_ addRecord:group], nil);
+ // Normally this next line would be STAssertTrue, however due to
+ // Radar 6200638: ABAddressBookHasUnsavedChanges doesn't work
+ // We will check to make sure it stays broken ;-)
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+
+ [book_ revert];
+ STAssertFalse([book_ hasUnsavedChanges], nil);
+}
+
+- (void)testPerson {
+ GTMABPerson *person = [[[GTMABPerson alloc] initWithRecord:nil] autorelease];
+ STAssertNil(person, nil);
+ person = [GTMABPerson personWithFirstName:@"Bart"
+ lastName:nil];
+ STAssertNotNil(person, nil);
+ STAssertEqualObjects([person compositeName], @"Bart", nil);
+ NSString *firstName = [person valueForProperty:kABPersonFirstNameProperty];
+ STAssertEqualObjects(firstName, @"Bart", nil);
+ NSString *lastName = [person valueForProperty:kABPersonLastNameProperty];
+ STAssertNil(lastName, nil);
+ STAssertTrue([person removeValueForProperty:kABPersonFirstNameProperty], nil);
+ STAssertFalse([person removeValueForProperty:kABPersonFirstNameProperty], nil);
+ STAssertFalse([person removeValueForProperty:kABPersonLastNameProperty], nil);
+ STAssertFalse([person setValue:nil forProperty:kABPersonFirstNameProperty], nil);
+ STAssertFalse([person setValue:[NSNumber numberWithInt:1]
+ forProperty:kABPersonFirstNameProperty], nil);
+ STAssertFalse([person setValue:@"Bart"
+ forProperty:kABPersonBirthdayProperty], nil);
+
+ ABPropertyType property
+ = [GTMABPerson typeOfProperty:kABPersonLastNameProperty];
+ STAssertEquals(property, (ABPropertyType)kABStringPropertyType, nil);
+
+ NSString *string
+ = [GTMABPerson localizedPropertyName:kABPersonLastNameProperty];
+ STAssertEqualObjects(string, @"Last", nil);
+
+ string = [GTMABPerson localizedPropertyName:kABRecordInvalidID];
+ STAssertEqualObjects(string, kGTMABUnknownPropertyName, nil);
+
+ string = [person description];
+ STAssertNotNil(string, nil);
+
+ ABPersonCompositeNameFormat format = [GTMABPerson compositeNameFormat];
+ STAssertTrue(format == kABPersonCompositeNameFormatFirstNameFirst ||
+ format == kABPersonCompositeNameFormatLastNameFirst, nil);
+
+ NSData *data = [person imageData];
+ STAssertNil(data, nil);
+ STAssertTrue([person setImageData:nil], nil);
+ data = [person imageData];
+ STAssertNil(data, nil);
+ UIImage *image = [UIImage imageNamed:@"phone.png"];
+ STAssertNotNil(image, nil);
+ data = UIImagePNGRepresentation(image);
+ STAssertTrue([person setImageData:data], nil);
+ NSData *data2 = [person imageData];
+ STAssertEqualObjects(data, data2, nil);
+ STAssertTrue([person setImageData:nil], nil);
+ data = [person imageData];
+ STAssertNil(data, nil);
+
+ STAssertTrue([person setImage:image], nil);
+ UIImage *image2 = [person image];
+ STAssertNotNil(image2, nil);
+ STAssertEqualObjects(UIImagePNGRepresentation(image),
+ UIImagePNGRepresentation(image2), nil);
+
+ person = [GTMABPerson personWithFirstName:@"Bart"
+ lastName:@"Simpson"];
+
+ data = [NSData dataWithBytes:"a" length:1];
+ STAssertFalse([person setImageData:data], nil);
+
+ GTMABMutableMultiValue *value
+ = [GTMABMutableMultiValue valueWithPropertyType:kABStringPropertyType];
+ STAssertNotNil(value, nil);
+ STAssertNotEquals([value addValue:@"222-222-2222"
+ withLabel:kABHomeLabel],
+ kABMultiValueInvalidIdentifier, nil);
+ STAssertNotEquals([value addValue:@"333-333-3333"
+ withLabel:kABWorkLabel],
+ kABMultiValueInvalidIdentifier, nil);
+ STAssertTrue([person setValue:value forProperty:kABPersonPhoneProperty], nil);
+ id value2 = [person valueForProperty:kABPersonPhoneProperty];
+ STAssertNotNil(value2, nil);
+ STAssertEqualObjects(value, value2, nil);
+ STAssertEquals([value hash], [value2 hash], nil);
+ STAssertNotEquals([person hash], (NSUInteger)0, nil);
+}
+
+- (void)testGroup {
+ GTMABGroup *group = [[[GTMABGroup alloc] initWithRecord:nil] autorelease];
+ STAssertNil(group, nil);
+ group = [GTMABGroup groupNamed:@"TestGroup"];
+ STAssertNotNil(group, nil);
+ STAssertEqualObjects([group compositeName], @"TestGroup", nil);
+ NSString *name = [group valueForProperty:kABGroupNameProperty];
+ STAssertEqualObjects(name, @"TestGroup", nil);
+ NSString *lastName = [group valueForProperty:kABPersonLastNameProperty];
+ STAssertNil(lastName, nil);
+ STAssertTrue([group removeValueForProperty:kABGroupNameProperty], nil);
+ STAssertFalse([group removeValueForProperty:kABGroupNameProperty], nil);
+ STAssertFalse([group removeValueForProperty:kABPersonLastNameProperty], nil);
+ STAssertFalse([group setValue:nil forProperty:kABGroupNameProperty], nil);
+ STAssertFalse([group setValue:[NSNumber numberWithInt:1]
+ forProperty:kABGroupNameProperty], nil);
+ STAssertFalse([group setValue:@"Bart"
+ forProperty:kABPersonBirthdayProperty], nil);
+
+ ABPropertyType property = [GTMABGroup typeOfProperty:kABGroupNameProperty];
+ STAssertEquals(property, (ABPropertyType)kABStringPropertyType, nil);
+
+ property = [GTMABGroup typeOfProperty:kABPersonLastNameProperty];
+ STAssertEquals(property, (ABPropertyType)kABInvalidPropertyType, nil);
+
+ NSString *string = [GTMABGroup localizedPropertyName:kABGroupNameProperty];
+ STAssertEqualObjects(string, @"Name", nil);
+
+ string = [GTMABGroup localizedPropertyName:kABPersonLastNameProperty];
+ STAssertEqualObjects(string, kGTMABUnknownPropertyName, nil);
+
+ string = [GTMABGroup localizedPropertyName:kABRecordInvalidID];
+ STAssertEqualObjects(string, kGTMABUnknownPropertyName, nil);
+
+ string = [group description];
+ STAssertNotNil(string, nil);
+
+ // Adding and removing members
+ group = [GTMABGroup groupNamed:@"TestGroup2"];
+ NSArray *members = [group members];
+ STAssertEquals([members count], (NSUInteger)0, @"Members: %@", members);
+
+ STAssertFalse([group addMember:nil], nil);
+
+ members = [group members];
+ STAssertEquals([members count], (NSUInteger)0, @"Members: %@", members);
+
+ GTMABPerson *person = [GTMABPerson personWithFirstName:@"Bart"
+ lastName:@"Simpson"];
+ STAssertNotNil(person, nil);
+ STAssertTrue([book_ addRecord:person], nil);
+ STAssertTrue([book_ save], nil);
+ STAssertTrue([group addMember:person], nil);
+ STAssertTrue([book_ addRecord:group], nil);
+ STAssertTrue([book_ save], nil);
+ members = [group members];
+ STAssertEquals([members count], (NSUInteger)1, @"Members: %@", members);
+ STAssertTrue([group removeMember:person], nil);
+ STAssertFalse([group removeMember:person], nil);
+ STAssertFalse([group removeMember:nil], nil);
+ STAssertTrue([book_ removeRecord:group], nil);
+ STAssertTrue([book_ removeRecord:person], nil);
+ STAssertTrue([book_ save], nil);
+}
+
+
+- (void)testMultiValues {
+ STAssertThrows([[GTMABMultiValue alloc] init], nil);
+ STAssertThrows([[GTMABMutableMultiValue alloc] init], nil);
+ GTMABMultiValue *value = [[GTMABMultiValue alloc] initWithMultiValue:nil];
+ STAssertNil(value, nil);
+ GTMABMutableMultiValue *mutValue
+ = [GTMABMutableMultiValue valueWithPropertyType:kABInvalidPropertyType];
+ STAssertNil(mutValue, nil);
+ mutValue
+ = [[[GTMABMutableMultiValue alloc]
+ initWithMutableMultiValue:nil] autorelease];
+ STAssertNil(mutValue, nil);
+ mutValue
+ = [[[GTMABMutableMultiValue alloc]
+ initWithMultiValue:nil] autorelease];
+ STAssertNil(mutValue, nil);
+ mutValue = [GTMABMutableMultiValue valueWithPropertyType:kABStringPropertyType];
+ STAssertNotNil(mutValue, nil);
+ value = [[mutValue copy] autorelease];
+ STAssertEqualObjects([value class], [GTMABMultiValue class], nil);
+ mutValue = [[value mutableCopy] autorelease];
+ STAssertEqualObjects([mutValue class], [GTMABMutableMultiValue class], nil);
+ STAssertEquals([mutValue count], (NSUInteger)0, nil);
+ STAssertNil([mutValue valueAtIndex:0], nil);
+ STAssertNil([mutValue labelAtIndex:0], nil);
+ STAssertEquals([mutValue identifierAtIndex:0],
+ kABMultiValueInvalidIdentifier, nil);
+ STAssertEquals([mutValue propertyType],
+ (ABPropertyType)kABStringPropertyType, nil);
+ ABMultiValueIdentifier ident = [mutValue addValue:nil
+ withLabel:kABHomeLabel];
+ STAssertEquals(ident, kABMultiValueInvalidIdentifier, nil);
+ ident = [mutValue addValue:@"val1"
+ withLabel:nil];
+ STAssertEquals(ident, kABMultiValueInvalidIdentifier, nil);
+ ident = [mutValue insertValue:@"val1"
+ withLabel:nil
+ atIndex:0];
+ STAssertEquals(ident, kABMultiValueInvalidIdentifier, nil);
+ ident = [mutValue insertValue:nil
+ withLabel:kABHomeLabel
+ atIndex:0];
+ STAssertEquals(ident, kABMultiValueInvalidIdentifier, nil);
+ ident = [mutValue addValue:@"val1"
+ withLabel:kABHomeLabel];
+ STAssertNotEquals(ident, kABMultiValueInvalidIdentifier, nil);
+ ABMultiValueIdentifier identCheck = [mutValue identifierAtIndex:0];
+ STAssertEquals(ident, identCheck, nil);
+ NSUInteger idx = [mutValue indexForIdentifier:ident];
+ STAssertEquals(idx, (NSUInteger)0, nil);
+ STAssertTrue([mutValue replaceLabelAtIndex:0
+ withLabel:kABWorkLabel], nil);
+ STAssertFalse([mutValue replaceLabelAtIndex:10
+ withLabel:kABWorkLabel], nil);
+ STAssertTrue([mutValue replaceValueAtIndex:0
+ withValue:@"newVal1"], nil);
+ STAssertFalse([mutValue replaceValueAtIndex:10
+ withValue:@"newVal1"], nil);
+
+ STAssertEqualObjects([mutValue valueForIdentifier:ident], @"newVal1", nil);
+ STAssertEqualObjects([mutValue labelForIdentifier:ident],
+ (NSString *)kABWorkLabel, nil);
+
+ ABMultiValueIdentifier ident2 = [mutValue insertValue:@"val2"
+ withLabel:kABOtherLabel
+ atIndex:0];
+ STAssertNotEquals(ident2, kABMultiValueInvalidIdentifier, nil);
+ STAssertNotEquals(ident2, ident, nil);
+ ABMultiValueIdentifier ident3 = [mutValue insertValue:@"val3"
+ withLabel:kABPersonPhoneMainLabel
+ atIndex:10];
+ STAssertEquals(ident3, kABMultiValueInvalidIdentifier, nil);
+ NSUInteger idx3 = [mutValue indexForIdentifier:ident3];
+ STAssertEquals(idx3, (NSUInteger)NSNotFound, nil);
+ STAssertTrue([mutValue removeValueAndLabelAtIndex:1], nil);
+ STAssertFalse([mutValue removeValueAndLabelAtIndex:1], nil);
+
+ NSUInteger idx4
+ = [mutValue indexForIdentifier:kABMultiValueInvalidIdentifier];
+ STAssertEquals(idx4, (NSUInteger)NSNotFound, nil);
+
+ STAssertNotNULL([mutValue multiValueRef], nil);
+
+ // Enumerator test
+ mutValue = [GTMABMutableMultiValue valueWithPropertyType:kABIntegerPropertyType];
+ STAssertNotNil(mutValue, nil);
+ for (int i = 0; i < 100; i++) {
+ NSString *label = [NSString stringWithFormat:@"label %d", i];
+ NSNumber *val = [NSNumber numberWithInt:i];
+ STAssertNotEquals([mutValue addValue:val
+ withLabel:(CFStringRef)label],
+ kABMultiValueInvalidIdentifier, nil);
+ }
+ int count = 0;
+ for (NSString *label in [mutValue labelEnumerator]) {
+ NSString *testLabel = [NSString stringWithFormat:@"label %d", count++];
+ STAssertEqualObjects(label, testLabel, nil);
+ }
+ count = 0;
+ value = [[mutValue copy] autorelease];
+ for (NSNumber *val in [value valueEnumerator]) {
+ STAssertEqualObjects(val, [NSNumber numberWithInt:count++], nil);
+ }
+
+ // Test messing with the values while we're enumerating them
+ NSEnumerator *labelEnum = [mutValue labelEnumerator];
+ NSEnumerator *valueEnum = [mutValue valueEnumerator];
+ STAssertNotNil(labelEnum, nil);
+ STAssertNotNil(valueEnum, nil);
+ STAssertNotNil([labelEnum nextObject], nil);
+ STAssertNotNil([valueEnum nextObject], nil);
+ STAssertTrue([mutValue removeValueAndLabelAtIndex:0], nil);
+ STAssertThrows([labelEnum nextObject], nil);
+ STAssertThrows([valueEnum nextObject], nil);
+
+ // Test messing with the values while we're fast enumerating them
+ // Should throw an exception on the second access.
+ BOOL exceptionThrown = NO;
+ // Start at one because we removed index 0 above.
+ count = 1;
+ @try {
+ for (NSString *label in [mutValue labelEnumerator]) {
+ NSString *testLabel = [NSString stringWithFormat:@"label %d", count++];
+ STAssertEqualObjects(label, testLabel, nil);
+ STAssertTrue([mutValue removeValueAndLabelAtIndex:50], nil);
+ }
+ } @catch(NSException *e) {
+ STAssertEqualObjects([e name], NSGenericException, @"Got %@ instead", e);
+ STAssertEquals(count, 2,
+ @"Should have caught it on the second access");
+ exceptionThrown = YES;
+ } // COV_NF_LINE - because we always catch, this brace doesn't get exec'd
+ STAssertTrue(exceptionThrown, @"We should have thrown an exception"
+ @" because the values under the enumerator were modified");
+
+}
+
+- (void)testRadar6208390 {
+ ABPropertyType types[] = {
+ kABStringPropertyType,
+ kABIntegerPropertyType,
+ kABRealPropertyType,
+ kABDateTimePropertyType,
+ kABDictionaryPropertyType
+ };
+ for (size_t j = 0; j < sizeof(types) / sizeof(ABPropertyType); ++j) {
+ ABPropertyType type = types[j];
+ ABMultiValueRef ref = ABMultiValueCreateMutable(type);
+ STAssertNotNULL(ref, nil);
+ NSString *label = [[NSString alloc] initWithString:@"label"];
+ STAssertNotNil(label, nil);
+ id val;
+ switch (type) {
+ case kABDictionaryPropertyType:
+ val = [[NSDictionary alloc] initWithObjectsAndKeys:@"1", @"1", nil];
+ break;
+
+ case kABStringPropertyType:
+ val = [[NSString alloc] initWithFormat:@"value %d"];
+ break;
+
+ case kABIntegerPropertyType:
+ case kABRealPropertyType:
+ val = [[NSNumber alloc] initWithInt:143];
+ break;
+
+ case kABDateTimePropertyType:
+ val = [[NSDate alloc] init];
+ break;
+ }
+ STAssertNotNil(val,
+ @"Testing type %d, %@", type, val);
+ NSUInteger firstRetainCount = [val retainCount];
+ STAssertNotEquals(firstRetainCount,
+ (NSUInteger)0,
+ @"Testing type %d, %@", type, val);
+
+ ABMultiValueIdentifier identifier;
+ STAssertTrue(ABMultiValueAddValueAndLabel(ref,
+ val,
+ (CFStringRef)label,
+ &identifier),
+ @"Testing type %d, %@", type, val);
+ NSUInteger secondRetainCount = [val retainCount];
+ STAssertEquals(firstRetainCount + 1,
+ secondRetainCount,
+ @"Testing type %d, %@", type, val);
+ [label release];
+ [val release];
+ NSUInteger thirdRetainCount = [val retainCount];
+ STAssertEquals(firstRetainCount,
+ thirdRetainCount,
+ @"Testing type %d, %@", type, val);
+
+ val = (id)ABMultiValueCopyValueAtIndex(ref, 0);
+ NSUInteger fourthRetainCount = [val retainCount];
+ if (type == kABIntegerPropertyType
+ || type == kABRealPropertyType
+ || type == kABDictionaryPropertyType) {
+ // We are verifying that yes indeed 6208390 is still broken
+ STAssertEquals(fourthRetainCount,
+ thirdRetainCount,
+ @"Testing type %d, %@. If you see this error it may "
+ @"be time to update the code to change retain behaviors"
+ @"with this os version", type, val);
+ } else {
+ STAssertEquals(fourthRetainCount,
+ thirdRetainCount + 1,
+ @"Testing type %d, %@", type, val);
+ [val release];
+ }
+
+ CFRelease(ref);
+ }
+}
+@end
diff --git a/iPhone/TestData/phone.png b/iPhone/TestData/phone.png
new file mode 100644
index 0000000..7e4051f
--- /dev/null
+++ b/iPhone/TestData/phone.png
Binary files differ