Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added dependency injection #1662

Merged
merged 7 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
2B7AC06B282452FB0082A5B8 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2B7AC06A282452FB0082A5B8 /* Media.xcassets */; };
2BE487EF28245162003F3F64 /* FinderSync.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BE487EE28245162003F3F64 /* FinderSync.swift */; };
2BE487F428245162003F3F64 /* OpenWithCodeEdit.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
300051672BBD3A5D00A98562 /* ServiceContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300051662BBD3A5D00A98562 /* ServiceContainer.swift */; };
3000516A2BBD3A8200A98562 /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300051692BBD3A8200A98562 /* ServiceType.swift */; };
3000516C2BBD3A9500A98562 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */; };
3026F50F2AC006C80061227E /* InspectorAreaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */; };
30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; };
3E0196732A3921AC002648D8 /* codeedit_shell_integration.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */; };
Expand Down Expand Up @@ -601,6 +604,9 @@
2BE487EC28245162003F3F64 /* OpenWithCodeEdit.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = OpenWithCodeEdit.appex; sourceTree = BUILT_PRODUCTS_DIR; };
2BE487EE28245162003F3F64 /* FinderSync.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinderSync.swift; sourceTree = "<group>"; };
2BE487F028245162003F3F64 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
300051662BBD3A5D00A98562 /* ServiceContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceContainer.swift; sourceTree = "<group>"; };
300051692BBD3A8200A98562 /* ServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceType.swift; sourceTree = "<group>"; };
3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = "<group>"; };
3026F50E2AC006C80061227E /* InspectorAreaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorAreaViewModel.swift; sourceTree = "<group>"; };
30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = "<group>"; };
3E0196722A3921AC002648D8 /* codeedit_shell_integration.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.zsh; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1247,6 +1253,16 @@
path = OpenWithCodeEdit;
sourceTree = "<group>";
};
300051652BBD3A4400A98562 /* DependencyInjection */ = {
isa = PBXGroup;
children = (
300051662BBD3A5D00A98562 /* ServiceContainer.swift */,
300051692BBD3A8200A98562 /* ServiceType.swift */,
3000516B2BBD3A9500A98562 /* ServiceWrapper.swift */,
);
path = DependencyInjection;
sourceTree = "<group>";
};
3026F50B2AC006A10061227E /* ViewModels */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2121,6 +2137,7 @@
58D01C85293167DC00C5B6B4 /* Utils */ = {
isa = PBXGroup;
children = (
300051652BBD3A4400A98562 /* DependencyInjection */,
6C48D8EF2972DAC300D6D205 /* Environment */,
58D01C87293167DC00C5B6B4 /* Extensions */,
58D01C8F293167DC00C5B6B4 /* KeyChain */,
Expand Down Expand Up @@ -3201,6 +3218,7 @@
B65B11012B09D5D4002852CF /* GitClient+Pull.swift in Sources */,
2072FA13280D74ED00C7F8D4 /* HistoryInspectorModel.swift in Sources */,
852E62012A5C17E500447138 /* PageAndSettings.swift in Sources */,
3000516C2BBD3A9500A98562 /* ServiceWrapper.swift in Sources */,
587B9DA029300ABD00AC7927 /* PanelDivider.swift in Sources */,
58822534292C280D00E83CDE /* CursorLocation.swift in Sources */,
201169E52837B40300F92B46 /* SourceControlNavigatorRepositoryView.swift in Sources */,
Expand Down Expand Up @@ -3416,6 +3434,7 @@
587B9E6329301D8F00AC7927 /* GitLabAccount.swift in Sources */,
6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */,
285FEC7027FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift in Sources */,
300051672BBD3A5D00A98562 /* ServiceContainer.swift in Sources */,
6CB9144B29BEC7F100BC47F2 /* (null) in Sources */,
587B9E7429301D8F00AC7927 /* URL+URLParameters.swift in Sources */,
61538B902B111FE800A88846 /* String+AppearancesOfSubstring.swift in Sources */,
Expand Down Expand Up @@ -3520,6 +3539,7 @@
58D01C97293167DC00C5B6B4 /* String+SHA256.swift in Sources */,
B6EA1FFD29DB792C001BF195 /* ThemeSettingsColorPreview.swift in Sources */,
2806E904297958B9000040F4 /* ContributorRowView.swift in Sources */,
3000516A2BBD3A8200A98562 /* ServiceType.swift in Sources */,
6C578D8C29CD372700DC73B2 /* ExtensionCommands.swift in Sources */,
B6041F5229D7D6D6000F3454 /* SettingsWindow.swift in Sources */,
B6EA1FF829DB78DB001BF195 /* ThemeSettingThemeRow.swift in Sources */,
Expand Down
9 changes: 9 additions & 0 deletions CodeEdit/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
private var openWindow

func applicationDidFinishLaunching(_ notification: Notification) {
setupServiceContainer()
enableWindowSizeSaveOnQuit()
Settings.shared.preferences.general.appAppearance.applyAppearance()
checkForFilesToOpen()
Expand Down Expand Up @@ -231,6 +232,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
}
}

/// Setup all the services into a ServiceContainer for the application to use.
private func setupServiceContainer() {
// Example for how services will be instantiated
// ServiceContainer.register(
// PasteboardService()
// )
}

extension AppDelegate {
static let recoverWorkspacesKey = "recover.workspaces"
}
65 changes: 65 additions & 0 deletions CodeEdit/Utils/DependencyInjection/ServiceContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// ServiceContainer.swift
// CodeEdit
//
// Created by Abe Malla on 4/3/24.
//

import Foundation

/// A service container that manages the registration and resolution of services.
enum ServiceContainer {
/// A dictionary storing the closures for creating service instances.
private static var factories: [ObjectIdentifier: () -> Any] = [:]
/// A dictionary storing the cached service instances.
private static var cache: [ObjectIdentifier: Any] = [:]
/// A dispatch queue used for synchronizing access to the factories and cache.
private static let queue = DispatchQueue(label: "ServiceContainerQueue")

/// Registers a factory closure for creating instances of a service type.
///
/// - Parameter factory: An autoclosure that returns an instance of the service type.
static func register<Service>(_ factory: @autoclosure @escaping () -> Service) {
queue.sync {
let key = ObjectIdentifier(Service.Type.self)
factories[key] = factory
}
}

/// Resolves an instance of a service type based on the specified resolution type.
///
/// - Parameters:
/// - resolveType: The type of resolution to use for the service. Defaults to `.singleton`.
/// - type: The type of the service to resolve.
/// - Returns: An instance of the resolved service type, or `nil` if the service is not registered.
static func resolve<Service>(_ resolveType: ServiceType = .singleton, _ type: Service.Type) -> Service? {
let serviceId = ObjectIdentifier(Service.Type.self)

return queue.sync {
switch resolveType {
case .singleton:
if let service = cache[serviceId] as? Service {
return service
} else {
let service = factories[serviceId]?() as? Service

if let service = service {
cache[serviceId] = service
}

return service
}
case .newSingleton:
let service = factories[serviceId]?() as? Service

if let service = service {
cache[serviceId] = service
}

return service
case .new:
return factories[serviceId]?() as? Service
}
}
}
}
15 changes: 15 additions & 0 deletions CodeEdit/Utils/DependencyInjection/ServiceType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// ServiceType.swift
// CodeEdit
//
// Created by Abe Malla on 4/3/24.
//

enum ServiceType {
FastestMolasses marked this conversation as resolved.
Show resolved Hide resolved
/// Returns a new singleton on the first call, then returns a cached one every other time
case singleton
/// Creates a new singleton reference each time and caches it, returning the newer singleton
case newSingleton
/// Creates a new singleton
case new
}
26 changes: 26 additions & 0 deletions CodeEdit/Utils/DependencyInjection/ServiceWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// ServiceWrapper.swift
// CodeEdit
//
// Created by Abe Malla on 4/3/24.
//

/// A property wrapper that provides access to a service instance.
@propertyWrapper
struct Service<Service> {
var service: Service

init(_ type: ServiceType = .singleton) {
guard let service = ServiceContainer.resolve(type, Service.self) else {
let serviceName = String(describing: Service.self)
fatalError("No service of type \(serviceName) registered!")
}

self.service = service
}

var wrappedValue: Service {
get { self.service }
mutating set { service = newValue }
}
}
Loading