Skip to content

Instantly share code, notes, and snippets.

@nevyn
Created October 8, 2012 17:20
Show Gist options
  • Save nevyn/3853718 to your computer and use it in GitHub Desktop.
Save nevyn/3853718 to your computer and use it in GitHub Desktop.
@protocol IThingieProvider <NSObject>
-(id)thingie;
@end
@protocol IDependent <NSObject>
@end
//
// main.m
// SPDependencyContainer
//
// Created by Joachim Bengtsson on 2012-10-08.
// Copyright (c) 2012 Spotify AB. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "SPDependencyContainer.h"
#import "Interfaces.h"
#import "SPWeakArray.h"
@interface ThingieProvider : NSObject <IThingieProvider>
@end
@interface MockedThingieProvider : NSObject <IThingieProvider>
@end
@interface Dependent : NSObject <IDependent>
@property(nonatomic,strong) id<IThingieProvider> thingieProvider;
- (NSString*)description;
@end
int main(int argc, const char * argv[])
{
@autoreleasepool {
id<IThingieProvider> thingieProvider = [[SPDependencyContainer sharedContainer] instanceForProtocol:@protocol(IThingieProvider) arguments:@[@"Specific thingie"]];
// insert code here...
NSLog(@"Creating from protocol: %@", thingieProvider.thingie);
id dependent = [[SPDependencyContainer sharedContainer] instanceForProtocol:@protocol(IDependent)];
NSLog(@"Creating through injection: %@", dependent);
[[SPDependencyContainer sharedContainer] registerClass:[MockedThingieProvider class] forProtocol:@protocol(IThingieProvider)];
dependent = [[SPDependencyContainer sharedContainer] instanceForProtocol:@protocol(IDependent)];
NSLog(@"Injecting mocked: %@", dependent);
id singleton = [ThingieProvider new];
[[SPDependencyContainer sharedContainer] registerSingleton:singleton forProtocol:@protocol(IThingieProvider)];
dependent = [[SPDependencyContainer sharedContainer] instanceForProtocol:@protocol(IDependent)];
NSLog(@"Injecting singleton: %@ into: %@", singleton, dependent);
[[SPDependencyContainer sharedContainer] removeSingletonForProtocol:@protocol(IThingieProvider)];
dependent = [[SPDependencyContainer sharedContainer] instanceForProtocol:@protocol(IDependent)];
NSLog(@"Injecting with killed singleton: %@", dependent);
}
SPMutableWeakArray *weaks = [SPMutableWeakArray new];
{
id thing = [NSObject new];
[weaks addObject:thing];
NSLog(@"Weaks: %@", weaks);
}
[weaks pruneNilEntries];
NSLog(@"Weaks after scope: %@", weaks);
return 0;
}
@implementation ThingieProvider {
id _otherThing;
}
+ (void)load
{
if(self == [ThingieProvider class])
[[SPDependencyContainer sharedContainer] registerClass:self forProtocol:@protocol(IThingieProvider)];
}
- (id)initWithArguments:(NSArray*)arguments;
{
if(arguments)
_otherThing = arguments[0];
return self;
}
- (id)thingie;
{
return _otherThing ?: @"Concrete thingie";
}
@end
@implementation MockedThingieProvider
- (id)thingie;
{
return @"Mocked thingie";
}
@end
@implementation Dependent
+ (void)load
{
if(self == [Dependent class])
[[SPDependencyContainer sharedContainer] registerClass:self forProtocol:@protocol(IDependent)];
}
- (NSString*)description
{
return [NSString stringWithFormat:@"<%p %@ %@>", self, self.thingieProvider, self.thingieProvider.thingie];
}
@end
//
// SPDependencyContainer.h
// SPDependencyContainer
//
// Created by Joachim Bengtsson on 2012-10-08.
// Copyright (c) 2012 Spotify AB. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface SPDependencyContainer : NSObject
+ (id)sharedContainer;
- (void)registerSingleton:(id)singleton forProtocol:(Protocol*)proto;
- (void)removeSingletonForProtocol:(Protocol*)protocol;
- (void)registerClass:(Class)klass forProtocol:(Protocol*)proto;
- (id)instanceForProtocol:(Protocol*)proto;
- (id)instanceForProtocol:(Protocol*)proto arguments:(NSArray*)arguments;
- (void)injectDependencies:(id)target;
@end
@protocol SPDependencyCreatable <NSObject>
- (id)initWithArguments:(NSArray*)arguments;
@end
//
// SPDependencyContainer.m
// SPDependencyContainer
//
// Created by Joachim Bengtsson on 2012-10-08.
// Copyright (c) 2012 Spotify AB. All rights reserved.
//
#import "SPDependencyContainer.h"
#import <objc/Protocol.h>
#import "MARTNSObject.h"
#import "RTProperty.h"
@interface SPDependencyContainer ()
{
NSMutableDictionary *_singletons;
NSMutableDictionary *_classes;
}
@end
@implementation SPDependencyContainer
+ (id)sharedContainer
{
static SPDependencyContainer *singleton;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [SPDependencyContainer new];
});
return singleton;
}
- (id)init
{
if (!(self = [super init]))
return nil;
_singletons = [NSMutableDictionary new];
_classes = [NSMutableDictionary new];
return self;
}
#pragma Proto > Instance map
- (void)registerSingleton:(id)singleton forProtocol:(Protocol*)proto
{
NSAssert([singleton conformsToProtocol:proto], @"%@ doesn't conform to %@", [singleton class], proto);
[_singletons setObject:singleton forKey:[self keyForProtocol:proto]];
}
- (void)removeSingletonForProtocol:(Protocol*)protocol
{
[_singletons removeObjectForKey:[self keyForProtocol:protocol]];
}
- (void)registerClass:(Class)klass forProtocol:(Protocol*)proto
{
NSAssert([klass conformsToProtocol:proto], @"%@ doesn't actually conform to %@", klass, proto);
[_classes setObject:klass forKey:[self keyForProtocol:proto]];
}
- (id)instanceForProtocol:(Protocol*)proto
{
return [self instanceForProtocol:proto arguments:nil];
}
- (id)instanceForProtocol:(Protocol*)proto arguments:(NSArray*)arguments
{
id key = [self keyForProtocol:proto];
id instance = [_singletons objectForKey:key];
if (instance)
return instance;
Class klass = [_classes objectForKey:key];
if (!klass)
return nil;
if ([klass instancesRespondToSelector:@selector(initWithArguments:)]) {
instance = [[klass alloc] initWithArguments:arguments];
} else {
instance = [[klass alloc] init];
}
if (instance)
[self injectDependencies:instance];
return instance;
}
- (id)keyForProtocol:(Protocol*)proto
{
NSValue *key = [NSValue valueWithPointer:(__bridge const void *)(proto)];
return key;
}
#pragma mark Injection
- (void)injectDependencies:(id)target
{
for(RTProperty *prop in [[target class] rt_properties])
[self injectProperty:prop on:target];
}
- (void)injectProperty:(RTProperty*)prop on:(id)target
{
NSString *type = [prop typeEncoding];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"@\"<(\\w+)>\"" options:0 error:NULL];
NSTextCheckingResult *checking = [regex firstMatchInString:type options:0 range:NSMakeRange(0, type.length)];
if (checking.numberOfRanges < 2)
return;
NSString *protocolName = [type substringWithRange:[checking rangeAtIndex:1]];
Protocol *proto = NSProtocolFromString(protocolName);
if (!proto)
return;
id instance = [self instanceForProtocol:proto arguments:nil];
if (!instance) {
NSLog(@"WARNING: Failed to inject a %@ into %@.%@", protocolName, target, prop.name);
return;
}
[target setValue:instance forKey:prop.name];
}
@end
#import <Foundation/Foundation.h>
/** Holds weak references. Note that objectAtIndex may return nil! */
@interface SPMutableWeakArray : NSMutableArray
- (id)init;
- (void)pruneNilEntries;
@end
#import "SPWeakArray.h"
@interface SPWeakHolder : NSObject
@property(nonatomic, weak) id object;
@end
@implementation SPMutableWeakArray {
NSMutableArray *_weakHolders;
}
- (id)init
{
if (!(self = [super init]))
return nil;
_weakHolders = [NSMutableArray new];
return self;
}
- (NSUInteger)count
{
return [_weakHolders count];
}
- (id)objectAtIndex:(NSUInteger)index
{
return [[_weakHolders objectAtIndex:index] object];
}
- (void)addObject:(id)anObject
{
SPWeakHolder *holder = [SPWeakHolder new];
holder.object = anObject;
[_weakHolders addObject:holder];
}
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index
{
SPWeakHolder *holder = [SPWeakHolder new];
holder.object = anObject;
[_weakHolders insertObject:holder atIndex:index];
}
- (void)removeLastObject
{
[_weakHolders removeLastObject];
}
- (void)removeObjectAtIndex:(NSUInteger)index
{
[_weakHolders removeObjectAtIndex:index];
}
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject
{
SPWeakHolder *holder = [SPWeakHolder new];
holder.object = anObject;
[_weakHolders replaceObjectAtIndex:index withObject:anObject];
}
- (void)pruneNilEntries
{
NSIndexSet *toRemove = [self indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [obj object] == nil;
}];
[self removeObjectsAtIndexes:toRemove];
}
@end
@implementation SPWeakHolder
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment