├── .gitignore ├── Package.swift ├── README.md ├── Sources └── DIAttribute │ ├── DIResolver.swift │ ├── Inject.swift │ └── RegisterBuilder.swift └── Tests ├── DIAttributeTests ├── DIAttributeTests.swift └── XCTestManifests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | .swiftpm 6 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "DIAttribute", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "DIAttribute", 12 | targets: ["DIAttribute"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "DIAttribute", 23 | dependencies: []), 24 | .testTarget( 25 | name: "DIAttributeTests", 26 | dependencies: ["DIAttribute"]), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DIAttribute 2 | === 3 | 4 | [WIP] DIAttribute introduces dependency injection attribute. 5 | 6 | # Installation 7 | 8 | You can install this framework with Swift Package Manager in Xcode 11. 9 | 10 | # Feature 11 | 12 | ## Inject protocol values 13 | 14 | ```swift 15 | final class ViewController: UIViewController { 16 | @Inject(Self.self) var apiClient: APIClientProtocol 17 | 18 | ... 19 | } 20 | 21 | // Production 22 | DIResolver.register(ViewController.self, keyPath: \.apiClient, value: ProductionAPIClient()) 23 | 24 | // Test 25 | DIResolver.register(ViewController.self, keyPath: \.apiClient, value: MockAPIClient()) 26 | ``` 27 | 28 | ## Inject multiple values 29 | 30 | ```swift 31 | struct Environment { 32 | @Inject(Self.self) var endpoint: URL 33 | @Inject(Self.self) var timeZone: TimeZone 34 | } 35 | 36 | DIResolver.register(Environment.self) { 37 | Register(URL.self, URL(string: "https://example.com")!) 38 | Register(TimeZone.self, TimeZone(identifier: "Asia/Tokyo")!) 39 | } 40 | ``` 41 | 42 | # License 43 | DIAttribute is released under the MIT license. See LICENSE for details. 44 | -------------------------------------------------------------------------------- /Sources/DIAttribute/DIResolver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DIResolver.swift 3 | // 4 | // 5 | // Created by Tatsuya Tanaka on 2019/06/15. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct DIResolver { 11 | public typealias Factory = () -> Value 12 | 13 | private static var factories: [ObjectIdentifier: [ObjectIdentifier: Factory]] = [:] 14 | 15 | public static func register( 16 | _ target: Target.Type = Target.self, 17 | type: Value.Type, 18 | value: @autoclosure @escaping Factory) { 19 | let targetKey = ObjectIdentifier(target) 20 | let valueKey = ObjectIdentifier(Value.self) 21 | register(targetKey: targetKey, valueKey: valueKey, value: value) 22 | } 23 | 24 | public static func register( 25 | _ target: Target.Type = Target.self, 26 | keyPath: KeyPath, 27 | value: @autoclosure @escaping Factory) { 28 | let targetKey = ObjectIdentifier(target) 29 | let valueKey = ObjectIdentifier(Value.self) 30 | register(targetKey: targetKey, valueKey: valueKey, value: value) 31 | } 32 | 33 | public static func register( 34 | _ target: Target.Type = Target.self, 35 | @RegisterBuilder registerBuilder: () -> [Register]) { 36 | let targetKey = ObjectIdentifier(target) 37 | for register in registerBuilder() { 38 | self.register(targetKey: targetKey, valueKey: register.key, value: register.value) 39 | } 40 | } 41 | 42 | private static func register( 43 | targetKey: ObjectIdentifier, 44 | valueKey: ObjectIdentifier, 45 | value: @escaping Factory) { 46 | if factories[targetKey] == nil { 47 | factories[targetKey] = [valueKey: value] 48 | } else { 49 | factories[targetKey]![valueKey] = value 50 | } 51 | } 52 | 53 | public static func resolve( 54 | _ target: Target.Type = Target.self, 55 | value: Value.Type = Value.self) -> Value? { 56 | factories[.init(target)]?[.init(value)]?() as? Value 57 | } 58 | 59 | public static func clear() { 60 | factories = [:] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/DIAttribute/Inject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Inject.swift 3 | // 4 | // 5 | // Created by Tatsuya Tanaka on 2019/06/15. 6 | // 7 | 8 | import Foundation 9 | 10 | @propertyWrapper 11 | public struct Inject { 12 | public init(_ type: Target.Type) {} 13 | 14 | public var wrappedValue: Value { 15 | DIResolver.resolve(Target.self)! 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/DIAttribute/RegisterBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RegisterBuilder.swift 3 | // 4 | // 5 | // Created by Tatsuya Tanaka on 2019/06/15. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Register { 11 | let key: ObjectIdentifier 12 | let value: () -> Any 13 | 14 | public init(_ type: T.Type, _ value: @autoclosure @escaping () -> T) { 15 | key = ObjectIdentifier(type) 16 | self.value = value 17 | } 18 | } 19 | 20 | @_functionBuilder public struct RegisterBuilder { 21 | public static func buildBlock( 22 | _ registers: Register... 23 | ) -> [Register] { 24 | registers 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/DIAttributeTests/DIAttributeTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import DIAttribute 3 | 4 | private protocol SampleProtocol { 5 | var stringValue: String { get } 6 | } 7 | 8 | private struct Sample: SampleProtocol { 9 | let stringValue: String 10 | } 11 | 12 | final class DIAttributeTests: XCTestCase { 13 | override func setUp() { 14 | DIResolver.clear() 15 | } 16 | 17 | func testInjectPrimitiveType() { 18 | struct A { 19 | @Inject(Self.self) var value: Int 20 | } 21 | 22 | DIResolver.register(A.self, type: Int.self, value: 123) 23 | XCTAssertEqual(A().value, 123) 24 | } 25 | 26 | func testInjectProtocol() { 27 | class A { 28 | @Inject(A.self) var sample: SampleProtocol 29 | } 30 | 31 | let sample = Sample(stringValue: "hello") 32 | DIResolver.register(A.self, keyPath: \.sample, value: sample) 33 | XCTAssertEqual(A().sample.stringValue, sample.stringValue) 34 | } 35 | 36 | func testInjectMultiValue() { 37 | struct A { 38 | @Inject(Self.self) var value: Int 39 | @Inject(Self.self) var sample: SampleProtocol 40 | } 41 | 42 | let sample = Sample(stringValue: "hello") 43 | DIResolver.register(A.self) { 44 | Register(Int.self, 123) 45 | Register(SampleProtocol.self, sample) 46 | } 47 | XCTAssertEqual(A().value, 123) 48 | XCTAssertEqual(A().sample.stringValue, sample.stringValue) 49 | } 50 | 51 | func testInjectStaticValue() { 52 | struct A { 53 | @Inject(Self.self) static var value: Int 54 | } 55 | 56 | DIResolver.register(A.self, type: Int.self, value: 321) 57 | XCTAssertEqual(A.value, 321) 58 | } 59 | 60 | static var allTests = [ 61 | ("testInjectPrimitiveType", testInjectPrimitiveType), 62 | ("testInjectProtocol", testInjectProtocol), 63 | ("testInjectMultiValue", testInjectMultiValue), 64 | ("testInjectStaticValue", testInjectStaticValue) 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /Tests/DIAttributeTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(DIAttributeTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import DIAttributeTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += DIAttributeTests.allTests() 7 | XCTMain(tests) 8 | --------------------------------------------------------------------------------