├── 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 |
--------------------------------------------------------------------------------