Created
October 22, 2011 17:08
-
-
Save JensAyton/1306226 to your computer and use it in GitHub Desktop.
Fun with ARC and exceptions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
The desired output of each test case is: | |
<MyException 0xPTR>{ TestName, retain count 1 } created. | |
<MyException 0xPTR>{ TestName, retain count 1 } caught. | |
<MyException 0xPTR>{ TestName, retain count 1 } dealloced. | |
For a fun time, try to predict the actual output for all six test cases! | |
UPDATE: I’ve been pointed at the Clang ARC internals documentation | |
(http://clang.llvm.org/docs/AutomaticReferenceCounting.html#misc.exceptions), | |
which say that the compiler doesn’t clean up after exceptions unless | |
-fobjc-arc-exceptions is passed. This is not mentioned in the Transitioning | |
to ARC Release Notes. With the flag, the ConvenienceConstructor variants all | |
work, and the AllocInit variants all crash. | |
FURTHER UPDATE: Documentation enhancement request: http://www.openradar.me/10329810 | |
*/ | |
#import <Foundation/Foundation.h> | |
@interface MyException: NSObject | |
+ (id) exceptionWithLabel:(NSString *)label; | |
- (id) initWithLabel:(NSString *)label; | |
@end | |
// Test cases | |
static void DirectThrowConvenienceConstructorTest(NSString *name) | |
{ | |
@throw [MyException exceptionWithLabel:name]; | |
} | |
static void DirectThrowAllocInitTest(NSString *name) | |
{ | |
@throw [[MyException alloc] initWithLabel:name]; | |
} | |
static void IndirectThrow(id exception) __attribute__((noreturn, noinline)); | |
static void IndirectThrow(id exception) | |
{ | |
@throw exception; | |
} | |
static void IndirectThrowConvenienceConstructorTest(NSString *name) | |
{ | |
IndirectThrow([MyException exceptionWithLabel:name]); | |
} | |
static void IndirectThrowAllocInitTest(NSString *name) | |
{ | |
IndirectThrow([[MyException alloc] initWithLabel:name]); | |
} | |
static void AutoreleasingThrow(__autoreleasing id exception) __attribute__((noreturn, noinline)); | |
static void AutoreleasingThrow(__autoreleasing id exception) | |
{ | |
@throw exception; | |
} | |
static void AutoreleasingThrowConvenienceConstructorTest(NSString *name) | |
{ | |
AutoreleasingThrow([MyException exceptionWithLabel:name]); | |
} | |
static void AutoreleasingThrowAllocInitTest(NSString *name) | |
{ | |
AutoreleasingThrow([[MyException alloc] initWithLabel:name]); | |
} | |
// Test rig | |
extern void _NSSetLogCStringFunction(void (*)(const char *string, unsigned length, BOOL withSyslogBanner)); | |
static void PrintNSLogMessage(const char *string, unsigned length, BOOL withSyslogBanner) | |
{ | |
puts(string); | |
} | |
static void RunTest_(void (*testCase)(NSString *name), NSString *name) | |
{ | |
NSLog(@"Running test %@", name); | |
__weak MyException *weakException; | |
@autoreleasepool | |
{ | |
@try | |
{ | |
testCase(name); | |
} | |
@catch (MyException *exception) | |
{ | |
weakException = exception; | |
NSLog(@"%@ caught.", exception); | |
} | |
} | |
if (weakException != nil) | |
{ | |
NSLog(@"%@ appears to have leaked.", weakException); | |
} | |
NSLog(@"\n"); | |
} | |
#define RunTest(NAME) RunTest_(NAME, @#NAME) | |
int main (int argc, const char * argv[]) | |
{ | |
// Avoid annoying log headers. | |
_NSSetLogCStringFunction(PrintNSLogMessage); | |
@autoreleasepool | |
{ | |
RunTest(IndirectThrowConvenienceConstructorTest); | |
RunTest(IndirectThrowAllocInitTest); | |
RunTest(AutoreleasingThrowConvenienceConstructorTest); | |
RunTest(AutoreleasingThrowAllocInitTest); | |
RunTest(DirectThrowConvenienceConstructorTest); | |
RunTest(DirectThrowAllocInitTest); | |
} | |
} | |
@implementation MyException | |
{ | |
NSString *_label; | |
} | |
+ (id) exceptionWithLabel:(NSString *)label | |
{ | |
id result = [[self alloc] initWithLabel:label]; | |
// Compiling the exception class separately with ARC off made no difference. | |
#if !__has_extension(objc_arc) | |
[result autorelease]; | |
#endif | |
return result; | |
} | |
- (id) initWithLabel:(NSString *)label | |
{ | |
if ((self = [super init])) | |
{ | |
_label = label; | |
NSLog(@"%@ created.", self); | |
} | |
return self; | |
} | |
- (void) dealloc | |
{ | |
NSLog(@"%@ dealloced.", self); | |
_label = [NSString stringWithFormat:@"ZOMBIE - was %@", _label]; | |
#if !__has_extension(objc_arc) | |
[super dealloc]; | |
#endif | |
} | |
- (NSString *) description | |
{ | |
return [NSString stringWithFormat:@"<%@ %p>{ %@, retain count %lu }", self.class, self, _label, CFGetRetainCount((__bridge CFTypeRef)self)]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment