Repository: mindsnacks/MSLeakHunter Branch: master Commit: 13942252cd68 Files: 31 Total size: 81.3 KB Directory structure: gitextract_gqgsbplz/ ├── .gitignore ├── LICENSE ├── MSLeakHunter+Private.h ├── MSLeakHunter.h ├── MSLeakHunter.m ├── MSLeakHunterRetainBreakpointsHelper.h ├── MSLeakHunterRetainBreakpointsHelper.m ├── MSViewControllerLeakHunter.h ├── MSViewControllerLeakHunter.m ├── MSViewLeakHunter.h ├── MSViewLeakHunter.m ├── MSZombieHunter.h ├── MSZombieHunter.m ├── README.md └── Sample Project/ └── MSVCLeakHunterSampleProject/ ├── MSVCLeakHunterSampleProject/ │ ├── MSAppDelegate.h │ ├── MSAppDelegate.m │ ├── MSLeakingVC.h │ ├── MSLeakingVC.m │ ├── MSLeakingVC.xib │ ├── MSMenuVC.h │ ├── MSMenuVC.m │ ├── MSMenuVC.xib │ ├── MSOKVC.h │ ├── MSOKVC.m │ ├── MSOKVC.xib │ ├── MSVCLeakHunterSampleProject-Info.plist │ ├── MSVCLeakHunterSampleProject-Prefix.pch │ ├── en.lproj/ │ │ └── InfoPlist.strings │ └── main.m └── MSVCLeakHunterSampleProject.xcodeproj/ ├── project.pbxproj └── xcshareddata/ └── xcdebugger/ └── Breakpoints.xcbkptlist ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode build/* *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 *.xcworkspace !default.xcworkspace xcuserdata profile *.moved-aside ================================================ FILE: LICENSE ================================================ Copyright 2012 MindSnacks 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. ================================================ FILE: MSLeakHunter+Private.h ================================================ // // MSAbstractLeakHunter+Private.h // MindSnacks // // Created by Javier Soto on 11/16/12. // // #if MSLeakHunter_ENABLED /** * @discussion leaks hunters must use these methods to implement their functionality. */ @interface MSLeakHunter () /** * @discussion call this method when an object got a notification that indicates that it will be deallocated soon. * @param objectReference should be a string that contains the class, the pointer, and a description of the object that leaked. */ + (void)scheduleLeakNotificationWithObjectReferenceString:(NSString *)referenceString afterDelay:(NSTimeInterval)delay; /** * @discussion call this method to cancel the schedule of a log for a leaked object * when it's finally deallocated. */ + (void)cancelLeakNotificationWithObjectReferenceString:(NSString *)referenceString; /** * @discussion you can call this method to swizzle the original method of the class you're trying to * catch leaks from, and replace it with a method in a category of that class. */ + (void)swizzleMethod:(SEL)aOriginalMethod ofClass:(Class)class withMethod:(SEL)aNewMethod; @end #endif ================================================ FILE: MSLeakHunter.h ================================================ // // MSLeakHunter.h // MindSnacks // // Created by Javier Soto on 11/16/12. // // #import /** * @discussion this is a general on/off switch for MSLeakHunter. * Classes that implement the `MSLeakHunter` protocol should wrap all their code between #if and #endif * statements so that none of their code is compiled if this is turned off. */ #define MSLeakHunter_ENABLED TARGET_IPHONE_SIMULATOR #if MSLeakHunter_ENABLED @protocol MSLeakHunter /** * @discussion leak hunters should implement the appropiate method swizzling in this method. */ + (void)install; @end @interface MSLeakHunter : NSObject + (void)installLeakHunter:(Class)leakHunter; @end #endif ================================================ FILE: MSLeakHunter.m ================================================ // // MSLeakHunter.m // MindSnacks // // Created by Javier Soto on 11/16/12. // // #import "MSLeakHunter.h" #import #if MSLeakHunter_ENABLED // This is hacky and relies on a "deprecated" (althought the documentation isn't updated saying so) method. What's the alternative? static inline void ms_dispatch_sync_safe(dispatch_queue_t dispatchQueue, dispatch_block_t block) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" if (dispatch_get_current_queue() == dispatchQueue) { #pragma clang diagnostic pop block(); } else { dispatch_sync(dispatchQueue, block); } } /** * @discussion this queue lets us ensure that the calls to `performSector:...` and `cancelPrevious...` * are always made in the same run loop. */ static dispatch_queue_t _msLeakHunterQueue = nil; @implementation MSLeakHunter + (void)initialize { if ([self class] == [MSLeakHunter class]) { _msLeakHunterQueue = dispatch_queue_create("com.mindsnacks.leakhunter", DISPATCH_QUEUE_SERIAL); } } + (MSLeakHunter *)sharedInstance { static MSLeakHunter *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[MSLeakHunter alloc] init]; }); return sharedInstance; } - (void)objectLeakedWithReference:(NSString *)objectReference { NSLog(@"[%@] POSSIBLE LEAK OF %@", NSStringFromClass([self class]), objectReference); } + (void)installLeakHunter:(Class)leakHunter { [leakHunter install]; } #pragma mark - Swizzling + (void)swizzleMethod:(SEL)aOriginalMethod ofClass:(Class)class withMethod:(SEL)aNewMethod { Method oldMethod = class_getInstanceMethod(class, aOriginalMethod); Method newMethod = class_getInstanceMethod(class, aNewMethod); method_exchangeImplementations(oldMethod, newMethod); } #pragma mark - Checking + (void)scheduleLeakNotificationWithObjectReferenceString:(NSString *)referenceString afterDelay:(NSTimeInterval)delay { // Ensure we always run these methods on the same thread ms_dispatch_sync_safe(_msLeakHunterQueue, ^{ // Cancel previous ones just in case to avoid multiple calls. [self cancelLeakNotificationWithObjectReferenceString:referenceString]; [[self sharedInstance] performSelector:@selector(objectLeakedWithReference:) withObject:referenceString afterDelay:delay]; }); } + (void)cancelLeakNotificationWithObjectReferenceString:(NSString *)referenceString { ms_dispatch_sync_safe(_msLeakHunterQueue, ^{ [self cancelPreviousPerformRequestsWithTarget:[self sharedInstance] selector:@selector(objectLeakedWithReference:) object:referenceString]; }); } @end #endif ================================================ FILE: MSLeakHunterRetainBreakpointsHelper.h ================================================ // // MSLeakHunterRetainBreakpointsHelper.h // MindSnacks // // Created by Javier Soto on 1/22/13. // // #import "MSLeakHunter.h" #if MSLeakHunter_ENABLED /** * @discussion installs a series of handles so that subsequent calls to the methods listed below result * in the debugger stopping with a brekapoint so that you can trace the calls in the stack to those methods. * @ methods: * - retain * - release * - autorelease * - dealloc * * @note these methods are not thread safe and should be called from the main thread. */ extern void ms_enableMemoryManagementMethodBreakpointsOnObject(id object); /** * @discussion undoes what `ms_enableMemoryManagementMethodBreakpointsOnObject()` did, so the object will no longer * make the debugger stop when those methods are called on it. */ extern void ms_disableMemoryManagementMethodBreakpointsOnObject(id object); #endif ================================================ FILE: MSLeakHunterRetainBreakpointsHelper.m ================================================ // // MSLeakHunterRetainBreakpointsHelper.m // MindSnacks // // Created by Javier Soto on 1/22/13. // // #import "MSLeakHunterRetainBreakpointsHelper.h" #if MSLeakHunter_ENABLED #if __has_feature(objc_arc) #error MSLeakHunterRetainBreakpointsHelper is non-ARC only. Either turn off ARC for the project or use -fno-objc-arc flag \ Because of how this class messes with retain and release calls, making this class support ARC is kind of tricky. #endif #import "MSLeakHunter+Private.h" #import #import #import #include #include #include #include #include #pragma mark - // Thanks to DCIntrospect for this trick: https://github.com/domesticcatsoftware/DCIntrospect // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). __unused static bool AmIBeingDebugged(void) { int junk; int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); assert(junk == 0); // We're being debugged if the P_TRACED flag is set. return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } #if TARGET_CPU_ARM64 #define DEBUGGER() do {__asm__ __volatile__ ("svc 0"); } while(0) #elif TARGET_CPU_X86_64 #define DEBUGGER() __builtin_trap() #elif TARGET_CPU_ARM #define DEBUGSTOP(signal) __asm__ __volatile__ ("mov r0, %0\nmov r1, %1\nmov r12, %2\nswi 128\n" : : "r"(getpid ()), "r"(signal), "r"(37) : "r12", "r0", "r1", "cc"); #define DEBUGGER() do { if (AmIBeingDebugged()) { DEBUGSTOP(SIGINT); } } while (false); #else #define DEBUGGER() do { if (AmIBeingDebugged()) { __asm__ __volatile__ ("pushl %0\npushl %1\npush $0\nmovl %2, %%eax\nint $0x80\nadd $12, %%esp" : : "g" (SIGINT), "g" (getpid ()), "n" (37) : "eax", "cc"); } } while (false); #endif #define PARENT_IMP(object, selector) (class_getMethodImplementation(class_getSuperclass(object_getClass(object)), selector)) #define CALL_PARENT_IMP(object, selector) IMP i = PARENT_IMP(object, selector); i(object, selector); #define CALL_AND_RETURN_PARENT_IMP(object, selector) IMP i = PARENT_IMP(object, selector); return i(object, selector); #define ADD_NEW_METHOD(class, selector, function_pointer) class_addMethod(class, selector, (IMP)function_pointer, @encode(typeof(function_pointer))); #pragma mark - static id ms_retain(id self, SEL _cmd) { DEBUGGER(); CALL_AND_RETURN_PARENT_IMP(self, _cmd); } static void ms_release(id self, SEL _cmd) { DEBUGGER(); CALL_PARENT_IMP(self, _cmd); } static id ms_autorelease(id self, SEL _cmd) { DEBUGGER(); CALL_AND_RETURN_PARENT_IMP(self, _cmd); } static void ms_dealloc(id self, SEL _cmd) { DEBUGGER(); CALL_PARENT_IMP(self, _cmd); // We could remove the dynamic class with this undocumented method: // objc_disposeClassPair(object_getClass(self)); // But we would probably need to do that only when this is the last living object of this class. } // We override class to make the dynamic subclass objects pose as the normal class static Class ms_class(id self, SEL _cmd) { Class thisClass = object_getClass(self); return class_getSuperclass(thisClass); } static BOOL _ms_hasBreakpointsEnabled(id self, SEL _cmd) { return YES; } static SEL _ms_hasBreakpointsEnabledSelector(void) { return NSSelectorFromString(@"_ms_hasBreakpointsEnabled"); } #pragma mark - #define kDynamicSubclassPrefix @"__MSLeak_" static BOOL ms_objectIsOfDynamicSubclass(id object) { if (!object) { return NO; } SEL selector = _ms_hasBreakpointsEnabledSelector(); if (class_respondsToSelector(object_getClass(object), selector)) { return ((BOOL(*)(id, SEL))objc_msgSend)((id)object, selector); } return NO; } static __inline__ NSString *ms_dynamicSubclassNameForObject(id object) { return [NSString stringWithFormat:@"%@%@", kDynamicSubclassPrefix, NSStringFromClass([object class])]; } void ms_enableMemoryManagementMethodBreakpointsOnObject(id object) { NSCParameterAssert(object); // Add a dynamic subclass for that object // 1. Does the subclass already exist? NSString *subclassName = ms_dynamicSubclassNameForObject(object); Class subclass = NSClassFromString(subclassName); // 2. Doesn't exist. Creating the dynamic subclass if (!subclass) { Class parentClass = [object class]; subclass = objc_allocateClassPair(parentClass, [subclassName cStringUsingEncoding:NSASCIIStringEncoding], 0); NSCAssert(subclass, @"Could not create dynamic subclass for object %@", object); objc_registerClassPair(subclass); // Implement the memory management methods for that subclass ADD_NEW_METHOD(subclass, @selector(retain), ms_retain); ADD_NEW_METHOD(subclass, @selector(release), ms_release); ADD_NEW_METHOD(subclass, @selector(autorelease), ms_autorelease); ADD_NEW_METHOD(subclass, @selector(dealloc), ms_dealloc); ADD_NEW_METHOD(subclass, @selector(class), ms_class); ADD_NEW_METHOD(subclass, _ms_hasBreakpointsEnabledSelector(), _ms_hasBreakpointsEnabled); } // 3. Make the object of that subclass object_setClass(object, subclass); } extern void ms_disableMemoryManagementMethodBreakpointsOnObject(id object) { NSCParameterAssert(object); if (ms_objectIsOfDynamicSubclass(object)) { // Simply set the class to the parent (the one posed as by -class) so that it doesn't have the modified method implementations object_setClass(object, [object class]); // Note: The dynamic subclass will still exist. } } #endif ================================================ FILE: MSViewControllerLeakHunter.h ================================================ // // MSViewControllerLeakHunter.h // MSAppKit // // Created by Javier Soto on 10/16/12. // // #import "MSLeakHunter.h" #if MSLeakHunter_ENABLED /** * @discussion if a view controller hasn't been deallocated after this time after it disappeared from screen, it's considered "pottentially leaked" */ #define kMSVCLeakHunterDisappearAndDeallocateMaxInterval 30.0f /** * @discussion this makes MSVCLeakHunter print logs when view controllers appear, disappear and are deallocated. */ #define MSVCLeakHunter_EnableUIViewControllerLog 0 /** * @discussion when installed, it's going to print messages in the log whenever a view controller is not deallocated after a while of disappearing from screen. */ @interface MSViewControllerLeakHunter : NSObject @end #endif ================================================ FILE: MSViewControllerLeakHunter.m ================================================ // // MSVCLeakHunter.m // MSAppKit // // Created by Javier Soto on 10/16/12. // // #import "MSViewControllerLeakHunter.h" #import "MSLeakHunter+Private.h" #if MSLeakHunter_ENABLED #if MSVCLeakHunter_EnableUIViewControllerLog #define LOG_METHOD(NAME) NSLog(@"MSViewControllerLeakHunter -[%@ %@]", NSStringFromClass([self class]), NAME) #else #define LOG_METHOD(NAME) #endif @interface UIViewController (MSViewControllerLeakHunter) - (void)_msvcLeakHunter_viewDidAppear:(BOOL)animated; - (void)_msvcLeakHunter_viewDidDisappear:(BOOL)animated; - (void)_msvcLeakHunter_dealloc; @end @implementation MSViewControllerLeakHunter + (void)install { Class class = [UIViewController class]; [MSLeakHunter swizzleMethod:@selector(viewDidAppear:) ofClass:class withMethod:@selector(_msvcLeakHunter_viewDidAppear:)]; [MSLeakHunter swizzleMethod:@selector(viewDidDisappear:) ofClass:class withMethod:@selector(_msvcLeakHunter_viewDidDisappear:)]; [MSLeakHunter swizzleMethod:NSSelectorFromString(@"dealloc") ofClass:class withMethod:@selector(_msvcLeakHunter_dealloc)]; } @end @implementation UIViewController (MSViewControllerLeakHunter) /** * @return a string that identifies the controller. This is used to be pased to `MSVCLeakHunter` without retaining the controller. */ - (NSString *)controllerReferenceString { return [NSString stringWithFormat:@"VIEW CONTROLLER %@ <%p>", NSStringFromClass([self class]), self]; } - (void)cancelLeakCheck { [MSLeakHunter cancelLeakNotificationWithObjectReferenceString:[self controllerReferenceString]]; } - (void)scheduleLeakCheck { [MSLeakHunter scheduleLeakNotificationWithObjectReferenceString:[self controllerReferenceString] afterDelay:kMSVCLeakHunterDisappearAndDeallocateMaxInterval]; } #pragma mark - Alternative method implementations) - (void)_msvcLeakHunter_viewDidAppear:(BOOL)animated { LOG_METHOD(@"viewDidAppear:"); [self cancelLeakCheck]; // Call original implementation [self _msvcLeakHunter_viewDidAppear:animated]; } - (void)_msvcLeakHunter_viewDidDisappear:(BOOL)animated { LOG_METHOD(@"viewDidDisappear:"); [self scheduleLeakCheck]; // Call original implementation [self _msvcLeakHunter_viewDidDisappear:animated]; } - (void)_msvcLeakHunter_dealloc { LOG_METHOD(@"dealloc"); [self cancelLeakCheck]; // Call original implementation [self _msvcLeakHunter_dealloc]; } @end #endif ================================================ FILE: MSViewLeakHunter.h ================================================ // // MSViewLeakHunter.h // MindSnacks // // Created by Javier Soto on 12/7/12. // // #import "MSLeakHunter.h" #if MSLeakHunter_ENABLED /** * @discussion allows you to enable/disable only this leak hunter. * if MSLeakHunter is completely disabled, this setting doesn't have effect. */ #define MSViewLeakHunter_ENABLED 0 #if MSViewLeakHunter_ENABLED /** * @discussion if a view hasn't been deallocated after this time after it disappeared from screen, it's considered "pottentially leaked" */ #define kMSViewLeakHunterDisappearAndDeallocateMaxInterval 15.0f /** * @discussion when installed, it's going to print messages in the log whenever a view is not deallocated after a while of disappearing from screen. */ @interface MSViewLeakHunter : NSObject @end #endif #endif ================================================ FILE: MSViewLeakHunter.m ================================================ // // MSViewLeakHunter.m // MindSnacks // // Created by Javier Soto on 12/7/12. // // #import "MSViewLeakHunter.h" #import "MSLeakHunter+Private.h" #if MSLeakHunter_ENABLED #if MSViewLeakHunter_ENABLED @interface UIView (MSViewLeakHunter) - (void)_msviewLeakHunter_didMoveToWindow; - (void)_msviewLeakHunter_dealloc; @end @implementation MSViewLeakHunter + (void)install { Class class = [UIView class]; [MSLeakHunter swizzleMethod:@selector(didMoveToWindow) ofClass:class withMethod:@selector(_msviewLeakHunter_didMoveToWindow)]; [MSLeakHunter swizzleMethod:NSSelectorFromString(@"dealloc") ofClass:class withMethod:@selector(_msviewLeakHunter_dealloc)]; } @end @implementation UIView (MSViewLeakHunter) /** * @return a string that identifies the controller. This is used to be pased to `MSVCLeakHunter` without retaining the controller. */ - (NSString *)viewReferenceString { return [NSString stringWithFormat:@"VIEW %@ <%p>", NSStringFromClass([self class]), self]; } - (void)cancelLeakCheck { [MSLeakHunter cancelLeakNotificationWithObjectReferenceString:[self viewReferenceString]]; } - (void)_msviewLeakHunter_didMoveToWindow { if (!self.window) { [MSLeakHunter scheduleLeakNotificationWithObjectReferenceString:[self viewReferenceString] afterDelay:kMSViewLeakHunterDisappearAndDeallocateMaxInterval]; } else { [self cancelLeakCheck]; } } - (void)_msviewLeakHunter_dealloc { [self cancelLeakCheck]; // Call original implementation [self _msviewLeakHunter_dealloc]; } @end #endif #endif ================================================ FILE: MSZombieHunter.h ================================================ // // MSZombieHunter.h // MindSnacks // // Created by Javier Soto on 3/1/13. // // #import #define MSZombieHunter_Available (TARGET_IPHONE_SIMULATOR || (!TARGET_OS_IPHONE)) #if MSZombieHunter_Available @interface MSZombieHunter : NSObject + (void)enable; + (void)disable; @end #endif ================================================ FILE: MSZombieHunter.m ================================================ // // MSZombieHunter.m // MindSnacks // // Created by Javier Soto on 3/1/13. // // #import "MSZombieHunter.h" #if MSZombieHunter_Available #if __has_feature(objc_arc) #error MSZombieHunter is non-ARC only. Either turn off ARC for the project or use -fno-objc-arc flag \ ARC explicitly disallows implementing retain/release/autorelease methods which must be implemented here. #endif #import static IMP ms_swizzleMethodWithBlock(Method method, void *block) { IMP blockImplementation = imp_implementationWithBlock(block); return method_setImplementation(method, blockImplementation); } @interface _MSZombie : NSProxy @property (nonatomic, assign) Class originalClass; @end static BOOL _enabled = NO; static NSArray *_rootClasses = nil; static NSArray *_originalDeallocImps = nil; @implementation MSZombieHunter + (void)initialize { if ([self class] == [MSZombieHunter class]) { _rootClasses = [@[[NSObject class], [NSProxy class]] retain]; } } + (void)swizzleDealloc { static void *swizzledDeallocBlock = NULL; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ swizzledDeallocBlock = [^void(id obj) { Class currentClass = [obj class]; object_setClass(obj, [_MSZombie class]); ((_MSZombie *)obj).originalClass = currentClass; } copy]; }); NSMutableArray *deallocImplementations = [NSMutableArray array]; for (Class rootClass in _rootClasses) { IMP originalDeallocImp = ms_swizzleMethodWithBlock(class_getInstanceMethod(rootClass, @selector(dealloc)), swizzledDeallocBlock); [deallocImplementations addObject:[NSValue valueWithBytes:&originalDeallocImp objCType:@encode(typeof(IMP))]]; } _originalDeallocImps = [deallocImplementations copy]; } + (void)unswizzleDealloc { [_rootClasses enumerateObjectsUsingBlock:^(Class rootClass, NSUInteger idx, BOOL *stop) { IMP originalDeallocImp = NULL; [_originalDeallocImps[idx] getValue:&originalDeallocImp]; NSParameterAssert(originalDeallocImp); method_setImplementation(class_getInstanceMethod(rootClass, @selector(dealloc)), originalDeallocImp); }]; [_originalDeallocImps release]; _originalDeallocImps = nil; } + (void)enable { @synchronized(self) { if (!_enabled) { [self swizzleDealloc]; _enabled = YES; } } } + (void)disable { @synchronized(self) { if (_enabled) { [self unswizzleDealloc]; _enabled = NO; } } } @end @implementation NSObject (MSZombieHunter) - (void)ms_zombieHunterDealloc { Class currentClass = [self class]; object_setClass(self, [_MSZombie class]); ((_MSZombie *)self).originalClass = currentClass; } @end static char MSZombieOriginalClassKey; #define MSZombieThrowMesssageSentException() [self throwMessageSentExceptionWithSelector:_cmd] @implementation _MSZombie - (void)throwMessageSentExceptionWithSelector:(SEL)selector { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"An Objective-C message (-[%@ %@]) was sent to a deallocated object (zombie) at address: %p", NSStringFromClass(self.originalClass), NSStringFromSelector(selector), self] userInfo:nil]; } #pragma mark - NSProxy stuff - (BOOL)respondsToSelector:(SEL)aSelector { return [self.originalClass instancesRespondToSelector:aSelector]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.originalClass instanceMethodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [self throwMessageSentExceptionWithSelector:invocation.selector]; } #pragma mark - NSObject protocol methods // These methods won't trigger the proxy forwarding, so we must implement them to throw the exception too: - (Class)class { MSZombieThrowMesssageSentException(); return nil; } - (BOOL)isEqual:(id)object { MSZombieThrowMesssageSentException(); return NO; } - (NSUInteger)hash { MSZombieThrowMesssageSentException(); return 0; } - (id)self { MSZombieThrowMesssageSentException(); return nil; } - (BOOL)isKindOfClass:(Class)aClass { MSZombieThrowMesssageSentException(); return NO; } - (BOOL)isMemberOfClass:(Class)aClass { MSZombieThrowMesssageSentException(); return NO; } - (BOOL)conformsToProtocol:(Protocol *)aProtocol { MSZombieThrowMesssageSentException(); return NO; } - (BOOL)isProxy { MSZombieThrowMesssageSentException(); return NO; } - (id)retain { MSZombieThrowMesssageSentException(); return nil; } - (oneway void)release { MSZombieThrowMesssageSentException(); } - (id)autorelease { MSZombieThrowMesssageSentException(); return nil; } - (void)dealloc { MSZombieThrowMesssageSentException(); [super dealloc]; } - (NSUInteger)retainCount { MSZombieThrowMesssageSentException(); return 0; } - (NSZone *)zone { MSZombieThrowMesssageSentException(); return nil; } - (NSString *)description { MSZombieThrowMesssageSentException(); return nil; } #pragma mark - Properties - (Class)originalClass { return objc_getAssociatedObject(self, &MSZombieOriginalClassKey); } - (void)setOriginalClass:(Class)originalClass { objc_setAssociatedObject(self, &MSZombieOriginalClassKey, originalClass, OBJC_ASSOCIATION_ASSIGN); } @end #endif ================================================ FILE: README.md ================================================ MSLeakHunter ============== ### Series of tools to help you find and debug objects that are leaking in your iOS apps. There are very common cases where objects may be leaking (not being deallocated when you expect them to) and instruments fails to detect this. One particular example is due to retain cycles using blocks, and it's very tricky sometimes to realize that some object isn't being deallocated. **MSLeakHunter** provides a generic interface to construct "leak hunter" objects, that are in charge of monitoring the allocation and deallocation of objects of a particular class. In this repo, two particular implementations are provided: `MSViewControllerLeakHunter` and `MSViewLeakHunter` (for `UIViewController` and `UIView` instances respectively). However, you can create as many leak hunters as you wish. The only thing they need is that the object they're expecting to be deallocated to have some method that gets called before this deallocation is supposed to happen. For example, `UIViewController` will get a `-viewDidDisappear:` call some time before its deallocation. What `MSLeakHunter` allows you to do, is to keep track of that object, and in case `-dealloc` isn't called some time after that, it's considered pottentially leaked and it's logged in the console. The implementation is pretty cheap, so it shouldn't hurt the performance of any application, but it's advised to keep this code disabled (through the `MSLeakHunter_ENABLED` macro) when shipping an app. For more instructions on how to create other leak hunter objects, refer to the `MSLeakHunter+Private` header and to the included sample implementations. # Installation - Add ```MSLeakHunter.{h,m}``` to the Xcode project. - Somewhere during app initialization (e.g. the ```applicationDidFinishLaunchingWithOptions:``` method of your app delegate.), install the leak hunters that you want to enable: ```objc [MSLeakHunter installLeakHunter:[MSViewControllerLeakHunter class]]; ``` - Make sure ```MSVCLeakHunter_ENABLED``` is set to 1 in ```MSVCLeakHunter.h``` # What it looks like - When you run the app with a leak hunter enabled, and it finds a possible object that is leaking, this is what you'll see: *screenshot from the sample project* # MSLeakHunterRetainBreakpointsHelper This other tool lets you debug a leak once you know it exists. It provides a very simple way to make the debugger stop on a breakpoint every time one of the 4 memory management methods is called on the object that you're interested in monitoring. This should help you find out where that extra -retain call is coming from, or who is retaining that object but never releasing it, etc. Using it is as simple as calling this method declared in `MSLeakHunterRetainBreakpointsHelper.h` with the object that you want to monitor: ```objc ms_enableMemoryManagementMethodBreakpointsOnObject(object); ``` After this call, the debugger will stop the application whenever `-retain`, `-release`, `-autorelease`, or `-dealloc` is called on that object. If you go up the stack, you will be able to see who caused the call to those methods and hopefully that will help you debug memory managament problems in your app. * Note: `MSLeakHunterRetainBreakpointsHelper.m` has to be compiled without ARC. If your project uses ARC, refer to [this tutorial](http://maniacdev.com/2012/01/easily-get-non-arc-enabled-open-source-libraries-working-in-arc-enabled-projects/) to know how to disable ARC only for that file. # MSZombieHunter `MSZombieHunter` works similarly to setting `NSZombieEnabled`, but you can enable it with a class method call: ```objc +[MSZombieHunter enable]; ``` From that point on, if an object is sent a message after it's deallocated, it will throw an exception that you can catch and know inmediately when it happened. This is what it looks like: **Important note** Enabling `MSZombieHunter` makes all objects stay alive when they receive the dealloc message, which increases the memory usage of the application exponentially. For this reason it's only advised to enable it on the simulator and when trying to debug an `EXC_BAD_ACCESS` crash. # Compatibility - ```MSLeakHunter``` is compatible with **ARC** and **non-ARC** projects. # Caveats of the leak hunter implementations If you look at the implementation in ```MSViewControllerLeakHunter.m```, it's very naive. All it does is swizzle some methods for every UIViewController instance to discover when a view controller disappear from screen( *it gets a ```viewDidDisappear:``` call* ), but isn't deallocated after a certain period of time. If this happens, it doesn't guarantee 100% that the view controller leaked. For example, if it's inside a ```UITabBarController```, it may disappear when you select another tab, but it's still retained by the tabbar, and it hasn't leaked. But it will help you discover, for example, view controllers that you push onto a navigation controller stack, and aren't deallocated when you pop them tapping on the back button. In the case where you have something like a navigation controller that is shown modally, and then the whole stack goes away when the modal is closed, you may want to tweak the value of ```kMSVCLeakHunterDisappearAndDeallocateMaxInterval``` ( *see ```MSViewControllerLeakHunter.h```* ) to give ```MSViewControllerLeakHunter``` enough margin to avoid a false positive. Otherwise, you may see a log for a possible leak of the controllers at the bottom of the stack if the modal takes longer to be closed. # License Copyright 2012 MindSnacks 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. ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSAppDelegate.h ================================================ // // MSAppDelegate.h // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import @interface MSAppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSAppDelegate.m ================================================ // // MSAppDelegate.m // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import "MSAppDelegate.h" #import "MSMenuVC.h" #import "MSLeakHunter.h" #import "MSViewControllerLeakHunter.h" #import "MSViewLeakHunter.h" #import "MSZombieHunter.h" @implementation MSAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // installing the MSVCLeakHunter #if MSLeakHunter_ENABLED [MSLeakHunter installLeakHunter:[MSViewControllerLeakHunter class]]; #endif #if MSViewLeakHunter_ENABLED [MSLeakHunter installLeakHunter:[MSViewLeakHunter class]]; #endif self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; MSMenuVC *menuVC = [[MSMenuVC alloc] init]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:menuVC]; self.window.rootViewController = navigationController; [self.window makeKeyAndVisible]; // Uncomment to try MSZombieHunter: /* [MSZombieHunter enable]; __unsafe_unretained UIView *view = [[UIView alloc] init]; // This object is deallocated right here. NSLog(@"View: %@", view); // This makes it crash because view is a zombie. */ return YES; } @end ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSLeakingVC.h ================================================ // // MSLeakingVC.h // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import @interface MSLeakingVC : UIViewController @end ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSLeakingVC.m ================================================ // // MSLeakingVC.m // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import "MSLeakingVC.h" @interface MSLeakingVC () @property (nonatomic, copy) dispatch_block_t block; @end @implementation MSLeakingVC - (id)init { if ((self = [super init])) { self.block = ^{ NSLog(@"%@ this blocks references self, so it retains self, and self retains the block, so there's a retain cycle => the VC never deallocates.", self); }; } return self; } @end ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSLeakingVC.xib ================================================ 1536 12C60 2843 1187.34 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin 1929 IBProxyObject IBUILabel IBUIView com.apple.InterfaceBuilder.IBCocoaTouchPlugin PluginDependencyRecalculationVersion IBFilesOwner IBCocoaTouchFramework IBFirstResponder IBCocoaTouchFramework 274 301 {{59, 228}, {202, 91}} _NS:9 NO YES 7 NO IBCocoaTouchFramework This view controller leaks 1 MCAwIDAAA darkTextColor 0 0 1 2 19 Helvetica-Bold 19 16 NO 202 {{0, 20}, {320, 548}} 3 MQA 2 IBUIScreenMetrics YES {320, 568} {568, 320} IBCocoaTouchFramework Retina 4 Full Screen 2 IBCocoaTouchFramework view 3 0 1 -1 File's Owner -2 4 MSLeakingVC com.apple.InterfaceBuilder.IBCocoaTouchPlugin UIResponder com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin 4 MSLeakingVC UIViewController IBProjectSource ./Classes/MSLeakingVC.h 0 IBCocoaTouchFramework YES 3 1929 ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSMenuVC.h ================================================ // // MSMenuVC.h // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import @interface MSMenuVC : UIViewController @end ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSMenuVC.m ================================================ // // MSMenuVC.m // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import "MSMenuVC.h" #import "MSOKVC.h" #import "MSLeakingVC.h" #import "MSLeakHunterRetainBreakpointsHelper.h" @interface MSMenuVC () @end @implementation MSMenuVC - (void)pushViewControllerOfClass:(Class)class { UIViewController *viewController = [[class alloc] init]; ms_enableMemoryManagementMethodBreakpointsOnObject(viewController); [self.navigationController pushViewController:viewController animated:YES]; } - (IBAction)onPushOKVC { [self pushViewControllerOfClass:[MSOKVC class]]; } - (IBAction)onPushLeakingVC { [self pushViewControllerOfClass:[MSLeakingVC class]]; } @end ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSMenuVC.xib ================================================ 1536 12C60 2843 1187.34 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin 1929 IBProxyObject IBUIButton IBUIView com.apple.InterfaceBuilder.IBCocoaTouchPlugin PluginDependencyRecalculationVersion IBFilesOwner IBCocoaTouchFramework IBFirstResponder IBCocoaTouchFramework 274 301 {{46, 194}, {228, 44}} _NS:9 NO IBCocoaTouchFramework 0 0 1 Push OK View Controller 3 MQA 1 MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA 3 MC41AA 2 15 Helvetica-Bold 15 16 301 {{46, 311}, {228, 44}} _NS:9 NO IBCocoaTouchFramework 0 0 1 Push Leaking View Controller 1 MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA {{0, 20}, {320, 548}} 3 MQA 2 IBUIScreenMetrics YES {320, 568} {568, 320} IBCocoaTouchFramework Retina 4 Full Screen 2 IBCocoaTouchFramework view 3 onPushOKVC 7 11 onPushLeakingVC 7 12 0 1 -1 File's Owner -2 4 10 MSMenuVC com.apple.InterfaceBuilder.IBCocoaTouchPlugin UIResponder com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin 12 0 IBCocoaTouchFramework YES 3 1929 ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSOKVC.h ================================================ // // MSOKVC.h // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import @interface MSOKVC : UIViewController @end ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSOKVC.m ================================================ // // MSOKVC.m // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import "MSOKVC.h" @interface MSOKVC () @end @implementation MSOKVC @end ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSOKVC.xib ================================================ 1536 12C60 2843 1187.34 625.00 com.apple.InterfaceBuilder.IBCocoaTouchPlugin 1929 IBProxyObject IBUILabel IBUIView com.apple.InterfaceBuilder.IBCocoaTouchPlugin PluginDependencyRecalculationVersion IBFilesOwner IBCocoaTouchFramework IBFirstResponder IBCocoaTouchFramework 274 301 {{59, 228}, {202, 91}} _NS:9 NO YES 7 NO IBCocoaTouchFramework This view controller doesn't leak 1 MCAwIDAAA darkTextColor 0 0 1 2 19 Helvetica-Bold 19 16 NO 202 {{0, 20}, {320, 548}} 3 MQA 2 IBUIScreenMetrics YES {320, 568} {568, 320} IBCocoaTouchFramework Retina 4 Full Screen 2 IBCocoaTouchFramework view 3 0 1 -1 File's Owner -2 4 MSOKVC com.apple.InterfaceBuilder.IBCocoaTouchPlugin UIResponder com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin 9 MSOKVC UIViewController IBProjectSource ./Classes/MSOKVC.h 0 IBCocoaTouchFramework YES 3 1929 ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier mindsnacks.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject-Prefix.pch ================================================ // // Prefix header for all source files of the 'MSVCLeakHunterSampleProject' target in the 'MSVCLeakHunterSampleProject' project // #import #ifndef __IPHONE_3_0 #warning "This project uses features only available in iOS SDK 3.0 and later." #endif #ifdef __OBJC__ #import #import #endif ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject/main.m ================================================ // // main.m // MSVCLeakHunterSampleProject // // Created by Javier Soto on 10/20/12. // Copyright (c) 2012 MindSnacks. All rights reserved. // #import #import "MSAppDelegate.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([MSAppDelegate class])); } } ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ D00724F916BA02AD006C55B1 /* MSViewLeakHunter.m in Sources */ = {isa = PBXBuildFile; fileRef = D00724F816BA02AD006C55B1 /* MSViewLeakHunter.m */; }; D00A1FE216E141EE004C06B7 /* MSZombieHunter.m in Sources */ = {isa = PBXBuildFile; fileRef = D00A1FE116E141EE004C06B7 /* MSZombieHunter.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; D0BB69DC16B9FBB0009A9834 /* MSLeakHunter.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BB69D616B9FBB0009A9834 /* MSLeakHunter.m */; }; D0BB69DD16B9FBB0009A9834 /* MSLeakHunterRetainBreakpointsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BB69D916B9FBB0009A9834 /* MSLeakHunterRetainBreakpointsHelper.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; D0BB69DE16B9FBB0009A9834 /* MSViewControllerLeakHunter.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BB69DB16B9FBB0009A9834 /* MSViewControllerLeakHunter.m */; }; D0E17BBF163380CD0045A2E4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E17BBE163380CD0045A2E4 /* UIKit.framework */; }; D0E17BC1163380CD0045A2E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E17BC0163380CD0045A2E4 /* Foundation.framework */; }; D0E17BC3163380CD0045A2E4 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E17BC2163380CD0045A2E4 /* CoreGraphics.framework */; }; D0E17BC9163380CD0045A2E4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0E17BC7163380CD0045A2E4 /* InfoPlist.strings */; }; D0E17BCB163380CD0045A2E4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E17BCA163380CD0045A2E4 /* main.m */; }; D0E17BCF163380CD0045A2E4 /* MSAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E17BCE163380CD0045A2E4 /* MSAppDelegate.m */; }; D0E17BD1163380CD0045A2E4 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = D0E17BD0163380CD0045A2E4 /* Default.png */; }; D0E17BD3163380CD0045A2E4 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0E17BD2163380CD0045A2E4 /* Default@2x.png */; }; D0E17BD5163380CD0045A2E4 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0E17BD4163380CD0045A2E4 /* Default-568h@2x.png */; }; D0E17BE2163381430045A2E4 /* MSMenuVC.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E17BE0163381430045A2E4 /* MSMenuVC.m */; }; D0E17BE3163381430045A2E4 /* MSMenuVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = D0E17BE1163381430045A2E4 /* MSMenuVC.xib */; }; D0E17BE7163381AA0045A2E4 /* MSOKVC.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E17BE5163381AA0045A2E4 /* MSOKVC.m */; }; D0E17BE8163381AA0045A2E4 /* MSOKVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = D0E17BE6163381AA0045A2E4 /* MSOKVC.xib */; }; D0E17BEC163381EE0045A2E4 /* MSLeakingVC.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E17BEA163381EE0045A2E4 /* MSLeakingVC.m */; }; D0E17BED163381EE0045A2E4 /* MSLeakingVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = D0E17BEB163381EE0045A2E4 /* MSLeakingVC.xib */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ D00724F716BA02AC006C55B1 /* MSViewLeakHunter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSViewLeakHunter.h; path = ../../../MSViewLeakHunter.h; sourceTree = ""; }; D00724F816BA02AD006C55B1 /* MSViewLeakHunter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSViewLeakHunter.m; path = ../../../MSViewLeakHunter.m; sourceTree = ""; }; D00A1FE016E141EE004C06B7 /* MSZombieHunter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSZombieHunter.h; path = ../../../MSZombieHunter.h; sourceTree = ""; }; D00A1FE116E141EE004C06B7 /* MSZombieHunter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSZombieHunter.m; path = ../../../MSZombieHunter.m; sourceTree = ""; }; D0BB69D516B9FBB0009A9834 /* MSLeakHunter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSLeakHunter.h; path = ../../../MSLeakHunter.h; sourceTree = ""; }; D0BB69D616B9FBB0009A9834 /* MSLeakHunter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSLeakHunter.m; path = ../../../MSLeakHunter.m; sourceTree = ""; }; D0BB69D716B9FBB0009A9834 /* MSLeakHunter+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "MSLeakHunter+Private.h"; path = "../../../MSLeakHunter+Private.h"; sourceTree = ""; }; D0BB69D816B9FBB0009A9834 /* MSLeakHunterRetainBreakpointsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSLeakHunterRetainBreakpointsHelper.h; path = ../../../MSLeakHunterRetainBreakpointsHelper.h; sourceTree = ""; }; D0BB69D916B9FBB0009A9834 /* MSLeakHunterRetainBreakpointsHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSLeakHunterRetainBreakpointsHelper.m; path = ../../../MSLeakHunterRetainBreakpointsHelper.m; sourceTree = ""; }; D0BB69DA16B9FBB0009A9834 /* MSViewControllerLeakHunter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSViewControllerLeakHunter.h; path = ../../../MSViewControllerLeakHunter.h; sourceTree = ""; }; D0BB69DB16B9FBB0009A9834 /* MSViewControllerLeakHunter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSViewControllerLeakHunter.m; path = ../../../MSViewControllerLeakHunter.m; sourceTree = ""; }; D0E17BBA163380CD0045A2E4 /* MSVCLeakHunterSampleProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MSVCLeakHunterSampleProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; D0E17BBE163380CD0045A2E4 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; D0E17BC0163380CD0045A2E4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; D0E17BC2163380CD0045A2E4 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; D0E17BC6163380CD0045A2E4 /* MSVCLeakHunterSampleProject-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "MSVCLeakHunterSampleProject-Info.plist"; sourceTree = ""; }; D0E17BC8163380CD0045A2E4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; D0E17BCA163380CD0045A2E4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; D0E17BCC163380CD0045A2E4 /* MSVCLeakHunterSampleProject-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MSVCLeakHunterSampleProject-Prefix.pch"; sourceTree = ""; }; D0E17BCD163380CD0045A2E4 /* MSAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSAppDelegate.h; sourceTree = ""; }; D0E17BCE163380CD0045A2E4 /* MSAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSAppDelegate.m; sourceTree = ""; }; D0E17BD0163380CD0045A2E4 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; D0E17BD2163380CD0045A2E4 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; D0E17BD4163380CD0045A2E4 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; D0E17BDF163381430045A2E4 /* MSMenuVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSMenuVC.h; sourceTree = ""; }; D0E17BE0163381430045A2E4 /* MSMenuVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMenuVC.m; sourceTree = ""; }; D0E17BE1163381430045A2E4 /* MSMenuVC.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MSMenuVC.xib; sourceTree = ""; }; D0E17BE4163381AA0045A2E4 /* MSOKVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSOKVC.h; sourceTree = ""; }; D0E17BE5163381AA0045A2E4 /* MSOKVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSOKVC.m; sourceTree = ""; }; D0E17BE6163381AA0045A2E4 /* MSOKVC.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MSOKVC.xib; sourceTree = ""; }; D0E17BE9163381EE0045A2E4 /* MSLeakingVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSLeakingVC.h; sourceTree = ""; }; D0E17BEA163381EE0045A2E4 /* MSLeakingVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSLeakingVC.m; sourceTree = ""; }; D0E17BEB163381EE0045A2E4 /* MSLeakingVC.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MSLeakingVC.xib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ D0E17BB7163380CD0045A2E4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( D0E17BBF163380CD0045A2E4 /* UIKit.framework in Frameworks */, D0E17BC1163380CD0045A2E4 /* Foundation.framework in Frameworks */, D0E17BC3163380CD0045A2E4 /* CoreGraphics.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ D0BB69DF16B9FBBD009A9834 /* MSLeakHunter */ = { isa = PBXGroup; children = ( D0BB69D516B9FBB0009A9834 /* MSLeakHunter.h */, D0BB69D616B9FBB0009A9834 /* MSLeakHunter.m */, D0BB69D716B9FBB0009A9834 /* MSLeakHunter+Private.h */, D0BB69DA16B9FBB0009A9834 /* MSViewControllerLeakHunter.h */, D0BB69DB16B9FBB0009A9834 /* MSViewControllerLeakHunter.m */, D00724F716BA02AC006C55B1 /* MSViewLeakHunter.h */, D00724F816BA02AD006C55B1 /* MSViewLeakHunter.m */, D0BB69D816B9FBB0009A9834 /* MSLeakHunterRetainBreakpointsHelper.h */, D0BB69D916B9FBB0009A9834 /* MSLeakHunterRetainBreakpointsHelper.m */, D00A1FE016E141EE004C06B7 /* MSZombieHunter.h */, D00A1FE116E141EE004C06B7 /* MSZombieHunter.m */, ); name = MSLeakHunter; sourceTree = ""; }; D0E17BAF163380CD0045A2E4 = { isa = PBXGroup; children = ( D0E17BC4163380CD0045A2E4 /* MSVCLeakHunterSampleProject */, D0E17BBD163380CD0045A2E4 /* Frameworks */, D0E17BBB163380CD0045A2E4 /* Products */, ); sourceTree = ""; }; D0E17BBB163380CD0045A2E4 /* Products */ = { isa = PBXGroup; children = ( D0E17BBA163380CD0045A2E4 /* MSVCLeakHunterSampleProject.app */, ); name = Products; sourceTree = ""; }; D0E17BBD163380CD0045A2E4 /* Frameworks */ = { isa = PBXGroup; children = ( D0E17BBE163380CD0045A2E4 /* UIKit.framework */, D0E17BC0163380CD0045A2E4 /* Foundation.framework */, D0E17BC2163380CD0045A2E4 /* CoreGraphics.framework */, ); name = Frameworks; sourceTree = ""; }; D0E17BC4163380CD0045A2E4 /* MSVCLeakHunterSampleProject */ = { isa = PBXGroup; children = ( D0E17BCD163380CD0045A2E4 /* MSAppDelegate.h */, D0E17BCE163380CD0045A2E4 /* MSAppDelegate.m */, D0BB69DF16B9FBBD009A9834 /* MSLeakHunter */, D0E17BDE163381240045A2E4 /* ViewControllers */, D0E17BC5163380CD0045A2E4 /* Supporting Files */, ); path = MSVCLeakHunterSampleProject; sourceTree = ""; }; D0E17BC5163380CD0045A2E4 /* Supporting Files */ = { isa = PBXGroup; children = ( D0E17BC6163380CD0045A2E4 /* MSVCLeakHunterSampleProject-Info.plist */, D0E17BC7163380CD0045A2E4 /* InfoPlist.strings */, D0E17BCA163380CD0045A2E4 /* main.m */, D0E17BCC163380CD0045A2E4 /* MSVCLeakHunterSampleProject-Prefix.pch */, D0E17BD0163380CD0045A2E4 /* Default.png */, D0E17BD2163380CD0045A2E4 /* Default@2x.png */, D0E17BD4163380CD0045A2E4 /* Default-568h@2x.png */, ); name = "Supporting Files"; sourceTree = ""; }; D0E17BDE163381240045A2E4 /* ViewControllers */ = { isa = PBXGroup; children = ( D0E17BDF163381430045A2E4 /* MSMenuVC.h */, D0E17BE0163381430045A2E4 /* MSMenuVC.m */, D0E17BE1163381430045A2E4 /* MSMenuVC.xib */, D0E17BE4163381AA0045A2E4 /* MSOKVC.h */, D0E17BE5163381AA0045A2E4 /* MSOKVC.m */, D0E17BE6163381AA0045A2E4 /* MSOKVC.xib */, D0E17BE9163381EE0045A2E4 /* MSLeakingVC.h */, D0E17BEA163381EE0045A2E4 /* MSLeakingVC.m */, D0E17BEB163381EE0045A2E4 /* MSLeakingVC.xib */, ); name = ViewControllers; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ D0E17BB9163380CD0045A2E4 /* MSVCLeakHunterSampleProject */ = { isa = PBXNativeTarget; buildConfigurationList = D0E17BD8163380CD0045A2E4 /* Build configuration list for PBXNativeTarget "MSVCLeakHunterSampleProject" */; buildPhases = ( D0E17BB6163380CD0045A2E4 /* Sources */, D0E17BB7163380CD0045A2E4 /* Frameworks */, D0E17BB8163380CD0045A2E4 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = MSVCLeakHunterSampleProject; productName = MSVCLeakHunterSampleProject; productReference = D0E17BBA163380CD0045A2E4 /* MSVCLeakHunterSampleProject.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ D0E17BB1163380CD0045A2E4 /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = MS; LastUpgradeCheck = 0450; ORGANIZATIONNAME = MindSnacks; }; buildConfigurationList = D0E17BB4163380CD0045A2E4 /* Build configuration list for PBXProject "MSVCLeakHunterSampleProject" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = D0E17BAF163380CD0045A2E4; productRefGroup = D0E17BBB163380CD0045A2E4 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( D0E17BB9163380CD0045A2E4 /* MSVCLeakHunterSampleProject */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ D0E17BB8163380CD0045A2E4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( D0E17BC9163380CD0045A2E4 /* InfoPlist.strings in Resources */, D0E17BD1163380CD0045A2E4 /* Default.png in Resources */, D0E17BD3163380CD0045A2E4 /* Default@2x.png in Resources */, D0E17BD5163380CD0045A2E4 /* Default-568h@2x.png in Resources */, D0E17BE3163381430045A2E4 /* MSMenuVC.xib in Resources */, D0E17BE8163381AA0045A2E4 /* MSOKVC.xib in Resources */, D0E17BED163381EE0045A2E4 /* MSLeakingVC.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ D0E17BB6163380CD0045A2E4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( D0E17BCB163380CD0045A2E4 /* main.m in Sources */, D0E17BCF163380CD0045A2E4 /* MSAppDelegate.m in Sources */, D0E17BE2163381430045A2E4 /* MSMenuVC.m in Sources */, D0E17BE7163381AA0045A2E4 /* MSOKVC.m in Sources */, D0E17BEC163381EE0045A2E4 /* MSLeakingVC.m in Sources */, D0BB69DC16B9FBB0009A9834 /* MSLeakHunter.m in Sources */, D0BB69DD16B9FBB0009A9834 /* MSLeakHunterRetainBreakpointsHelper.m in Sources */, D0BB69DE16B9FBB0009A9834 /* MSViewControllerLeakHunter.m in Sources */, D00724F916BA02AD006C55B1 /* MSViewLeakHunter.m in Sources */, D00A1FE216E141EE004C06B7 /* MSZombieHunter.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ D0E17BC7163380CD0045A2E4 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( D0E17BC8163380CD0045A2E4 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ D0E17BD6163380CD0045A2E4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; D0E17BD7163380CD0045A2E4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 6.0; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; D0E17BD9163380CD0045A2E4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject-Prefix.pch"; INFOPLIST_FILE = "MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; D0E17BDA163380CD0045A2E4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject-Prefix.pch"; INFOPLIST_FILE = "MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ D0E17BB4163380CD0045A2E4 /* Build configuration list for PBXProject "MSVCLeakHunterSampleProject" */ = { isa = XCConfigurationList; buildConfigurations = ( D0E17BD6163380CD0045A2E4 /* Debug */, D0E17BD7163380CD0045A2E4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; D0E17BD8163380CD0045A2E4 /* Build configuration list for PBXNativeTarget "MSVCLeakHunterSampleProject" */ = { isa = XCConfigurationList; buildConfigurations = ( D0E17BD9163380CD0045A2E4 /* Debug */, D0E17BDA163380CD0045A2E4 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = D0E17BB1163380CD0045A2E4 /* Project object */; } ================================================ FILE: Sample Project/MSVCLeakHunterSampleProject/MSVCLeakHunterSampleProject.xcodeproj/xcshareddata/xcdebugger/Breakpoints.xcbkptlist ================================================