From 45ef1abbeb319daeff01be3b42662ce651d17b55 Mon Sep 17 00:00:00 2001 From: Carl Lindberg Date: Wed, 23 Oct 2013 09:55:18 -0500 Subject: [PATCH] NSInvocation appears to add all retained arguments (which includes the target) into an internal NSArray when retainArguments is turned on, and never removes them. This array is only freed up when the invocation itself is dealloced. Because CTAppearance by necessity has its stored invocations retain their arguments, every object the calls were invoked with got added to these arrays, and were never freed. They will not show up in Leaks because there are valid pointers to them. My fix is to only turn on retainArguments if any of the arguments are in fact objects, since we never care about retaining the targets (and I don't *think* we care about retaining the return values, so I didn't check for that). Most setter methods of scalar values will fall into this category, so it seems worth it to check. When invoking, if the NSInvocation does not retain arguments, we can call invokeWithTarget: directly. Otherwise, we have to make a new NSInvocation instance as a copy, and call invokeWithTarget on the copy instead, to avoid the target from being retained. --- CTAppearance/CTAppearance.m | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/CTAppearance/CTAppearance.m b/CTAppearance/CTAppearance.m index 5a2de86..ed8b3d9 100644 --- a/CTAppearance/CTAppearance.m +++ b/CTAppearance/CTAppearance.m @@ -84,9 +84,40 @@ + (NSArray*)appearanceParentsForClass:(Class)cls { return items; } +// Invocations which retain arguments also retain all targets it is invoked with, +// and keeps them until the NSInvocation is dealloced (they are stored perpetually in +// an internal NSArray). Since all of our captured invocations are basically retained +// forever, we have to use a copy of the NSInvocation to do the actual perform lest +// we leak all of the objects we perform on. We can avoid making the copy if the +// NSInvocation does not retain arguments. ++ (void)_performInvocation:(NSInvocation *)invocation onTarget:(id)target +{ + NSInvocation *invocationToUse = invocation; + + if (invocation.argumentsRetained) + { + NSMethodSignature *sig = invocation.methodSignature; + NSInvocation *invokeCopy = [NSInvocation invocationWithMethodSignature:sig]; + invokeCopy.selector = invocation.selector; + + for (NSUInteger arg=2; arg%@ with invocations %@", d , NSStringFromClass(self.customizableClass), containedIn, self.invocations]; } +// If there are no object arguments, we don't need to have the invocation retain arguments. +// We use that fact to optimize invocation later on. +- (BOOL)_needsRetainArguments:(NSInvocation *)invocation +{ + NSMethodSignature *sig = [invocation methodSignature]; + + for (NSUInteger i = 2; i < [sig numberOfArguments]; i++) { + const char *objcType = [sig getArgumentTypeAtIndex:i]; + if (objcType[0] == _C_ID) + return YES; + } + + return NO; +} + - (void)forwardInvocation:(NSInvocation *)anInvocation { if (self.invocations == nil) { self.invocations = [NSMutableArray array]; } [self.invocations addObject: anInvocation]; - [anInvocation retainArguments]; + if ([self _needsRetainArguments:anInvocation]) + [anInvocation retainArguments]; } - (BOOL)instanceRespondToSelector:(SEL)aSelector {