Skip to content

Commit ac3b4ca

Browse files
Added dependency injection (#1662)
* Added dependency injection * Fix lint * Moved DependencyInjection under Utils Refactors and made ServiceContainer thread safe * Refactors and made ServiceContainer thread safe * Added comment documentation * Added ServiceType documentation --------- Co-authored-by: Austin Condiff <austin.condiff@gmail.com>
1 parent 58faf4d commit ac3b4ca

File tree

5 files changed

+136
-0
lines changed

5 files changed

+136
-0
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
2B7AC06B282452FB0082A5B8 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B7AC06A282452FB0082A5B8 /* Media.xcassets */; };
6060
2BE487EF28245162003F3F64 /* FinderSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE487EE28245162003F3F64 /* FinderSync.swift */; };
6161
2BE487F428245162003F3F64 /* OpenWithCodeEdit.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
62+
300051672BBD3A5D00A98562 /* ServiceContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300051662BBD3A5D00A98562 /* ServiceContainer.swift */; };
63+
3000516A2BBD3A8200A98562 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300051692BBD3A8200A98562 /* ServiceType.swift */; };
64+
3000516C2BBD3A9500A98562 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */; };
6265
3026F50F2AC006C80061227E /* InspectorAreaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */; };
6366
30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; };
6467
3E0196732A3921AC002648D8 /* codeedit_shell_integration.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */; };
@@ -618,6 +621,9 @@
618621
2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenWithCodeEdit.appex; sourceTree = BUILT_PRODUCTS_DIR; };
619622
2BE487EE28245162003F3F64 /* FinderSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinderSync.swift; sourceTree = "<group>"; };
620623
2BE487F028245162003F3F64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
624+
300051662BBD3A5D00A98562 /* ServiceContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceContainer.swift; sourceTree = "<group>"; };
625+
300051692BBD3A8200A98562 /* ServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceType.swift; sourceTree = "<group>"; };
626+
3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = "<group>"; };
621627
3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorAreaViewModel.swift; sourceTree = "<group>"; };
622628
30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = "<group>"; };
623629
3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.zsh; sourceTree = "<group>"; };
@@ -1281,6 +1287,16 @@
12811287
path = OpenWithCodeEdit;
12821288
sourceTree = "<group>";
12831289
};
1290+
300051652BBD3A4400A98562 /* DependencyInjection */ = {
1291+
isa = PBXGroup;
1292+
children = (
1293+
300051662BBD3A5D00A98562 /* ServiceContainer.swift */,
1294+
300051692BBD3A8200A98562 /* ServiceType.swift */,
1295+
3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */,
1296+
);
1297+
path = DependencyInjection;
1298+
sourceTree = "<group>";
1299+
};
12841300
3026F50B2AC006A10061227E /* ViewModels */ = {
12851301
isa = PBXGroup;
12861302
children = (
@@ -2163,6 +2179,7 @@
21632179
58D01C85293167DC00C5B6B4 /* Utils */ = {
21642180
isa = PBXGroup;
21652181
children = (
2182+
300051652BBD3A4400A98562 /* DependencyInjection */,
21662183
6C48D8EF2972DAC300D6D205 /* Environment */,
21672184
58D01C87293167DC00C5B6B4 /* Extensions */,
21682185
58D01C8F293167DC00C5B6B4 /* KeyChain */,
@@ -3284,6 +3301,7 @@
32843301
B65B11012B09D5D4002852CF /* GitClient+Pull.swift in Sources */,
32853302
2072FA13280D74ED00C7F8D4 /* HistoryInspectorModel.swift in Sources */,
32863303
852E62012A5C17E500447138 /* PageAndSettings.swift in Sources */,
3304+
3000516C2BBD3A9500A98562 /* ServiceWrapper.swift in Sources */,
32873305
77A01E1F2BB33FB500F0EA38 /* CEWorkspaceSettingsView.swift in Sources */,
32883306
587B9DA029300ABD00AC7927 /* PanelDivider.swift in Sources */,
32893307
58822534292C280D00E83CDE /* CursorLocation.swift in Sources */,
@@ -3509,6 +3527,7 @@
35093527
587B9E6329301D8F00AC7927 /* GitLabAccount.swift in Sources */,
35103528
6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */,
35113529
285FEC7027FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift in Sources */,
3530+
300051672BBD3A5D00A98562 /* ServiceContainer.swift in Sources */,
35123531
77A01E582BBD7ECE00F0EA38 /* AddCETaskView.swift in Sources */,
35133532
6CB9144B29BEC7F100BC47F2 /* (null) in Sources */,
35143533
587B9E7429301D8F00AC7927 /* URL+URLParameters.swift in Sources */,
@@ -3615,6 +3634,7 @@
36153634
58D01C97293167DC00C5B6B4 /* String+SHA256.swift in Sources */,
36163635
B6EA1FFD29DB792C001BF195 /* ThemeSettingsColorPreview.swift in Sources */,
36173636
2806E904297958B9000040F4 /* ContributorRowView.swift in Sources */,
3637+
3000516A2BBD3A8200A98562 /* ServiceType.swift in Sources */,
36183638
6C578D8C29CD372700DC73B2 /* ExtensionCommands.swift in Sources */,
36193639
77A01E272BB4249800F0EA38 /* CEWorkspaceSettingsData.swift in Sources */,
36203640
77A01E802BC5101200F0EA38 /* EnvironmentVariableListItem.swift in Sources */,

CodeEdit/AppDelegate.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
1616
private var openWindow
1717

1818
func applicationDidFinishLaunching(_ notification: Notification) {
19+
setupServiceContainer()
1920
enableWindowSizeSaveOnQuit()
2021
Settings.shared.preferences.general.appAppearance.applyAppearance()
2122
checkForFilesToOpen()
@@ -231,6 +232,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
231232
}
232233
}
233234

235+
/// Setup all the services into a ServiceContainer for the application to use.
236+
private func setupServiceContainer() {
237+
// Example for how services will be instantiated
238+
// ServiceContainer.register(
239+
// PasteboardService()
240+
// )
241+
}
242+
234243
extension AppDelegate {
235244
static let recoverWorkspacesKey = "recover.workspaces"
236245
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// ServiceContainer.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 4/3/24.
6+
//
7+
8+
import Foundation
9+
10+
/// A service container that manages the registration and resolution of services.
11+
enum ServiceContainer {
12+
/// A dictionary storing the closures for creating service instances.
13+
private static var factories: [ObjectIdentifier: () -> Any] = [:]
14+
/// A dictionary storing the cached service instances.
15+
private static var cache: [ObjectIdentifier: Any] = [:]
16+
/// A dispatch queue used for synchronizing access to the factories and cache.
17+
private static let queue = DispatchQueue(label: "ServiceContainerQueue")
18+
19+
/// Registers a factory closure for creating instances of a service type.
20+
///
21+
/// - Parameter factory: An autoclosure that returns an instance of the service type.
22+
static func register<Service>(_ factory: @autoclosure @escaping () -> Service) {
23+
queue.sync {
24+
let key = ObjectIdentifier(Service.Type.self)
25+
factories[key] = factory
26+
}
27+
}
28+
29+
/// Resolves an instance of a service type based on the specified resolution type.
30+
///
31+
/// - Parameters:
32+
/// - resolveType: The type of resolution to use for the service. Defaults to `.singleton`.
33+
/// - type: The type of the service to resolve.
34+
/// - Returns: An instance of the resolved service type, or `nil` if the service is not registered.
35+
static func resolve<Service>(_ resolveType: ServiceType = .singleton, _ type: Service.Type) -> Service? {
36+
let serviceId = ObjectIdentifier(Service.Type.self)
37+
38+
return queue.sync {
39+
switch resolveType {
40+
case .singleton:
41+
if let service = cache[serviceId] as? Service {
42+
return service
43+
} else {
44+
let service = factories[serviceId]?() as? Service
45+
46+
if let service = service {
47+
cache[serviceId] = service
48+
}
49+
50+
return service
51+
}
52+
case .newSingleton:
53+
let service = factories[serviceId]?() as? Service
54+
55+
if let service = service {
56+
cache[serviceId] = service
57+
}
58+
59+
return service
60+
case .new:
61+
return factories[serviceId]?() as? Service
62+
}
63+
}
64+
}
65+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// ServiceType.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 4/3/24.
6+
//
7+
8+
/// Defines the type of service instantiation strategy.
9+
enum ServiceType {
10+
/// Returns a new singleton on the first call, then returns a cached one every other time
11+
case singleton
12+
/// Creates a new singleton reference each time and caches it, returning the newer singleton
13+
case newSingleton
14+
/// Creates a new singleton
15+
case new
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// ServiceWrapper.swift
3+
// CodeEdit
4+
//
5+
// Created by Abe Malla on 4/3/24.
6+
//
7+
8+
/// A property wrapper that provides access to a service instance.
9+
@propertyWrapper
10+
struct Service<Service> {
11+
var service: Service
12+
13+
init(_ type: ServiceType = .singleton) {
14+
guard let service = ServiceContainer.resolve(type, Service.self) else {
15+
let serviceName = String(describing: Service.self)
16+
fatalError("No service of type \(serviceName) registered!")
17+
}
18+
19+
self.service = service
20+
}
21+
22+
var wrappedValue: Service {
23+
get { self.service }
24+
mutating set { service = newValue }
25+
}
26+
}

0 commit comments

Comments
 (0)