Till Schneidereit

Partly, yes

SwiftSuspenders 1.6: A Tale of Small Changes and Big Plans

| Comments

I just released version 1.6 of my not-so-small-anymore IoC container SwiftSuspenders. This release marks the end of the line for 1.x-releases, all future development work will go towards the all-new, shiny 2.0 release. Bug fixing will continue as per usual, of course.

Small Changes?

Yes - in 1.6:

  • An easy but nevertheless significant performance optimizations for child injectors: Through a slight refactoring of the InjectionPoint class, I was able to make it fully independent from the injector that creates them, enabling them to be shared among all injectors in the VM instance. This allows them to be cached once, removing the significant performance overhead associated in inspecting classes to find injection points for all but the first injector.

  • Some usability improvements such as better error, warning and info messages on trying to instantiate interfaces, re-mapping previously mapped types and construction of dummy instances to work around Flash Player bugs.

Right, but Big Plans?

Indeed - for 2.0!

For the next major release, I plan to change things up quite a bit. The main features on the roadmap are:

  • a much nicer API
  • factory mappings
  • field mappings
  • live injections
  • optional injections
  • weak mappings in child injectors
  • full live-cycle management with [PreDestroy] annotations
  • a new format for external, runtime-loadable definition of both injection points and type mappings
  • full implementation of the metadata standardization @darscan started, for example:
  • optionally shortening [Inject(name='name')] to [Inject('name')]

That sounds just swell, but I’d really like some details, please!

Of course you do!

The shiny new API

SwiftSuspenders’ API mainly consists of methods that facilitate the mapping of types (and, optionally, names) to responses. Internally, this already is a two-step process: First, an object is created that contains the request configuration (i.e., the type and the optional name) and second, this request is assigned a response of a certain type: A value, class, singleton or rule mapping.

While 2.0 will ship with a façade to make porting from 1.x easy, the real interface will expose this process as the two steps it really is. Look forward to seeing mapping commands that look (approximately) like this:

1
2
3
4
5
6
7
8
9
10
injector.map(YourClass); //current equivalent: injector.mapClass(YourClass, YourClass);
injector.map(YourClass, 'name'); //current equivalent: injector.mapClass(YourClass, YourClass, 'name');
injector.map(YourClass).to(YourClass); //equivalent to the first mapping (i.e.: optional)
injector.map(YourClass, 'name').to(YourClass); //equivalent to the second mapping (i.e.: optional)
injector.map(YourInterface).toType(YourClass); //current equivalent: injector.mapClass(YourInterface, YourClass);
injector.map(YourClass).asSingleton(); //current equivalent: injector.mapSingleton(YourClass);
injector.map(YourInterface).toType(YourClass).asSingleton(); //current equivalent: injector.mapSingletonOf(YourInterface, YourClass);
injector.map(YourInterface).toValue(value); //current equivalent: injector.mapValue(YourInterface, value);
injector.map(YourInterface).toRule(otherRule); //current equivalent: injector.mapRule(YourInterface, otherRule);
var request : InjectionRequest = injector.map(YourClass); //current equivalent: var request : InjectionConfig = injector.getMapping(YourClass);

Basically, the API will be somewhat more DSL-ish, without going all the way into that direction, though: I like to think of it as bundling up all parameters related to one concept in one method call: First everything related to what gets mapped, then, optionally, to what it gets mapped and finally, how its being mapped.

Additionally, the small rest of the API will be subject to some tweaks, I suppose.

Factory mappings

I haven’t really gotten through the concept phase for these. The absolute minimum is support for injector.map().toFactory() and injector.map().toFactoryMethod(), but if I come up with some clever mechanism for parameterizing specific injections, I would like to support that as well.

Field mappings

In order to turn SwiftSuspenders into a light-weight one-way binding solution, 2.0 will support mapping of fields in objects with injector.map().toField(fieldName, inObject). If the mapped field is bindable, SwiftSuspenders will listen to its change events and update the affected live injections:

Live injections

Now those are something I’m really looking forward to. By specifying the live-parameter in your Inject-metadata (as in [Inject(live)]), you basically create a one-way binding that updates the value as soon as the associated mapping changes or, in the case of field mappings, the relevant binding event is dispatched as described above.

Optional injections

These are exactly what you think they are. [Inject(optional)] will instruct the injector not to throw an exception if it can’t find a mapping for the requested type.

Weak mappings

Imagine you have a module that you want to test - or perhaps even deploy - standalone but also want to load into a bigger application. Now this module might have some configuration that’s different based on what context it gets loaded into. If its loaded into another application, all or parts of the configuration should be supplied by that application, but if its running standalone, it has to deal with configuration all by itself. Enter weak mappings: These instruct the injector to always ask its parent injector if it has a mapping for the injection request. Only if that returns empty, the weakly mapped value, in this case the module’s configuration, is used.

Tear-down with [PreDestroy]

Just as [Inject] is used to build object graphs, [PreDestroy] facilitates tearing them down. When instructed to destroy a certain instance that it created itself or that was injected into with injector.injectInto, SwiftSuspenders will first go through all injection points in the instance and destroy those recursively (if they are only used by this instance, not if they are singleton or value mappings, of course) and the invoke all methods marked with [PreDestroy] metadata.

This will probably be used to replace the currently hard-coded preRemove and onRemove methods in Robotlegs mediators.

New runtime-loadable configuration format for injection points and mappings

The current XML-based configuration format was solely meant to work around restrictions in the Flash CS* AS3 compiler which doesn’t support custom metadata out of the box. This problem has since been worked around, rendering the format obsolete as it is right now. As quite some people expressed interest in being able to mix hard-coded and runtime-loaded configuration, I will try to come up with a good format for that. For now, I don’t have anything in that direction, so we’ll see how that goes. And if you want to define just such a format: Please do (and tell me about it).

Standardization FTW

One thing I like about Robotlegs is how @darscan went to great lengths to prevent as much lock-in as possible: Not only is the framework’s core just a set of interfaces that makes it easy to roll your own for each part, the framework specifically tries to make as many parts of your application independent and even ignorant of its existence as possible. Toward that end, Shaun started a metadata standardization effort with the goal of getting as much agreement on the format of injection point- configuration metadata as possible between the various IoC containers out there.

And while I like to think of SwiftSuspenders (especially in its future 2.0-form) as being a pretty decent IoC container implementation, you should definitely check out SmartyPants, Swiz, Dawn, Spicelib and other IoC containers if only out of interest. I’d also like for you to tell me of you don’t like SwiftSuspenders or you think it’s missing some Very Important Feature, though. Anyway, to allow you to switch from or to SwiftSuspenders as painlessly as possible, I’ll implement as much of whatever everyone decides upon as possible in 2.0 and future releases.

That’s quite a lot of work you just described. You really think you can handle all of it for 2.0?

Yeah, I know: This sounds like (and is) a lot. I’ll pack all of this up in the form of a roadmap on the github wiki, prioritizing stuff and distributing it across beta releases. Depending on when it makes sense to do a new major release (which in turn depends on when we decide to release Robotlegs 2.0), I might let one or two of the described features slip into 2.*.

But, some of these features already exist in forks other people published on github and which I was very bad at keeping up with. It is my hope that I can integrate much of these other great people’s hard work instead of doing much myself.

Comments