ishkawa / dikit Goto Github PK
View Code? Open in Web Editor NEWA statically typed dependency injector for Swift.
License: MIT License
A statically typed dependency injector for Swift.
License: MIT License
Will be tapped to https://github.com/ishkawa/homebrew-formulae.
When we use storyboard segue. We don't instantiate view controller, but we receives a new view controller immediately after instantiation by storyboard segue.In this case, we can't use Injectable
and FactoryMethodInjectable
(explained in #2).
One possible way to inject dependency is property injection.
protocol PropertyInjectable {
associatedtype Dependency
var dependency: Dependency! { get set }
}
Once a type conforms to PropertyInjectable
, it should be able to receive dependencies via inject(toSomeClass:)
method.
// Generated code by dikitgen
extension SomeResolver {
func inject(toSomeClass: SomeClass) {
someClass.dependency = ...
}
}
Hi, @ishkawa
I'm using your library in product and CI. I noticed that it always fails building in CI.
To use this library in Xcode 10 (or above) and CI, I use output files like "$SRCROOT/AppResolver.generated.swift" in Run Script phases, which also done in R.swift.
Please add about "Output Files" to README.
Thanks!
Rough sketch:
final class Counter {
var value = 0
}
protocol MyResolver: Resolver {
// A MyResolver instance always returns the same instance of Counter.
func provideCounter() -> Shared<Counter>
}
// Generated code
extension MyResolver {
func resolveCounter() -> Counter {
final class Scope {
// NOTE: WeakDictionary doesn't present in standard library.
static var sharedInstances = [:] as WeakDictionary<MyResolver, Counter>
}
// Return shared instance associated with MyResolver instance if it presents.
if let sharedInstance = Scope.sharedInstances[self] {
return associatedObject
}
// If shared instance is not found, call provider method and save returned instance as a shared instance.
let sharedInstance = provideCounter()
Scope.sharedInstances[self] = sharedInstance
return sharedInstance
}
}
I am super interested in this project and recently started working on a similar concept but I think it would be best to help contribute to this project instead. What are the goals and milestones for the future of this project so that I can help contribute and place efforts where they are most needed?
At this moment, dikitgen never reports errors.
Errors are useful for developers to find out what is wrong and how can they fix them.
Currently, dikitgen
expects that Injectable.Dependency
is declared as a struct.
Structs can have computed properties and methods, but the type elements are not related to declaring dependencies. I think Injectable.Dependency
should focus on expressing dependencies, and the other works should be done by other components.
To encourage developers to focus on defining dependencies, I'm planning to convert Injectable.Dependency
to tuple type.
I wrote this test in forked project. (Tests/DIGenKitTests/CodeGeneratorTests.swift)
But failed.
func testMultipleResolver() throws {
let code = """
import DIKit
struct A: Injectable, FactoryMethodInjectable {
struct Dependency {
let value: Int
}
init(dependency: Dependency) {}
static func makeInstance(dependency: Dependency) {}
}
struct B: Injectable, FactoryMethodInjectable {
struct Dependency {
let value: Int
}
init(dependency: Dependency) {}
static func makeInstance(dependency: Dependency) {}
}
protocol TestResolverA: Resolver {
func provideA() -> A
}
protocol TestResolverB: Resolver {
func provideB() -> B
}
"""
let file = File(contents: code)
let generator = try CodeGenerator(files: [file])
let contents = try generator.generate().trimmingCharacters(in: .whitespacesAndNewlines)
// Generated code uses provider method only
XCTAssertEqual(contents, """
//
// Resolver.swift
// Generated by dikitgen.
//
import DIKit
extension TestResolverA {
func resolveA() -> A {
return provideA()
}
}
extension TestResolverB {
func resolveB() -> B {
return provideB()
}
}
""")
}
//
// Resolver.swift
// Generated by dikitgen.
//
import DIKit
extension TestResolverA {
func resolveA() -> A {
return provideA()
}
func resolveB(value: Int) -> B {
return B(dependency: .init(value: value))
}
}
extension TestResolverB {
func resolveA(value: Int) -> A {
return A(dependency: .init(value: value))
}
func resolveB() -> B {
return provideB()
}
}
Workaround at this moment: Make typealias within the same module.
There're some situations that developers can't override initializer. In such situations, factory method version of Injectable protocol helps developer to implement dependency injection.
Suppose we have UIViewController
as a root view controller of storyboard. We can't make the view controller to conform Injectable
, because we can't substitute storyboard view controller into self
in init(dependency:)
.
To support this case, factory method version of Injectable
is necessary.
protocol FactoryMethodInjectable {
associatedtype Dependency
static func makeInstance(dependency: Dependency) -> Self
}
Implementation of conformance looks like below:
final class SomeViewController: UIViewController, FactoryMethodInjectable {
struct Dependency {...}
static func makeInstance(dependency: Dependency) -> SomeViewController {
let storyboard = UIStoryboard(name: "Some", bundle: nil)
let viewController = storyboard.instantiateInitialViewController() as! SomeViewController
viewController.dependency = dependency
return viewController
}
private var dependency: Dependency!
}
Once SomeViewController
conforms to FactoryMethodInjectable
, it should be able to resolved same as conformers of Injectable
.
// Generated code by dikitgen
extension SomeResolver {
func resolveSomeViewController() -> SomeViewController {
...
return makeInstance(dependency: ...)
}
}
I'm trying to support Xcode 10.
However, the generated xcodeproj is changed every time when I execute swift package generate-xcodeproj
.
Swift Package Manager is not yet suitable for iOS.
I think that it is necessary to replace the generate of xcodeproj file with another tool.
I would like to consider migration from Swift Package Manager.
When separating sources to some frameworks, there are multiple directories on project root.
But if we use dikitgen for the project root, it recognizes Carthage/Checkouts/DIKit
...
I find several ways to solve this issue,
Carthage
directory..digenkitignore
The last one seems smart for me. Any idea?
There is no problem with the simulator, but applications depend on DIKit can not be installed on the actual iOS machine.
-[MIExecutableBundle codeSigningInfoByValidatingResources:performingOnlineAuthorization:ignoringCachedSigningInfo:checkingTrustCacheIfApplicable:error:]: 759: Code signing identifier (DIKit-5555494433789024584e33efa5e65138640f2c13) does not match bundle identifier (DIKit) for /private/var/installd/Library/Caches/com.apple.mobile.installd.staging/temp.boLOcV/extracted/Xxx.app/Frameworks/DIKit.framework
I think that this problem is caused by Swift 4.1 and SwiftPM.
Add PRODUCT_BUNDLE_IDENTIFIER = DIKit;
to DIKit.xcodeproj/project.pbxproj after bfe0dc0.
This should be org.ishkawa.DIKit
instead of DIKit
.
And this causes unexpected behavior to DIKit_Info.plist.
Unfortunately, I have no idea with a way to easily solve it.
However, I confirmed that it can be avoided by revert bfe0dc0.
yutailang0119@5dabb48
When I define a class that conforms to Injectable and has a closure in its Dependency like below:
class SomeModel: Injectable {
struct Dependency {
let stringFactory: (Int) -> String
}
let stringFactory: (Int) -> String
required init(dependency: Dependency) {
self.stringFactory = dependency.stringFactory
}
}
Then, I run dikitgen
and I get error below. Closures in function parameter need @escaping
.
// Passing non-escaping parameter 'stringFactory' to function expecting an @escaping closure
// Parameter 'stringFactory' is implicitly non-escaping
func resolveSomeModel(stringFactory: (Int) -> String) -> SomeModel {
return SomeModel(dependency: .init(stringFactory: stringFactory))
}
I can avoid this error by creating factory container like below:
struct StringFactoryContainer {
let factory: (Int) -> String
}
And use like this:
class SomeModel: Injectable {
struct Dependency {
let stringFactoryContainer: StringFactoryContainer
}
let stringFactory: (Int) -> String
required init(dependency: Dependency) {
self.stringFactory = dependency.stringFactoryContainer.factory
}
}
Does anyone know good way to use closure in Dependency?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.