Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental circular dependency fix #38

Merged
merged 13 commits into from
Jul 30, 2013
1 change: 0 additions & 1 deletion Source/Factory/Block/TyphoonAssembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#import <Foundation/Foundation.h>


static NSString* const TYPHOON_BEFORE_ADVICE_SUFFIX = @"__typhoonBeforeAdvice";

@interface TyphoonAssembly : NSObject
{
Expand Down
197 changes: 147 additions & 50 deletions Source/Factory/Block/TyphoonAssembly.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
#import "TyphoonJRSwizzle.h"
#import "TyphoonDefinition.h"
#import "TyphoonComponentFactory.h"
#import "TyphoonAssemblySelectorWrapper.h"

static NSMutableArray* resolveStack;
static NSMutableDictionary *resolveStackForKey;

@implementation TyphoonAssembly

Expand All @@ -29,75 +30,171 @@ + (TyphoonAssembly*)assembly
return [[[self class] alloc] init];
}

+ (TyphoonAssembly*)defaultAssembly
{
return (TyphoonAssembly*) [TyphoonComponentFactory defaultFactory];
}

+ (void)load
{
[super load];
resolveStackForKey = [[NSMutableDictionary alloc] init];
}

#pragma mark - Instance Method Resolution
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([TyphoonAssembly selectorReserved:sel])
if ([self shouldProvideDynamicImplementationFor:sel]) {
[self provideDynamicImplementationToConstructDefinitionForSEL:sel];
return YES;
}

return [super resolveInstanceMethod:sel];
}

+ (BOOL)shouldProvideDynamicImplementationFor:(SEL)sel;
{
return (![TyphoonAssembly selectorReserved:sel] && [TyphoonAssemblySelectorWrapper selectorIsWrapped:sel]);
}

+ (BOOL)selectorReserved:(SEL)selector
{
return selector == @selector(init) || selector == @selector(cachedDefinitionsForMethodName) || selector == NSSelectorFromString(@".cxx_destruct") ||
selector == @selector(defaultAssembly);
}

+ (void)provideDynamicImplementationToConstructDefinitionForSEL:(SEL)sel;
{
IMP imp = [self implementationToConstructDefinitionForSEL:sel];
class_addMethod(self, sel, imp, "@");
}

+ (IMP)implementationToConstructDefinitionForSEL:(SEL)selWithAdvicePrefix
{
return imp_implementationWithBlock((__bridge id) objc_unretainedPointer((TyphoonDefinition *)^(id me)
{
NSString *key = [TyphoonAssemblySelectorWrapper keyForWrappedSEL:selWithAdvicePrefix];
return [self definitionForKey:key me:me];
}));
}

+ (TyphoonDefinition *)definitionForKey:(NSString *)key me:(id)me
{
NSLog(@"Resolving request for definition for key: %@", key);

TyphoonDefinition *cached = [self cachedDefinitionForKey:key me:me];
if (!cached)
{
return [super resolveInstanceMethod:sel];
NSLog(@"Definition for key: '%@' is not cached, building...", key);
return [self buildDefinitionForKey:key me:me];
}

NSLog(@"Using cached definition for key '%@.'", key);
return cached;
}

+ (TyphoonDefinition *)cachedDefinitionForKey:(NSString *)key me:(id)me
{
return [[me cachedDefinitionsForMethodName] objectForKey:key];
}

+ (TyphoonDefinition *)buildDefinitionForKey:(NSString *)key me:(TyphoonAssembly *)me;
{
NSMutableArray *resolveStack = [self resolveStackForKey:key];
[self markCurrentlyResolvingKey:key resolveStack:resolveStack];

if ([self dependencyForKey:key involvedInCircularDependencyInResolveStack:resolveStack]) {
return [self definitionToTerminateCircularDependencyForKey:key];
}

id cached = [self populateCacheWithDefinitionForKey:key me:me];
[self markKeyResolved:key resolveStack:resolveStack];

NSLog(@"Did finish building definition for key: '%@'", key);

return cached;
}

NSString* name = NSStringFromSelector(sel);
if ([name hasSuffix:TYPHOON_BEFORE_ADVICE_SUFFIX])
+ (BOOL)dependencyForKey:(NSString *)key involvedInCircularDependencyInResolveStack:(NSArray *)resolveStack;
{
if ([resolveStack count] >= 2)
{
IMP imp = imp_implementationWithBlock((__bridge id) objc_unretainedPointer(^(id me)
NSString* bottom = [resolveStack objectAtIndex:0];
NSString* top = [resolveStack objectAtIndex:[resolveStack count] - 1];
if ([top isEqualToString:bottom])
{
NSString* key = [name stringByReplacingOccurrencesOfString:TYPHOON_BEFORE_ADVICE_SUFFIX withString:@""];
TyphoonDefinition* cached = [[me cachedSelectors] objectForKey:key];
if (cached == nil)
{
[resolveStack addObject:key];
if ([resolveStack count] > 2)
{
NSString* bottom = [resolveStack objectAtIndex:0];
NSString* top = [resolveStack objectAtIndex:[resolveStack count] - 1];
if ([top isEqualToString:bottom])
{
NSLog(@"Resolve stack: %@", resolveStack);
return [[TyphoonDefinition alloc] initWithClass:[NSString class] key:key];
}
}

[[self class] typhoon_swizzleMethod:sel withMethod:NSSelectorFromString(key) error:nil];
cached = objc_msgSend(me, sel);
if (cached && [cached isKindOfClass:[TyphoonDefinition class]])
{
TyphoonDefinition* definition = (TyphoonDefinition*) cached;
if ([definition.key length] == 0)
{
definition.key = key;
}
[[me cachedSelectors] setObject:definition forKey:key];
}
[[self class] typhoon_swizzleMethod:NSSelectorFromString(key) withMethod:sel error:nil];
}
[resolveStack removeAllObjects];
return cached;

}));
class_addMethod(self, sel, imp, "@");
return YES;
NSLog(@"Circular dependency detected in definition for key '%@'. Breaking the cycle.", key);
return YES;
}
}

return NO;
}

+ (TyphoonAssembly*)defaultAssembly
+ (TyphoonDefinition *)definitionToTerminateCircularDependencyForKey:(NSString *)key
{
return (TyphoonAssembly*) [TyphoonComponentFactory defaultFactory];
// we return a 'dummy' definition just to terminate the cycle. This dummy definition will be overwritten by the real one, which will be set further up the stack and will overwrite this one in 'cachedDefinitionsForMethodName'.
return [[TyphoonDefinition alloc] initWithClass:[NSString class] key:key];
}

+ (NSMutableArray *)resolveStackForKey:(NSString *)key
{
NSMutableArray *resolveStack = [resolveStackForKey objectForKey:key];
if (!resolveStack) {
resolveStack = [[NSMutableArray alloc] init];
[resolveStackForKey setObject:resolveStack forKey:key];
}
return resolveStack;
}

+ (BOOL)selectorReserved:(SEL)selector
+ (void)markCurrentlyResolvingKey:(NSString *)key resolveStack:(NSMutableArray *)resolveStack
{
return selector == @selector(init) || selector == @selector(cachedSelectors) || selector == NSSelectorFromString(@".cxx_destruct") ||
selector == @selector(defaultAssembly);
[resolveStack addObject:key];
NSLog(@"Definition resolve stack: '%@' for key: '%@'", resolveStack, key);
}

+ (void)load
+ (TyphoonDefinition *)populateCacheWithDefinitionForKey:(NSString *)key me:(TyphoonAssembly *)me;
{
[super load];
resolveStack = [[NSMutableArray alloc] init];
id d = [self definitionByCallingAssemblyMethodForKey:key me:me];
[self populateCacheWithDefinition:d forKey:key me:me];
return d;
}

+ (id)definitionByCallingAssemblyMethodForKey:(NSString *)key me:(TyphoonAssembly *)me
{
SEL sel = [TyphoonAssemblySelectorWrapper wrappedSELForKey:key];
id cached = objc_msgSend(me, sel); // the wrappedSEL will call through to the original, unwrapped implementation because of the active swizzling.
return cached;
}

+ (void)populateCacheWithDefinition:(TyphoonDefinition *)cached forKey:(NSString *)key me:(id)me
{
if (cached && [cached isKindOfClass:[TyphoonDefinition class]])
{
TyphoonDefinition* definition = (TyphoonDefinition*) cached;
[self setKey:key onDefinitionIfExistingKeyEmpty:definition];

[[me cachedDefinitionsForMethodName] setObject:definition forKey:key];
}
}

+ (void)setKey:(NSString *)key onDefinitionIfExistingKeyEmpty:(TyphoonDefinition *)definition
{
if ([definition.key length] == 0)
{
definition.key = key;
}
}

+ (void)markKeyResolved:(NSString *)key resolveStack:(NSMutableArray *)resolveStack
{
if (resolveStack.count) {
NSLog(@"Will clear definition resolve stack: '%@' for key: '%@'", resolveStack, key);
[resolveStack removeAllObjects];
}
}

#pragma mark - Initializers
/* ============================================================ Initializers ============================================================ */
- (id)init
{
Expand All @@ -116,7 +213,7 @@ - (void)dealloc
}

/* ============================================================ Private Methods ========================================================= */
- (NSMutableDictionary*)cachedSelectors
- (NSMutableDictionary*)cachedDefinitionsForMethodName
{
return _cachedDefinitions;
}
Expand Down
19 changes: 19 additions & 0 deletions Source/Factory/Block/TyphoonAssemblySelectorWrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// AssemblyMethodSelectorToKeyConverter.h
// Static Library
//
// Created by Robert Gilliam on 7/29/13.
// Copyright (c) 2013 Jasper Blues. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface TyphoonAssemblySelectorWrapper : NSObject

+ (SEL)wrappedSELForKey:(NSString *)key;
+ (NSString *)keyForWrappedSEL:(SEL)selWithAdvicePrefix;
+ (BOOL)selectorIsWrapped:(SEL)sel;

+ (SEL)wrappedSELForSEL:(SEL)unwrappedSEL;

@end
42 changes: 42 additions & 0 deletions Source/Factory/Block/TyphoonAssemblySelectorWrapper.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// AssemblyMethodSelectorToKeyConverter.m
// Static Library
//
// Created by Robert Gilliam on 7/29/13.
// Copyright (c) 2013 Jasper Blues. All rights reserved.
//

#import "TyphoonAssemblySelectorWrapper.h"



static NSString* const TYPHOON_BEFORE_ADVICE_SUFFIX = @"__typhoonBeforeAdvice";



@implementation TyphoonAssemblySelectorWrapper

+ (SEL)wrappedSELForKey:(NSString *)key;
{
return NSSelectorFromString([key stringByAppendingString:TYPHOON_BEFORE_ADVICE_SUFFIX]);
}

+ (NSString *)keyForWrappedSEL:(SEL)selWithAdvicePrefix;
{
NSString* name = NSStringFromSelector(selWithAdvicePrefix);
NSString* key = [name stringByReplacingOccurrencesOfString:TYPHOON_BEFORE_ADVICE_SUFFIX withString:@""];
return key;
}

+ (BOOL)selectorIsWrapped:(SEL)sel;
{
NSString* name = NSStringFromSelector(sel);
return [name hasSuffix:TYPHOON_BEFORE_ADVICE_SUFFIX]; // a name will always have this suffix after a TyphoonBlockComponentFactory has been initialized with us as the assembly. Make this clearer. All user facing calls will always go through the dynamic implementation machinery.
}

+ (SEL)wrappedSELForSEL:(SEL)unwrappedSEL;
{
return NSSelectorFromString([NSStringFromSelector(unwrappedSEL) stringByAppendingString:TYPHOON_BEFORE_ADVICE_SUFFIX]);
}

@end
Loading