├── InjectableDemo ├── Assets.xcassets │ ├── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── InjectableDemoApp.swift ├── ContentViewModel.swift ├── InjectableDemoApp+Injections.swift ├── MyService.swift ├── Info.plist ├── InjectableType.swift ├── ContentView.swift └── Injectable.swift ├── InjectableDemo.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── michael.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── michael.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── InjectableDemoTests ├── InjectableDemoTests.swift └── Info.plist └── README.md /InjectableDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /InjectableDemo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /InjectableDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /InjectableDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /InjectableDemo.xcodeproj/xcuserdata/michael.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /InjectableDemo.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hmlongco/InjectableDemo/HEAD/InjectableDemo.xcodeproj/project.xcworkspace/xcuserdata/michael.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /InjectableDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /InjectableDemo/InjectableDemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectableDemoApp.swift 3 | // InjectableDemo 4 | // 5 | // Created by Michael Long on 7/31/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct InjectableDemoApp: App { 12 | // init() { 13 | // //Injections.registerMockServices() 14 | // } 15 | var body: some Scene { 16 | WindowGroup { 17 | ContentView() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /InjectableDemo.xcodeproj/xcuserdata/michael.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | InjectableDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /InjectableDemoTests/InjectableDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectableDemoTests.swift 3 | // InjectableDemoTests 4 | // 5 | // Created by Michael Long on 7/31/21. 6 | // 7 | 8 | import XCTest 9 | @testable import InjectableDemo 10 | 11 | class InjectableDemoTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | Injections.registerMockServices() 15 | } 16 | 17 | func testExample() throws { 18 | let viewModel = ContentViewModel() 19 | XCTAssert(viewModel.id.contains("Mock")) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /InjectableDemo/ContentViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewModel.swift 3 | // InjectableDemo 4 | // 5 | // Created by Michael Long on 7/31/21. 6 | // 7 | 8 | import Foundation 9 | 10 | class ContentViewModel: ObservableObject { 11 | 12 | @Injectable(\.myService) var service 13 | 14 | @Published var count = 0 15 | @Published var string = "" 16 | 17 | var id: String { 18 | service.service() 19 | } 20 | 21 | init() { 22 | print("init ContentViewModel") 23 | } 24 | 25 | deinit { 26 | print("deinit ContentViewModel") 27 | } 28 | 29 | func bump() { 30 | count += 1 31 | } 32 | 33 | func test() { 34 | print(service.service() + " = \(count)") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /InjectableDemo/InjectableDemoApp+Injections.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InjectableDemoApp+Injections.swift 3 | // InjectableDemo 4 | // 5 | // Created by Michael Long on 12/13/21. 6 | // 7 | 8 | import Foundation 9 | 10 | // basic injection extensions 11 | 12 | extension Injections { 13 | var myService: MyServiceType { shared( MyService() as MyServiceType ) } 14 | var mockServiceType: MyServiceType { MockService() } 15 | } 16 | 17 | // testing constructor injection 18 | 19 | extension Injections { 20 | var constructedService: ConstructedService { 21 | ConstructedService(resolve(\.myService)) 22 | } 23 | } 24 | 25 | // testing registering mocks 26 | 27 | extension Injections { 28 | func registerMockServices() { 29 | register { self.shared( MockService() as MyServiceType ) } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /InjectableDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /InjectableDemo/MyService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyService.swift 3 | // InjectableDemo 4 | // 5 | // Created by Michael Long on 7/31/21. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol MyServiceType { 11 | var id: UUID { get } 12 | func service() -> String 13 | } 14 | 15 | class MyService: MyServiceType { 16 | let id = UUID() 17 | // init() { 18 | // print("init MyService \(id)") 19 | // } 20 | // deinit { 21 | // print("deinit MyService \(id)") 22 | // } 23 | func service() -> String { 24 | "Service \(id)" 25 | } 26 | } 27 | 28 | class MockService: MyServiceType { 29 | let id = UUID() 30 | func service() -> String { 31 | "Mock \(id)" 32 | } 33 | } 34 | 35 | // service with constructor injection 36 | 37 | class ConstructedService { 38 | let id = UUID() 39 | let service: MyServiceType 40 | init(_ service: MyServiceType) { 41 | self.service = service 42 | print("init ConstructedService \(id)") 43 | print("init ConstructedService using \(service.id)") 44 | } 45 | deinit { 46 | print("deinit ConstructedService \(id)") 47 | print("deinit ConstructedService using \(service.id)") 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /InjectableDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /InjectableDemo/InjectableType.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// InjectableType.swift 3 | //// InjectableDemo 4 | //// 5 | //// Created by Michael Long on 1/2/22. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | //#define INJECTABLE 11 | // 12 | //// add simple injectable type 13 | // 14 | //public protocol InjectableType { 15 | // static func resolve(_ args: Any?) -> Self 16 | //} 17 | // 18 | //extension Injectable { 19 | // public init() where Service: InjectableType { 20 | // self.service = (Injections.container as? InjectableTypeSupport)?.resolve(nil) 21 | // } 22 | //} 23 | // 24 | //public class InjectableTypeSupport: Injections { 25 | // 26 | // public override init() {} 27 | // 28 | // public func register(factory: @escaping (_ arg: Any) -> Service) { 29 | // defer { lock.unlock() } 30 | // lock.lock() 31 | // let id = Int(bitPattern: ObjectIdentifier(Service.self)) 32 | // registrationsArgs[id] = factory 33 | // } 34 | // 35 | // public func resolve(_ args: Any? = nil) -> Service where Service: InjectableType { 36 | // return registered(args) ?? Service.resolve(args) 37 | // } 38 | // 39 | // public func registered(_ args: Any?) -> Service? { 40 | // defer { lock.unlock() } 41 | // lock.lock() 42 | // let id = Int(bitPattern: ObjectIdentifier(Service.self)) 43 | // return registrationsArgs[id]?(args) as? Service 44 | // } 45 | // 46 | // public override func reset() { 47 | // super.reset() 48 | // registrationsArgs = [:] 49 | // } 50 | // 51 | // internal var registrationsArgs: [Int:(_ arg: Any?) -> Any] = [:] 52 | // 53 | //} 54 | //// testing injectable type 55 | // 56 | //final class MyInjectableType { 57 | // init() { 58 | // print("init MyInjectableType") 59 | // } 60 | //} 61 | // 62 | //extension MyInjectableType: InjectableType { 63 | // static func resolve(_ args: Any?) -> MyInjectableType { 64 | // MyInjectableType() 65 | // } 66 | //} 67 | -------------------------------------------------------------------------------- /InjectableDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /InjectableDemo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // InjectableDemo 4 | // 5 | // Created by Michael Long on 7/31/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | init() { 12 | print("init ContentView") 13 | } 14 | var body: some View { 15 | NavigationView { 16 | VStack { 17 | NavigationLink("Test", destination: TestView()) 18 | } 19 | .onAppear { 20 | print("ContentView appeared") 21 | } 22 | } 23 | } 24 | } 25 | 26 | struct TestView: View { 27 | @StateObject var viewModel = TestViewModel() 28 | init() { 29 | print("init TestView") 30 | } 31 | var body: some View { 32 | Text("TestView") 33 | .onAppear { 34 | print("TestView appeared") 35 | } 36 | } 37 | } 38 | 39 | class TestViewModel: ObservableObject { 40 | @Injectable(\.myService) var service 41 | let id = UUID() 42 | init() { 43 | print("init VM \(id)") 44 | } 45 | deinit { 46 | print("deinit VM \(id)") 47 | } 48 | } 49 | 50 | 51 | class TestViewModel2: ObservableObject { 52 | @Injectable(\.myService) var service 53 | @Injectable(\.constructedService) var constructedService 54 | let id = UUID() 55 | init() { 56 | print("init vm \(id)") 57 | print("init vm using \(service.id)") 58 | } 59 | deinit { 60 | print("deinit vm \(id)") 61 | print("deinit vm using \(service.id)") 62 | } 63 | } 64 | 65 | struct TestView2: View { 66 | 67 | @StateObject var viewModel = ContentViewModel() 68 | 69 | var body: some View { 70 | VStack(spacing: 16) { 71 | Text("\(viewModel.id)") 72 | .font(.footnote) 73 | 74 | Text("\(viewModel.count)") 75 | 76 | TextField("Name", text: $viewModel.string) 77 | 78 | Button("Increment") { 79 | viewModel.bump() 80 | } 81 | 82 | NavigationLink("Next", destination: TestView2()) 83 | } 84 | .padding() 85 | .onAppear { 86 | viewModel.test() 87 | } 88 | } 89 | 90 | } 91 | 92 | struct WrappedTestView: View { 93 | var body: some View { 94 | TestView2() 95 | } 96 | } 97 | 98 | 99 | struct ContentView_Previews: PreviewProvider { 100 | static var previews: some View { 101 | Injections.container.registerMockServices() 102 | return ContentView() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Injectable Demo 2 | 3 | Preliminary musings and demonstration code for a simple Swift property-wrapper, keypath-based dependency injection system. The keypaths ensure compile-time safety for all injectable services. 4 | 5 | Injectable also supports overriding services for mocking and testing purposes, as well as a rudimentary thread-safe scoping system that enables unique, shared, cached, and application-level scopes for services. 6 | 7 | ## Note 8 | 9 | **Some of the concepts discussed here are now used in my new dependency injection framework, [Factory](https://github.com/hmlongco/Factory).** 10 | 11 | ## Demo Code 12 | Here's a SwiftUI view that uses an injectable view model. 13 | 14 | ```swift 15 | struct ContentView: View { 16 | 17 | @InjectableObject(\.contentViewModel) var viewModel 18 | 19 | var body: some View { 20 | VStack(spacing: 16) { 21 | Text("\(viewModel.id)") 22 | .font(.footnote) 23 | 24 | NavigationLink("Next", destination: ContentView()) 25 | } 26 | .onAppear(perform: { 27 | viewModel.test() 28 | }) 29 | } 30 | } 31 | ``` 32 | And here's the code for the view model which in turn has its own injectable service. 33 | ```Swift 34 | class ContentViewModel { 35 | 36 | @Injectable(\.myServiceType) var service 37 | 38 | var id: String { 39 | service.service() 40 | } 41 | 42 | func test() { 43 | print(service.service()) 44 | } 45 | } 46 | ``` 47 | Note that `MyServiceType` is a protocol and as such can be overridden with other values for testing. 48 | 49 | The service protocol, service, and a mock service appear as follows. 50 | ```swift 51 | protocol MyServiceType { 52 | func service() -> String 53 | } 54 | 55 | class MyService: MyServiceType { 56 | private let id = UUID() 57 | func service() -> String { 58 | "Service \(id)" 59 | } 60 | } 61 | 62 | class MockService: MyServiceType { 63 | private let id = UUID() 64 | func service() -> String { 65 | "Mock \(id)" 66 | } 67 | } 68 | ``` 69 | 70 | ## Resolving the ViewModel and Services 71 | 72 | Here's are the registrations that resolve the various keypaths. 73 | ```swift 74 | extension Injections { 75 | var contentViewModel: ContentViewModel { shared( ContentViewModel() ) } 76 | var myServiceType: MyServiceType { shared( MyService() ) } 77 | } 78 | ``` 79 | For each one we extend `Injections` to add a factory closure that will be called to provide a new instance of the viewmodel or service when needed. 80 | 81 | Note that we're using shared scopes here in order to ensure persistance across view updates in SwiftUI. 82 | 83 | ## Mocking and Testing 84 | 85 | The key to overriding a given service for mocking and testing lies in adding a Resolver-style inferred-type registration factory that will override the keypath registration. 86 | ```swift 87 | extension Injections { 88 | static func registerMockServices() { 89 | container.register { MockService() as MyServiceType } 90 | // others as needed 91 | } 92 | } 93 | ``` 94 | Here's an example of the mocks being used in the ContentView preview. 95 | ```swift 96 | struct ContentView_Previews: PreviewProvider { 97 | static var previews: some View { 98 | Injections.registerMockServices() 99 | return ContentView() 100 | } 101 | } 102 | ``` 103 | 104 | ## Injectable 105 | 106 | And finally, here's part of the @Injectable property wrapper that demonstrates the basic technique used. The initialization function checks to see if an override exists (optional). If not it resorts to using the required keypath. 107 | ```swift 108 | @propertyWrapper public struct Injectable { 109 | 110 | private var service: Service 111 | 112 | public init(_ keyPath: KeyPath) { 113 | self.service = Injections.container.resolve() ?? Injections.container[keyPath: keyPath] 114 | } 115 | 116 | ... 117 | 118 | } 119 | ``` 120 | As the initializer requires the keypath, it *must* exist. Thus all registrations are required to exist, which ensures compile-time safety. 121 | 122 | Overrides to the keypaths are exceptions to the rule, and are treated as such. 123 | 124 | All of the code, including the code for the scopes, requires about 160 lines of code. That also includes an addtional property wrapper, `@InjectableObject`, which can be used in SwiftUI code like an `ObservableObject`. 125 | 126 | ## The Idea 127 | 128 | The impetus for this code and demo resolves around an article written by Antoine van der Lee, titled [Dependency Injection in Swift using latest Swift features](https://www.avanderlee.com/swift/dependency-injection/). 129 | 130 | That article, in turn, triggered my own Medium article, [I Hate Swift. I Love Swift](https://medium.com/geekculture/i-hate-swift-i-love-swift-318171a0f0df), where I detailed some of my own attempts to solve some of the issues perceived in Antoine's original approach. 131 | 132 | And this is the final result. 133 | -------------------------------------------------------------------------------- /InjectableDemo/Injectable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Injectable.swift 3 | // InjectableDemo 4 | // 5 | // Created by Michael Long on 7/31/21. 6 | // 7 | 8 | import Foundation 9 | 10 | // Injectable property wrapper 11 | 12 | @propertyWrapper public struct Injectable { 13 | private var service: Service 14 | public init(_ keyPath: KeyPath) { 15 | self.service = Injections.container.resolve(keyPath) 16 | } 17 | public var wrappedValue: Service { 18 | get { return service } 19 | mutating set { service = newValue } 20 | } 21 | public var projectedValue: Injectable { 22 | get { return self } 23 | mutating set { self = newValue } 24 | } 25 | } 26 | 27 | @propertyWrapper public struct LazyInjectable { 28 | private var keyPath: KeyPath? 29 | private var service: Service! 30 | public init(_ keyPath: KeyPath) { 31 | self.keyPath = keyPath 32 | } 33 | public var wrappedValue: Service { 34 | mutating get { 35 | resolve() 36 | return service 37 | } 38 | mutating set { 39 | service = newValue 40 | } 41 | } 42 | public var projectedValue: LazyInjectable { 43 | mutating get { 44 | resolve() 45 | return self 46 | } 47 | mutating set { 48 | self = newValue 49 | } 50 | } 51 | private mutating func resolve() { 52 | guard service == nil else { 53 | return 54 | } 55 | if let keyPath = keyPath { 56 | self.service = Injections.container.resolve(keyPath) 57 | } 58 | } 59 | } 60 | 61 | // add core container class for factories with registration and resolution mechanisms for overrides 62 | 63 | public class Injections { 64 | 65 | // global injection container 66 | 67 | public static let container = Injections() 68 | 69 | // registration functions 70 | 71 | public func register(factory: @escaping () -> Service?) { 72 | defer { lock.unlock() } 73 | lock.lock() 74 | let id = Int(bitPattern: ObjectIdentifier(Service.self)) 75 | registrations[id] = factory 76 | } 77 | 78 | // resolution fuctions 79 | 80 | public func resolve(_ keyPath: KeyPath) -> Service { 81 | defer { lock.unlock() } 82 | lock.lock() 83 | return registered() ?? Self.container[keyPath: keyPath] 84 | } 85 | 86 | public func optional(_ keyPath: KeyPath) -> Service? { 87 | defer { lock.unlock() } 88 | lock.lock() 89 | return registered() ?? Self.container[keyPath: keyPath] 90 | } 91 | 92 | // singleton scope where services exist for lifetime of the app 93 | public var application: InjectableScope = InjectableCacheScope() 94 | 95 | // cached scope where services exist until scope is reset 96 | public var cached: InjectableScope = InjectableCacheScope() 97 | 98 | // shared scope where services are maintained until last reference is released 99 | public var shared: InjectableScope = InjectableSharedScope() 100 | 101 | public func reset() { 102 | defer { lock.unlock() } 103 | lock.lock() 104 | registrations = [:] 105 | } 106 | 107 | // private 108 | 109 | private func registered() -> Service? { 110 | let id = Int(bitPattern: ObjectIdentifier(Service.self)) 111 | return registrations[id]?() as? Service 112 | } 113 | 114 | private init() {} 115 | private var registrations: [Int:() -> Any] = [:] 116 | private var lock = NSRecursiveLock() 117 | 118 | } 119 | 120 | public protocol InjectableScope { 121 | func callAsFunction(_ factory: @autoclosure () -> S) -> S 122 | func release(_ type: S.Type) 123 | func reset() 124 | } 125 | 126 | class InjectableCacheScope: InjectableScope { 127 | func callAsFunction(_ factory: @autoclosure () -> S) -> S { 128 | defer { lock.unlock() } 129 | lock.lock() 130 | let id = Int(bitPattern: ObjectIdentifier(S.self)) 131 | if let service = cache[id] as? S { 132 | return service 133 | } 134 | let service = factory() 135 | cache[id] = service 136 | return service 137 | } 138 | func release(_ type: S.Type) { 139 | defer { lock.unlock() } 140 | lock.lock() 141 | let id = Int(bitPattern: ObjectIdentifier(type)) 142 | cache.removeValue(forKey: id) 143 | } 144 | func reset() { 145 | defer { lock.unlock() } 146 | lock.lock() 147 | cache = [:] 148 | } 149 | fileprivate var cache = [Int:Any]() 150 | fileprivate var lock = NSRecursiveLock() 151 | } 152 | 153 | class InjectableSharedScope: InjectableScope { 154 | private struct WeakBox { 155 | weak var service: AnyObject? 156 | } 157 | func callAsFunction(_ factory: @autoclosure () -> S) -> S { 158 | defer { lock.unlock() } 159 | lock.lock() 160 | let id = Int(bitPattern: ObjectIdentifier(S.self)) 161 | if let service = cache[id]?.service as? S { 162 | return service 163 | } 164 | let service = factory() 165 | cache[id] = WeakBox(service: service as AnyObject) 166 | return service 167 | } 168 | func release(_ type: S.Type) { 169 | defer { lock.unlock() } 170 | lock.lock() 171 | let id = Int(bitPattern: ObjectIdentifier(type)) 172 | cache.removeValue(forKey: id) 173 | } 174 | func reset() { 175 | defer { lock.unlock() } 176 | lock.lock() 177 | cache = [:] 178 | } 179 | private var cache = [Int:WeakBox]() 180 | private var lock = NSRecursiveLock() 181 | } 182 | -------------------------------------------------------------------------------- /InjectableDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4C0F3B202782381C00AC87EF /* InjectableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0F3B1F2782381C00AC87EF /* InjectableType.swift */; }; 11 | 4C3BF5462767F22000E9EA68 /* InjectableDemoApp+Injections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BF5452767F22000E9EA68 /* InjectableDemoApp+Injections.swift */; }; 12 | 4CEA348926B5E7A400174C3D /* InjectableDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEA348826B5E7A400174C3D /* InjectableDemoApp.swift */; }; 13 | 4CEA348B26B5E7A400174C3D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEA348A26B5E7A400174C3D /* ContentView.swift */; }; 14 | 4CEA348D26B5E7A600174C3D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CEA348C26B5E7A600174C3D /* Assets.xcassets */; }; 15 | 4CEA349026B5E7A600174C3D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CEA348F26B5E7A600174C3D /* Preview Assets.xcassets */; }; 16 | 4CEA349B26B5E7A600174C3D /* InjectableDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEA349A26B5E7A600174C3D /* InjectableDemoTests.swift */; }; 17 | 4CEA34B426B5E7CB00174C3D /* Injectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEA34B326B5E7CB00174C3D /* Injectable.swift */; }; 18 | 4CEA34B826B5EAD600174C3D /* ContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEA34B726B5EAD600174C3D /* ContentViewModel.swift */; }; 19 | 4CEA34BA26B5EB9000174C3D /* MyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CEA34B926B5EB9000174C3D /* MyService.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 4CEA349726B5E7A600174C3D /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 4CEA347D26B5E7A400174C3D /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 4CEA348426B5E7A400174C3D; 28 | remoteInfo = InjectableDemo; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 4C0F3B1F2782381C00AC87EF /* InjectableType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectableType.swift; sourceTree = ""; }; 34 | 4C3BF5452767F22000E9EA68 /* InjectableDemoApp+Injections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InjectableDemoApp+Injections.swift"; sourceTree = ""; }; 35 | 4CEA348526B5E7A400174C3D /* InjectableDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InjectableDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 4CEA348826B5E7A400174C3D /* InjectableDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectableDemoApp.swift; sourceTree = ""; }; 37 | 4CEA348A26B5E7A400174C3D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 38 | 4CEA348C26B5E7A600174C3D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 39 | 4CEA348F26B5E7A600174C3D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 40 | 4CEA349126B5E7A600174C3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 4CEA349626B5E7A600174C3D /* InjectableDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = InjectableDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 4CEA349A26B5E7A600174C3D /* InjectableDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectableDemoTests.swift; sourceTree = ""; }; 43 | 4CEA349C26B5E7A600174C3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | 4CEA34B326B5E7CB00174C3D /* Injectable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Injectable.swift; sourceTree = ""; }; 45 | 4CEA34B726B5EAD600174C3D /* ContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewModel.swift; sourceTree = ""; }; 46 | 4CEA34B926B5EB9000174C3D /* MyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyService.swift; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 4CEA348226B5E7A400174C3D /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | 4CEA349326B5E7A600174C3D /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 4CEA347C26B5E7A400174C3D = { 68 | isa = PBXGroup; 69 | children = ( 70 | 4CEA348726B5E7A400174C3D /* InjectableDemo */, 71 | 4CEA349926B5E7A600174C3D /* InjectableDemoTests */, 72 | 4CEA348626B5E7A400174C3D /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 4CEA348626B5E7A400174C3D /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 4CEA348526B5E7A400174C3D /* InjectableDemo.app */, 80 | 4CEA349626B5E7A600174C3D /* InjectableDemoTests.xctest */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | 4CEA348726B5E7A400174C3D /* InjectableDemo */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 4CEA34B326B5E7CB00174C3D /* Injectable.swift */, 89 | 4C0F3B1F2782381C00AC87EF /* InjectableType.swift */, 90 | 4CEA348826B5E7A400174C3D /* InjectableDemoApp.swift */, 91 | 4C3BF5452767F22000E9EA68 /* InjectableDemoApp+Injections.swift */, 92 | 4CEA348A26B5E7A400174C3D /* ContentView.swift */, 93 | 4CEA34B726B5EAD600174C3D /* ContentViewModel.swift */, 94 | 4CEA34B926B5EB9000174C3D /* MyService.swift */, 95 | 4CEA348C26B5E7A600174C3D /* Assets.xcassets */, 96 | 4CEA349126B5E7A600174C3D /* Info.plist */, 97 | 4CEA348E26B5E7A600174C3D /* Preview Content */, 98 | ); 99 | path = InjectableDemo; 100 | sourceTree = ""; 101 | }; 102 | 4CEA348E26B5E7A600174C3D /* Preview Content */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 4CEA348F26B5E7A600174C3D /* Preview Assets.xcassets */, 106 | ); 107 | path = "Preview Content"; 108 | sourceTree = ""; 109 | }; 110 | 4CEA349926B5E7A600174C3D /* InjectableDemoTests */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 4CEA349A26B5E7A600174C3D /* InjectableDemoTests.swift */, 114 | 4CEA349C26B5E7A600174C3D /* Info.plist */, 115 | ); 116 | path = InjectableDemoTests; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | 4CEA348426B5E7A400174C3D /* InjectableDemo */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = 4CEA34AA26B5E7A600174C3D /* Build configuration list for PBXNativeTarget "InjectableDemo" */; 125 | buildPhases = ( 126 | 4CEA348126B5E7A400174C3D /* Sources */, 127 | 4CEA348226B5E7A400174C3D /* Frameworks */, 128 | 4CEA348326B5E7A400174C3D /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = InjectableDemo; 135 | productName = InjectableDemo; 136 | productReference = 4CEA348526B5E7A400174C3D /* InjectableDemo.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | 4CEA349526B5E7A600174C3D /* InjectableDemoTests */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 4CEA34AD26B5E7A600174C3D /* Build configuration list for PBXNativeTarget "InjectableDemoTests" */; 142 | buildPhases = ( 143 | 4CEA349226B5E7A600174C3D /* Sources */, 144 | 4CEA349326B5E7A600174C3D /* Frameworks */, 145 | 4CEA349426B5E7A600174C3D /* Resources */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | 4CEA349826B5E7A600174C3D /* PBXTargetDependency */, 151 | ); 152 | name = InjectableDemoTests; 153 | productName = InjectableDemoTests; 154 | productReference = 4CEA349626B5E7A600174C3D /* InjectableDemoTests.xctest */; 155 | productType = "com.apple.product-type.bundle.unit-test"; 156 | }; 157 | /* End PBXNativeTarget section */ 158 | 159 | /* Begin PBXProject section */ 160 | 4CEA347D26B5E7A400174C3D /* Project object */ = { 161 | isa = PBXProject; 162 | attributes = { 163 | LastSwiftUpdateCheck = 1250; 164 | LastUpgradeCheck = 1250; 165 | TargetAttributes = { 166 | 4CEA348426B5E7A400174C3D = { 167 | CreatedOnToolsVersion = 12.5; 168 | }; 169 | 4CEA349526B5E7A600174C3D = { 170 | CreatedOnToolsVersion = 12.5; 171 | TestTargetID = 4CEA348426B5E7A400174C3D; 172 | }; 173 | }; 174 | }; 175 | buildConfigurationList = 4CEA348026B5E7A400174C3D /* Build configuration list for PBXProject "InjectableDemo" */; 176 | compatibilityVersion = "Xcode 9.3"; 177 | developmentRegion = en; 178 | hasScannedForEncodings = 0; 179 | knownRegions = ( 180 | en, 181 | Base, 182 | ); 183 | mainGroup = 4CEA347C26B5E7A400174C3D; 184 | productRefGroup = 4CEA348626B5E7A400174C3D /* Products */; 185 | projectDirPath = ""; 186 | projectRoot = ""; 187 | targets = ( 188 | 4CEA348426B5E7A400174C3D /* InjectableDemo */, 189 | 4CEA349526B5E7A600174C3D /* InjectableDemoTests */, 190 | ); 191 | }; 192 | /* End PBXProject section */ 193 | 194 | /* Begin PBXResourcesBuildPhase section */ 195 | 4CEA348326B5E7A400174C3D /* Resources */ = { 196 | isa = PBXResourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 4CEA349026B5E7A600174C3D /* Preview Assets.xcassets in Resources */, 200 | 4CEA348D26B5E7A600174C3D /* Assets.xcassets in Resources */, 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | 4CEA349426B5E7A600174C3D /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXResourcesBuildPhase section */ 212 | 213 | /* Begin PBXSourcesBuildPhase section */ 214 | 4CEA348126B5E7A400174C3D /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | 4CEA34B826B5EAD600174C3D /* ContentViewModel.swift in Sources */, 219 | 4CEA34BA26B5EB9000174C3D /* MyService.swift in Sources */, 220 | 4CEA348B26B5E7A400174C3D /* ContentView.swift in Sources */, 221 | 4CEA34B426B5E7CB00174C3D /* Injectable.swift in Sources */, 222 | 4C0F3B202782381C00AC87EF /* InjectableType.swift in Sources */, 223 | 4CEA348926B5E7A400174C3D /* InjectableDemoApp.swift in Sources */, 224 | 4C3BF5462767F22000E9EA68 /* InjectableDemoApp+Injections.swift in Sources */, 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | }; 228 | 4CEA349226B5E7A600174C3D /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 4CEA349B26B5E7A600174C3D /* InjectableDemoTests.swift in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXSourcesBuildPhase section */ 237 | 238 | /* Begin PBXTargetDependency section */ 239 | 4CEA349826B5E7A600174C3D /* PBXTargetDependency */ = { 240 | isa = PBXTargetDependency; 241 | target = 4CEA348426B5E7A400174C3D /* InjectableDemo */; 242 | targetProxy = 4CEA349726B5E7A600174C3D /* PBXContainerItemProxy */; 243 | }; 244 | /* End PBXTargetDependency section */ 245 | 246 | /* Begin XCBuildConfiguration section */ 247 | 4CEA34A826B5E7A600174C3D /* Debug */ = { 248 | isa = XCBuildConfiguration; 249 | buildSettings = { 250 | ALWAYS_SEARCH_USER_PATHS = NO; 251 | CLANG_ANALYZER_NONNULL = YES; 252 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 253 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 254 | CLANG_CXX_LIBRARY = "libc++"; 255 | CLANG_ENABLE_MODULES = YES; 256 | CLANG_ENABLE_OBJC_ARC = YES; 257 | CLANG_ENABLE_OBJC_WEAK = YES; 258 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 259 | CLANG_WARN_BOOL_CONVERSION = YES; 260 | CLANG_WARN_COMMA = YES; 261 | CLANG_WARN_CONSTANT_CONVERSION = YES; 262 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 263 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 264 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 265 | CLANG_WARN_EMPTY_BODY = YES; 266 | CLANG_WARN_ENUM_CONVERSION = YES; 267 | CLANG_WARN_INFINITE_RECURSION = YES; 268 | CLANG_WARN_INT_CONVERSION = YES; 269 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 270 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 273 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 274 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 275 | CLANG_WARN_STRICT_PROTOTYPES = YES; 276 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 277 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 278 | CLANG_WARN_UNREACHABLE_CODE = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | COPY_PHASE_STRIP = NO; 281 | DEBUG_INFORMATION_FORMAT = dwarf; 282 | ENABLE_STRICT_OBJC_MSGSEND = YES; 283 | ENABLE_TESTABILITY = YES; 284 | GCC_C_LANGUAGE_STANDARD = gnu11; 285 | GCC_DYNAMIC_NO_PIC = NO; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_OPTIMIZATION_LEVEL = 0; 288 | GCC_PREPROCESSOR_DEFINITIONS = ( 289 | "DEBUG=1", 290 | "$(inherited)", 291 | ); 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 299 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 300 | MTL_FAST_MATH = YES; 301 | ONLY_ACTIVE_ARCH = YES; 302 | SDKROOT = iphoneos; 303 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 304 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 305 | }; 306 | name = Debug; 307 | }; 308 | 4CEA34A926B5E7A600174C3D /* Release */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ALWAYS_SEARCH_USER_PATHS = NO; 312 | CLANG_ANALYZER_NONNULL = YES; 313 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_ENABLE_OBJC_WEAK = YES; 319 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_COMMA = YES; 322 | CLANG_WARN_CONSTANT_CONVERSION = YES; 323 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 326 | CLANG_WARN_EMPTY_BODY = YES; 327 | CLANG_WARN_ENUM_CONVERSION = YES; 328 | CLANG_WARN_INFINITE_RECURSION = YES; 329 | CLANG_WARN_INT_CONVERSION = YES; 330 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 332 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 333 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 334 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 335 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 336 | CLANG_WARN_STRICT_PROTOTYPES = YES; 337 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 338 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 339 | CLANG_WARN_UNREACHABLE_CODE = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | COPY_PHASE_STRIP = NO; 342 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 343 | ENABLE_NS_ASSERTIONS = NO; 344 | ENABLE_STRICT_OBJC_MSGSEND = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu11; 346 | GCC_NO_COMMON_BLOCKS = YES; 347 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 348 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 349 | GCC_WARN_UNDECLARED_SELECTOR = YES; 350 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 351 | GCC_WARN_UNUSED_FUNCTION = YES; 352 | GCC_WARN_UNUSED_VARIABLE = YES; 353 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 354 | MTL_ENABLE_DEBUG_INFO = NO; 355 | MTL_FAST_MATH = YES; 356 | SDKROOT = iphoneos; 357 | SWIFT_COMPILATION_MODE = wholemodule; 358 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 359 | VALIDATE_PRODUCT = YES; 360 | }; 361 | name = Release; 362 | }; 363 | 4CEA34AB26B5E7A600174C3D /* Debug */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 367 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 368 | CODE_SIGN_STYLE = Automatic; 369 | DEVELOPMENT_ASSET_PATHS = "\"InjectableDemo/Preview Content\""; 370 | DEVELOPMENT_TEAM = 9ZVHDWC4WU; 371 | ENABLE_PREVIEWS = YES; 372 | INFOPLIST_FILE = InjectableDemo/Info.plist; 373 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 374 | LD_RUNPATH_SEARCH_PATHS = ( 375 | "$(inherited)", 376 | "@executable_path/Frameworks", 377 | ); 378 | PRODUCT_BUNDLE_IDENTIFIER = com.hmlong.InjectableDemo; 379 | PRODUCT_NAME = "$(TARGET_NAME)"; 380 | SWIFT_VERSION = 5.0; 381 | TARGETED_DEVICE_FAMILY = "1,2"; 382 | }; 383 | name = Debug; 384 | }; 385 | 4CEA34AC26B5E7A600174C3D /* Release */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 389 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 390 | CODE_SIGN_STYLE = Automatic; 391 | DEVELOPMENT_ASSET_PATHS = "\"InjectableDemo/Preview Content\""; 392 | DEVELOPMENT_TEAM = 9ZVHDWC4WU; 393 | ENABLE_PREVIEWS = YES; 394 | INFOPLIST_FILE = InjectableDemo/Info.plist; 395 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 396 | LD_RUNPATH_SEARCH_PATHS = ( 397 | "$(inherited)", 398 | "@executable_path/Frameworks", 399 | ); 400 | PRODUCT_BUNDLE_IDENTIFIER = com.hmlong.InjectableDemo; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | SWIFT_VERSION = 5.0; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | }; 405 | name = Release; 406 | }; 407 | 4CEA34AE26B5E7A600174C3D /* Debug */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 411 | BUNDLE_LOADER = "$(TEST_HOST)"; 412 | CODE_SIGN_STYLE = Automatic; 413 | DEVELOPMENT_TEAM = 9ZVHDWC4WU; 414 | INFOPLIST_FILE = InjectableDemoTests/Info.plist; 415 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 416 | LD_RUNPATH_SEARCH_PATHS = ( 417 | "$(inherited)", 418 | "@executable_path/Frameworks", 419 | "@loader_path/Frameworks", 420 | ); 421 | PRODUCT_BUNDLE_IDENTIFIER = com.hmlong.InjectableDemoTests; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | SWIFT_VERSION = 5.0; 424 | TARGETED_DEVICE_FAMILY = "1,2"; 425 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/InjectableDemo.app/InjectableDemo"; 426 | }; 427 | name = Debug; 428 | }; 429 | 4CEA34AF26B5E7A600174C3D /* Release */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 433 | BUNDLE_LOADER = "$(TEST_HOST)"; 434 | CODE_SIGN_STYLE = Automatic; 435 | DEVELOPMENT_TEAM = 9ZVHDWC4WU; 436 | INFOPLIST_FILE = InjectableDemoTests/Info.plist; 437 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 438 | LD_RUNPATH_SEARCH_PATHS = ( 439 | "$(inherited)", 440 | "@executable_path/Frameworks", 441 | "@loader_path/Frameworks", 442 | ); 443 | PRODUCT_BUNDLE_IDENTIFIER = com.hmlong.InjectableDemoTests; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | SWIFT_VERSION = 5.0; 446 | TARGETED_DEVICE_FAMILY = "1,2"; 447 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/InjectableDemo.app/InjectableDemo"; 448 | }; 449 | name = Release; 450 | }; 451 | /* End XCBuildConfiguration section */ 452 | 453 | /* Begin XCConfigurationList section */ 454 | 4CEA348026B5E7A400174C3D /* Build configuration list for PBXProject "InjectableDemo" */ = { 455 | isa = XCConfigurationList; 456 | buildConfigurations = ( 457 | 4CEA34A826B5E7A600174C3D /* Debug */, 458 | 4CEA34A926B5E7A600174C3D /* Release */, 459 | ); 460 | defaultConfigurationIsVisible = 0; 461 | defaultConfigurationName = Release; 462 | }; 463 | 4CEA34AA26B5E7A600174C3D /* Build configuration list for PBXNativeTarget "InjectableDemo" */ = { 464 | isa = XCConfigurationList; 465 | buildConfigurations = ( 466 | 4CEA34AB26B5E7A600174C3D /* Debug */, 467 | 4CEA34AC26B5E7A600174C3D /* Release */, 468 | ); 469 | defaultConfigurationIsVisible = 0; 470 | defaultConfigurationName = Release; 471 | }; 472 | 4CEA34AD26B5E7A600174C3D /* Build configuration list for PBXNativeTarget "InjectableDemoTests" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | 4CEA34AE26B5E7A600174C3D /* Debug */, 476 | 4CEA34AF26B5E7A600174C3D /* Release */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Release; 480 | }; 481 | /* End XCConfigurationList section */ 482 | }; 483 | rootObject = 4CEA347D26B5E7A400174C3D /* Project object */; 484 | } 485 | --------------------------------------------------------------------------------