This is a follow up to my previous post (How does the SwiftUI Environment work and can it be used outside SwiftUI for Dependency Injection?).
Since writing that post I've refined the MicroInjection Package I created a bit. The core library is still a single file but with a couple of new features and fairly decent documentation included the file is now about 100 lines long.
[Apologies for the lack of code snippets, they are a real fiddle on Posthaven. I might move blogging to my own server at some point but I need to find time to migrate things].
[Update 20th Feb 2021: Package 1.0.0 release.]
Additional Features since Previous Post
There is a reasonably full test suite which isn't very long either as despite the power there isn't actually a significant surface area to test.
What I have also done is added another file full of tests that shouldn't compile that are commented out but can be manually uncommented to check that the type safety is robust. At the moment this is a manual process. I can't immediately see a way to automate it easily. Possibly making them into separate test files and scripting each separately (and handling build failures as success). One to think about.
I've also tried GitHub actions for the first time and have set it up to build and test both on Mac and Linux.
Support For Testing Injectable Objects
I've added an optional init argument `callForUnstoredValues ` to the InjectionValues so that (mostly for tests) a provided closure will be called if any properties that have not been specifically inserted are requested. This allows tests to ensure that the objects they are testing only access the expected injected values that have been set up by the test and fail if other values are accessed (so either the object can be fixed or the test can be changed to set up all the objects).
It could also be used in production if you wanted to raise or report errors if values aren't stored but that would only make sense if the defaultValues set up were intended only as emergency fallbacks (a valid approach but I think I prefer them as intended for use in general but overridable if needed).
The closure you provide can either return nil (to still allow the default value to function) or return an object that will be returned instead (provided it can be cast to the correct type for the key used).
Set Override with Closure
[Update 20th Feb 2021: This feature was removed from the 1.0 release, an unnecessary complication - just because you can doesn't mean that you should.]
Originally only instances of the correct type could be used to override the default values. This change adds flexibility by allowing overriding with closure to provide the required values making it easier to provide factories and things.
I'm slightly nervous that this is too much flexibility as it may be unexpected behaviour for Injectable objects to provide different values on their wrapped property every call but it is already possible with the default value (and the same is true of the SwiftUI Environment). Views on the topic especially welcome.
What is still to do before 1.0?
I'm currently happy with the feature set and test coverage and I don't have any plans to expand the library. The main thing that I'm waiting on before declaring 1.0 is to be entirely comfortable with the naming. Some parts are obvious and I'm happy with especially where they match up with the SwiftUI Environment (InjectionValues -> EnvironmentValues, InjectionKey -> EnvironmentKey, @Injection -> @Environment).
With the Injectable protocol I'm happy with the name but I'm less sure about its `injection` property. I think it is OK but if anyone does have thoughts or suggestions please let me know. If I don't have a better plan by the end of the weekend I'm going to declare 1.0 anyway.
What is missing? (Post 1.0)
Not a lot. The one feature addition I would really like would be to be able to make structs injectable but it doesn't seem to be possible from what I can tell. If someone can solve this for me it would be great.
A really good usage guide with examples. The original blog post had some of that but an expanded example might be useful.
[Update 20th Feb 2021: Package 1.0.0 release includes this license information.]
I haven't picked one yet. If you need one and it isn't for a repressive regime or Facebook I'll likely grant a personal/company one for free with very broad rights. I may set up a file with licensee that people can make PRs too to gain a full commercial license. If you have suggestions of broadly permissive licenses that disallow evil uses then give me a shout. Also if someone wants to include in bigger open source project I can probably be persuaded to license under the same license as that project uses.