简体   繁体   中英

Typhoon - Runtime Configurable Components Using Storyboard

When my application starts I fetch a remote config file containing information (URLs, etc) required to configure other dependencies.

After I fetch the remote config I have a Config object that I need to supply to other TyphoonDefinition s.

Now I am also using the plist storyboard integration.

I was originally going down the path of injecting the assembly into the ViewController that loads the Config object, and when I receive the remote config and create the Config object, I would somehow set it as a property on the assembly. I did this hoping that I could then use the property in the definitions, but this did not work and I got:

2014-10-22 21:18:06.203 four[39840:516543] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'No component matching id 'setConfig:'.'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010a3e63f5 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010a07fbb7 objc_exception_throw + 45
    2   CoreFoundation                      0x000000010a3e632d +[NSException raise:format:] + 205
    3   four                                0x00000001070a011d -[TyphoonComponentFactory componentForKey:args:] + 148
    4   CoreFoundation                      0x000000010a2de22c __invoking___ + 140
    5   CoreFoundation                      0x000000010a2de082 -[NSInvocation invoke] + 290
    6   CoreFoundation                      0x000000010a36d456 -[NSInvocation invokeWithTarget:] + 54
    7   four                                0x000000010709d358 -[TyphoonBlockComponentFactory forwardInvocation:] + 276

Is there any way for me to inject an object into an assembly at runtime?

Is there a cleaner way to do what I'm trying to do?

I was reading about run-time arguments which sounds like what I need, but I really don't understand the docs.

For example, I have this as a definition. I need to pass the runtime Config object as a parameter to the constructor.

- (id<ApiService>)apiService
{
    return [TyphoonDefinition withClass:[ApiService class] configuration:^(TyphoonDefinition* definition) {}];
}

Using runtime arguments

For example, I have this as a definition. I need to pass the runtime Config object as a parameter to the constructor.

 - (id<ApiService>)apiService { return [TyphoonDefinition withClass:[ApiService class] configuration:^(TyphoonDefinition* definition) {}]; } 

Try something like this:

- (id<ApiService>)apiServiceWithConfig:(Config *)config {
    return [TyphoonDefinition withClass:[ApiService class] configuration:^(TyphoonDefinition* definition) {

        // inject into property: 
        [definition injectProperty:@selector(config) with:config];

        // inject into constructor:
        [definition useInitializer:@selector(initWithConfig:) parameters:^(TyphoonMethod *initializer) {
            [initializer injectParameterWith:config];
        }];

    }]; 
}

Using factory definition

Take a look into next two definitions:

- (Config *)currentConfig {
    return [TyphoonDefinition withFactory:[self configManager] selector:@selector(currentConfig)];
}

 - (ConfigManager *)configManager {
     return [TyphoonDefinition withClass:[ConfigManager class] configuration:^(TyphoonDefinition *definition){
         definition.scope = TyphoonScopeSingleton;
     }];
 }

Imagine you have a ConfigManager which downloads remote config and stores it as 'currentConfig' property, 'configManager' definition describes that object.

Then check the 'currentConfig' definition. This definition just returns result of calling 'currentConfig'method on ConfigManager instance.

Then you can inject config as:

- (id<ApiService>)apiService {
    return [TyphoonDefinition withClass:[ApiService class] configuration:^(TyphoonDefinition* definition) {
        [definition injectProperty:@selector(config) with:[self currentConfig]];
    }]; 
}

But make sure that currentConfig loaded (not nil) during 'apiService' creation. (maybe better inject ConfigManager instead - then if currentConfig nil, it would be filled later)

A few points:

  • Run-time arguments are super-cool feature and really help to get the full power of Typhoon. We strongly recommend to learn about them (ask questions here, if you like). They're not a good fit for what you're trying to do though.
  • It is possible to register a new definition in the container at runtime using:

.

- (void)registerDefinition:(TyphoonDefinition *)definition;

. . you probably don't want to do this in your case either.


what you are interested in is:

TyphoonComponentFactoryPostProcessor and TyphoonComponentPostProcessor:

Typhoon has two kinds of interfaces that can be attached, one is TyphoonComponentFactoryPostProcessor and the other is TyphoonComponentPostProcessor . They are used internally, but you can also write your own and do all kinds of cool things with them.

  • TyphoonComponentFactoryPostProcessor: Modifies the definitions (recipes) for a component before it gets built.

  • TyphoonComponentPostProcessor: Modifies the instances after they are built.


TyphoonConfig:

There is an existing TyphoonComponentFactoryPostProcessor that you can use for the task you described. Its called TyphoonConfigPostProcessor and is described in the user guide here .

All post processors can be attached either at built-time (in the assembly), or at runtime, as follows:

TyphoonConfigPostProcessor* configurer = [[TyphoonConfigPostProcessor alloc] init];
[configurer useResourceWithName:@"Configuration.plist"]];
[factory attachPostProcessor:configurer];


Note that, since TyphoonComponentFactoryPostProcessor modifies definitions , if you have components of singleton scope that are already built, they would not be affected. You'd have to either create lazy singletons, or call [factory unload] .


How do I get a reference to the TyphoonComponentFactory after startup?

Whichever thing in your app is doing the configuration step will need to be made 'Typhoon Aware'. You can do this by using the following:

- (ConfigController *)configController
{
    return [ConfigController withClass:[INFGiftDeliveryAddressController class] 
        configuration:^(TyphoonDefinition *definition)
    {        
        [definition injectProperty:@selector(factory) with:self];
    }];

. . when inject the assembly you can declare a property of type TyphoonComponentFactory or any of your assembly sub-classes. It will work either way. The documentation for this feature is here .

In mobile and desktop applications, your components will often need to be made 'Typhoon aware' so they we can proceed from one object graph (eg a view controller) to another (and this is where the run-time arguments feature can be useful). Note that you're coding to your own domain-specific assembly interface, so Typhoon is "non-invasive". For the most part your apps don't refer to any Typhoon APIs directly.

We've covered quite a lot here, so if you have any further questions or need clarifications don't hesitate to ask.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM