-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Auto-injection #13
Auto-injection #13
Conversation
c3a7afb
to
74dfc3e
Compare
@AliSoftware I will update documentation and playground after your review when we will agree on the implementation |
Also as you will see I rolled back to using |
Interesting feature!
_It some point I wish that |
@ilyapuchka Did you properly rebase that branch on top of develop? I saw in the travis build that the Sample app still had the depreciation warnings whereas I fixed them by using |
|
||
let stringKey: String | ||
switch key.associatedTag { | ||
case .String(let tagValue)? where tagValue.hasPrefix("InjectedWeak<"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we do more secure and error-proof tests rather than string-based comparisons here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, sorry, thats not needed anymore, redundant code from my attempts to make it work with structs. Will fix now.
No no, there is no objc runtime at all, I meant something else =) It relies completely on Swift reflection. Regarding the first question I actually does not detect what type is wrapped, instead I rely on tags that are constructed based on that wrapped types, so there is no confusion cause the same tag is used to register original definition for not wrapped type. |
Ah I see. Don't like the string-based comparisons at all anyway (I know that Swift's |
Yep, that looks a bit fragile but as long as runtime generated tags and I will appreciate your thought on that, cause I've spent a lot of time with this implementation and now it's hard to think about other possibilities (though I think I tried everything) |
Regarding rebase it's strange cause what I did was just adding implementation after I pull latest develop. Will check now. |
Ah, that's right, looks like you fixed warnings only in app code, not in tests, I will fix that =) |
Wooops, my bad ;) |
Mmmmh problem is I'd say that this is the most likely stuff that might change, a bit like the implementation of |
What I can suggest to make it safer is instead of using |
Another possible solution could be to define some protocol, like |
Maybe provide a protocol default implementation that return the |
I've made some changes that make tag generation safer. If fact now it does not really matter what its value will be, protocols will ensure that the right one is used. But to make it easier to debug I think it is better to use "(Injected.self)" and "(InjectedWeak.self)". Also using generic T in this tag makes it possible to have different tags for different wrapped types. And reflection code became obvious now. Also you may find this interesting - https://twitter.com/ilyapuchka/status/668870356225142784 To provide custom tags with additional protocol looks impossible. The problem could be in some bugs in protocol extensions (I personally filed two or three) but more likely in the way Swift picks up implementation when you cast your object in runtime to more general type. It picks up more general implementation when we need to use more specific one. And there is no way we can provide more specific type doing reflection, we just can not construct it cause it's a generic type. I've pushed it to separate brunch - bf9aa5e - so you can check out. |
a4eb0c5
to
1377a9b
Compare
I refactored how the keys for auto injection are managed so that they are now encapsulated in |
ff4f365
to
1656389
Compare
1656389
to
6bbcc01
Compare
@AliSoftware I tried to extract this implementation to separate framework, but it looks much more complicated and requires lots of delegate callbacks from container, which makes complicated both container and "extension". Some of the issues I could not solve, like storing auto injected instances for singleton scope. So I think it's better to include it in core. Also it does not change registration/resolution API, it only changes how you define properties in you classes and whether you use |
6bbcc01
to
3ab83b5
Compare
3ab83b5
to
9a99ed4
Compare
1e9e4c2
to
92f80e5
Compare
I rebased this brunch on develop and squashed commits. |
Oh god and it's already monday, 1am… won't have time to review this again until 2-3 days, will keep you posted. |
No pressure 😉 |
@AliSoftware this one is desperately waiting for you review too =) |
Oh god thx for the reminder, totally forgot those! Will review now. |
Ok was stopped in my motivation by a phone call from family that lasted a bit longer than expected and now it's already late and haven't even eaten yet… so will probably do tomorrow instead 😉 but I didn't forget yet |
Looks good at first. |
Yes, sure. I just wanted you first to check out the implementation and if it's ok for you proceed with documenting. |
Yeah I'm ok Reading a quite too simple example especially with only one injected property, I didn't immediately see the benefit of using So maybe in the playground you can (1) use a first simple example of Client/Server to demonstrate how it helps with circular dips (2) but add another example using classes with 2 or 3 injected properties to demonstrate how it helps calling |
7a9c59d
to
932b27c
Compare
I like the examples, 👌 |
932b27c
to
7cc4528
Compare
7cc4528
to
cc39903
Compare
@AliSoftware let's merge this? |
Yep, that's what I meant by those ":ok_hand: :shipit:" emojis, I thought you'd do it :laughing: |
This PR adds auto-injection feature.
What is auto-injection
Auto-injection is a feature that let's user to get all dependencies (properties) injected by container automatically without calling
resolve
for each dependency.Why we need this
How it works
This implementation provides not completely automatic resolution which seems to be impossible to implement in Swift without using ObjC runtime. So to inject dependencies user needs:
resolveDependencies
method of container and pass it this instanceresolveDependencies
method uses reflection to inspect types of properties. To work around different issues, like initial nil values of properties, weak references and others we use simple wrapper classes:Injected<T>
andInjectedWeak<T>
. UsingMirror
we iterate over properties and pick up those of typeInjected
orInjectedWeak
(actually dummy internal protocols). Then we useresolve
and assign resolved instance to values wrapped by those properties. The fact thatInjected
andInjectedWeak
are classes makes it possible to change wrapped value not only in the mirror, but also in reflected object cause values of mirror children are copied by reference as any class in Swift (though Apple does not document this behavior of Mirror with all the possible consequences).To be able to resolve this wrapped values for each definition registered with
nil
tag and block-factory that does not accept runtime arguments we register two additional definitions forAny
andAnyObject
type (for strong and weak properties correspondingly) associated with special tags. Those tags are just string descriptions of type being registered wrapped inInjected
orInjectedWeak
. Using such tags makes it possible to distinguish those accessory definitions, cause they all resolveAny
orAnyObject
types. User will likely not use such tags so there should be no collisions.Drawbacks
resolveDependencies
method ofDefinitionOf
. But using auto-injection registration syntax looks much cleaner. Also I think thatInjected
andInjectedWeak
wrappers serve as a developer's documentation to indicate that those properties will be injected at runtime, so developer does not need to go to the point where all registration is performed. Other frameworks like Typhoon also use similar approach.Mirror
enough to be sure that required behavior (referencing to child values instead of coping) will not change in future. And actually they already changed reflection API (but not the behavior I believe) between Swift 1.2 and Swift 2.0. But this behavior looks logical and reflection is a language feature, not an OS, so we will be able to adapt (or disable) this feature on demand in later Swift releases.Example