Search code examples
objective-ctyphoon

How can I pass along runtime arguments to sub components using Typhoon?


Let's say I have two classes Foo and Bar. I can construct a Bar independently of Foo, but Foo needs a Bar. In Typhoon I define these two components and use them in my applicaiton however at runtime when constructing a Foo the barId that I pass in appears to be passed down to the other Bar component as a <TyphoonInjectionByRuntimeArgument: 0x15ea4670, type=Undifined> and not the NSString that I passed in.

I'm probably doing it wrong.

What should I do differently?

@implementation Assembly

-(Foo *)fooWithFooId:(NSString *)fooId andBarId:(NSString *)barId {
  return [TyphoonDefinition withClass:[Foo class] configuration:^(TyphoonDefinition* definition) {
    [definition useInitializer:@selector(initWithFooId:andBar:) parameters:^(TyphoonMethod *initializer) {
        [initializer injectParameterWith:fooId];
        [initializer injectParameterWith:[self barWithId:barId]];
    }];
  }];
}

-(Bar *)barWithBarId:(NSString *)barId {
  return [TyphoonDefinition withClass:[Bar class] configuration:^(TyphoonDefinition* definition) {
    [definition useInitializer:@selector(initWithBarId:) parameters:^(TyphoonMethod *initializer) {
      [initializer injectParameterWith:barId];
    }];
  }];
}

@end

////////

@implementation Foo

-(instancetype) initWithFooId:(NSString *)fooId andBar:(Bar *)bar
{
  self = [super init];
  self.fooId = fooId;
  self.bar = bar;
  return self;
}

@end

@implementation Bar

-(instancetype) initWithBarId:(NSString *)barId
{
  self = [super init];
  self.barId = barId;
  return self;
}

@end

Update:

After further review the issue appeared to be caused by using the runtime argument inside the definition to build another parameter for another definition. See the usage of NSString below:

@implementation Assembly

-(Foo *)fooWithFooId:(NSString *)fooId andBazId:(NSString *)bazId {
  return [TyphoonDefinition withClass:[Foo class] configuration:^(TyphoonDefinition* definition) {
    [definition useInitializer:@selector(initWithFooId:andBaz:) parameters:^(TyphoonMethod *initializer) {
        [initializer injectParameterWith:fooId];
        [initializer injectParameterWith:[self bazWithPathToBaz:[NSString stringWithFormat:@"/some/path/to/baz/%@", bazId]]];
    }];
  }];
}

-(Baz *)bazWithPathToBaz:(NSString *)bazPath {
  return [TyphoonDefinition withClass:[Baz class] configuration:^(TyphoonDefinition* definition) {
    [definition useInitializer:@selector(initWithBazPath:) parameters:^(TyphoonMethod *initializer) {
      [initializer injectParameterWith:bazPath];
    }];
  }];
}

@end

When called the path passed into the initializer would come be "/some/path/to/baz/<TyphoonInjectionByRuntimeArgument: 0x15ea4670, type=Undifined>"


Solution

  • How are you obtaining your built instance of Foo? Your assembly code above looks OK, but keep in mind the following:

    • Build-time: An assembly returns instances of TyphoonDefinition. Typhoon instruments this assembly to collect the information that it needs to return built instances at runtime. This includes injecting runtime arguments with proxies (instances of TyphoonInjectionByRuntimeArgument).
    • Run-time: The assembly now returns built components according to the rules defined in the definitions. (Its actually a proxy pointing at a TyphoonComponentFactory).

    After creating one ore more assemblies, the following is required to have the assembly return a built component:

    - (void)testFoobar
    {
        //Pass in a not-built assembly to TyphoonComponentFactory
        TyphoonComponentFactory *factory =
            [TyphoonBlockComponentFactory factoryWithAssemblies:@[[Assembly assembly]]]; 
    
        //Now we have a built assembly
        Assembly *builtAssembly = (Assembly*) factory;
    
        Foo *builtFoo = [builtAssembly fooWithFooId:@"1234" andBarId:@"5678"];
        NSLog(@"%@", builtFoo);
    }
    

    Result: <Foo: self.fooId=1234, self.bar=<Bar: self.barId=5678>>

    (or in the case of an app you could use plist-integration to bootstrap Typhoon and inject the app delegate. Note that you can inject the assembly itself into any component, either as a TyphoonComponentFactory or posing as one of your assembly interfaces. This is useful to transition from one object-graph (eg view controller) to another.)


    Edit:

    Although it wasn't the original intention, in most cases you can also use run-time arguments from within an assembly, and we now do this in our own apps. For example:

    - (OfferManager *)offerManager
    {
        return [TyphoonDefinition withClass:[OfferManager class] 
            configuration:^(TyphoonDefinition *definition) {
            [definition useInitializer:@selector(initWithEngine:) 
                parameters:^(TyphoonMethod *initializer) {
    
                [initializer injectParameterWith:
                    [self engineWithPollingInterval:@120 cacheExpire:@3600]];
            }];
        }];
    }
    

    . . but currently its not possible to create a top-level definition that is the result of another definition with runtime arguments. Example:

    - (Engine*) preConfiguredEngine {
        return [self engineWithPollingInterval:@120 cacheExpire:@3600];
    } 
    

    . . the reason is fairly simple, and if you need this we can look at it.