From 1d65c29a69122cab8b7465d91beaac814dea137a Mon Sep 17 00:00:00 2001 From: Marcin Iwanicki Date: Sat, 3 Aug 2024 20:44:52 +0100 Subject: [PATCH] Initial version --- .gitignore | 60 +----- .mise.toml | 8 + .mise/helpers/common | 6 + .mise/tasks/autocorrect | 7 + .mise/tasks/build | 7 + .mise/tasks/clean | 7 + .mise/tasks/env | 9 + .mise/tasks/format | 5 + .mise/tasks/lint | 8 + .mise/tasks/test | 7 + .swiftformat | 10 + .swiftlint.yml | 16 ++ .../contents.xcworkspacedata | 7 + Makefile | 48 +++++ Package.swift | 27 +++ Sources/SCInject/Assembler.swift | 37 ++++ Sources/SCInject/Assembly.swift | 21 ++ Sources/SCInject/Container.swift | 183 ++++++++++++++++++ Sources/SCInject/Identifier.swift | 29 +++ Sources/SCInject/Registry.swift | 28 +++ Sources/SCInject/Resolver.swift | 23 +++ Sources/SCInject/Scope.swift | 20 ++ Tests/SCInject/ContainerTests.swift | 88 +++++++++ Tests/SCInject/TestUtils/Stubs.swift | 33 ++++ 24 files changed, 637 insertions(+), 57 deletions(-) create mode 100644 .mise.toml create mode 100644 .mise/helpers/common create mode 100755 .mise/tasks/autocorrect create mode 100755 .mise/tasks/build create mode 100755 .mise/tasks/clean create mode 100755 .mise/tasks/env create mode 100755 .mise/tasks/format create mode 100755 .mise/tasks/lint create mode 100755 .mise/tasks/test create mode 100644 .swiftformat create mode 100644 .swiftlint.yml create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 Makefile create mode 100644 Package.swift create mode 100644 Sources/SCInject/Assembler.swift create mode 100644 Sources/SCInject/Assembly.swift create mode 100644 Sources/SCInject/Container.swift create mode 100644 Sources/SCInject/Identifier.swift create mode 100644 Sources/SCInject/Registry.swift create mode 100644 Sources/SCInject/Resolver.swift create mode 100644 Sources/SCInject/Scope.swift create mode 100644 Tests/SCInject/ContainerTests.swift create mode 100644 Tests/SCInject/TestUtils/Stubs.swift diff --git a/.gitignore b/.gitignore index 52fe2f7..ffab9a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,62 +1,8 @@ -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore +## MacOS +.DS_Store ## User settings xcuserdata/ -## Obj-C/Swift specific -*.hmap - -## App packaging -*.ipa -*.dSYM.zip -*.dSYM - -## Playgrounds -timeline.xctimeline -playground.xcworkspace - -# Swift Package Manager -# -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -# Package.pins -# Package.resolved -# *.xcodeproj -# -# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata -# hence it is not needed unless you have added a package configuration file to your project -# .swiftpm - +## SPM .build/ - -# CocoaPods -# -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# -# Pods/ -# -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace - -# Carthage -# -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build/ - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots/**/*.png -fastlane/test_output diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 0000000..a64b7a1 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,8 @@ +[tools] +swiftlint = "0.54.0" +swiftformat = "0.53.3" + +[settings] +jobs = 6 +http_timeout = 30 +experimental = true diff --git a/.mise/helpers/common b/.mise/helpers/common new file mode 100644 index 0000000..799e36e --- /dev/null +++ b/.mise/helpers/common @@ -0,0 +1,6 @@ +#!/bin/bash + +set -euo pipefail + +# Predefined variables +readonly EXT_BUILD_DIR="$MISE_PROJECT_ROOT/.build" diff --git a/.mise/tasks/autocorrect b/.mise/tasks/autocorrect new file mode 100755 index 0000000..373d7ac --- /dev/null +++ b/.mise/tasks/autocorrect @@ -0,0 +1,7 @@ +#!/bin/bash +# mise description="Clean build directory" +set -euo pipefail + +source "$MISE_PROJECT_ROOT/.mise/helpers/common" + +swiftlint autocorrect --quiet diff --git a/.mise/tasks/build b/.mise/tasks/build new file mode 100755 index 0000000..b8ac9c7 --- /dev/null +++ b/.mise/tasks/build @@ -0,0 +1,7 @@ +#!/bin/bash +# mise description="Build the project" +set -euo pipefail + +source "$MISE_PROJECT_ROOT/.mise/helpers/common" + +xcrun swift build diff --git a/.mise/tasks/clean b/.mise/tasks/clean new file mode 100755 index 0000000..88324f6 --- /dev/null +++ b/.mise/tasks/clean @@ -0,0 +1,7 @@ +#!/bin/bash +# mise description="Clean build directory" +set -euo pipefail + +source "$MISE_PROJECT_ROOT/.mise/helpers/common" + +rm -rf "$EXT_BUILD_DIR" diff --git a/.mise/tasks/env b/.mise/tasks/env new file mode 100755 index 0000000..6673a62 --- /dev/null +++ b/.mise/tasks/env @@ -0,0 +1,9 @@ +#!/bin/bash +# mise description="Show environment" +set -euo pipefail + +source "$MISE_PROJECT_ROOT/.mise/helpers/common" + +xcrun sw_vers +xcrun xcode-select -p +xcrun xcodebuild -version diff --git a/.mise/tasks/format b/.mise/tasks/format new file mode 100755 index 0000000..9e0baca --- /dev/null +++ b/.mise/tasks/format @@ -0,0 +1,5 @@ +#!/bin/bash +# mise description="Format the project" +set -euo pipefail + +swiftformat $MISE_PROJECT_ROOT diff --git a/.mise/tasks/lint b/.mise/tasks/lint new file mode 100755 index 0000000..a7464b4 --- /dev/null +++ b/.mise/tasks/lint @@ -0,0 +1,8 @@ +#!/bin/bash +# mise description="Lint the project" +set -euo pipefail + +source "$MISE_PROJECT_ROOT/.mise/helpers/common" + +swiftlint --strict --quiet +swiftformat . --lint diff --git a/.mise/tasks/test b/.mise/tasks/test new file mode 100755 index 0000000..121e79c --- /dev/null +++ b/.mise/tasks/test @@ -0,0 +1,7 @@ +#!/bin/bash +# mise description="Test the project" +set -euo pipefail + +source "$MISE_PROJECT_ROOT/.mise/helpers/common" + +xcrun swift test diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..1c39ff6 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,10 @@ +--swiftversion 5.10 +--exclude .build +--maxwidth 120 +--allman false +--disable wrapMultilineStatementBraces +--funcattributes prev-line +--typeattributes prev-line +--varattributes prev-line +--wraparguments before-first +--wrapparameters before-first diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..0b76b32 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,16 @@ +disabled_rules: + - trailing_comma + - opening_brace + - force_try + +included: + - Sources/ + - Tests/ + +identifier_name: + excluded: + - r + +type_name: + excluded: + - T diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..23f8687 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +mise := ~/.local/bin/mise + +define HELP_BODY +USAGE: make + +SUBCOMMANDS: + help Show help. + setup Set up development environment. + clean Clean build folder. + env Show build environment. + build Build. + test Run tests. + format Format source code. + autocorrect Autocorrect lint issues if possible. + lint Lint source code. + +endef +export HELP_BODY + +help: + @echo "$$HELP_BODY" + +setup: + curl "https://mise.run" | sh + +clean: + @$(mise) run clean + +env: + @$(mise) run env + +build: env + @$(mise) run build + +test: + @$(mise) run test + +format: + @$(mise) install + @$(mise) run format + +autocorrect: + @$(mise) install + @$(mise) run autocorrect + +lint: + @$(mise) install + @$(mise) run lint diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..85eed67 --- /dev/null +++ b/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.10 + +import PackageDescription + +let package = Package( + name: "SwiftCommons", + platforms: [ + .macOS(.v10_13), + .iOS(.v15), + ], + products: [ + .library( + name: "SCInject", + targets: ["SCInject"] + ), + ], + targets: [ + .target( + name: "SCInject", + dependencies: [] + ), + .testTarget( + name: "SCInjectTests", + dependencies: ["SCInject"] + ), + ] +) diff --git a/Sources/SCInject/Assembler.swift b/Sources/SCInject/Assembler.swift new file mode 100644 index 0000000..94140e4 --- /dev/null +++ b/Sources/SCInject/Assembler.swift @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public final class Assembler { + private let container: Container + + public init(container: Container) { + self.container = container + } + + @discardableResult + public func assemble(_ assemblies: [Assembly]) -> Assembler { + for assembly in assemblies { + assembly.assemble(container) + } + return self + } + + public func resolver() -> Resolver { + container + } +} diff --git a/Sources/SCInject/Assembly.swift b/Sources/SCInject/Assembly.swift new file mode 100644 index 0000000..b5150d7 --- /dev/null +++ b/Sources/SCInject/Assembly.swift @@ -0,0 +1,21 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public protocol Assembly { + func assemble(_ registry: Registry) +} diff --git a/Sources/SCInject/Container.swift b/Sources/SCInject/Container.swift new file mode 100644 index 0000000..d69ba28 --- /dev/null +++ b/Sources/SCInject/Container.swift @@ -0,0 +1,183 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public protocol Container: Registry, Resolver {} + +public final class DefaultContainer: Container { + private let parent: DefaultContainer? + private let lock = NSRecursiveLock() + private let defaultScope = Scope.transient + private var resolvers: [ResolverIdentifier: ReferenceResolver] = [:] + + public init(parent: DefaultContainer? = nil) { + self.parent = parent + } + + // MARK: - Registry + + public func register(_ type: T.Type, closure: @escaping (Resolver) -> T) { + register(type: type, id: nil, scope: nil, closure: closure) + } + + public func register(_ type: T.Type, _ scope: Scope, closure: @escaping (Resolver) -> T) { + register(type: type, id: nil, scope: scope, closure: closure) + } + + public func register(_ type: T.Type, id: String, closure: @escaping (Resolver) -> T) { + register(type: type, id: .init(rawValue: id), scope: nil, closure: closure) + } + + public func register(_ type: T.Type, id: String, _ scope: Scope, closure: @escaping (Resolver) -> T) { + register(type: type, id: .init(rawValue: id), scope: scope, closure: closure) + } + + public func register(_ type: T.Type, id: Identifier, closure: @escaping (Resolver) -> T) { + register(type: type, id: id, scope: nil, closure: closure) + } + + public func register(_ type: T.Type, id: Identifier, _ scope: Scope, closure: @escaping (Resolver) -> T) { + register(type: type, id: id, scope: scope, closure: closure) + } + + // MARK: - Resolver + + public func resolve(_ type: T.Type) -> T { + guard let instance = tryResolve(type) else { + let message = errorMessage("Failed to resolve given type -- TYPE=\(type)") + fatalError(message) + } + return instance + } + + public func resolve(_ type: T.Type, id: String) -> T { + resolve(type, id: .init(rawValue: id)) + } + + public func resolve(_ type: T.Type, id: Identifier) -> T { + guard let instance = tryResolve(type, id: id) else { + let message = errorMessage("Failed to resolve given type -- TYPE=\(type) ID=\(id.rawValue)") + fatalError(message) + } + return instance + } + + // MARK: - Public + + public func tryResolve(_ type: T.Type) -> T? { + tryResolve(type: type, id: nil, container: self) + } + + public func tryResolve(_ type: T.Type, id: String) -> T? { + tryResolve(type: type, id: .init(rawValue: id), container: self) + } + + public func tryResolve(_ type: T.Type, id: Identifier) -> T? { + tryResolve(type: type, id: id, container: self) + } + + // MARK: - Private + + private func register( + type: T.Type, + id: Identifier?, + scope: Scope?, + closure: @escaping (Resolver) -> T + ) { + lock.lock(); defer { lock.unlock() } + let identifier = identifier(of: type, id: id) + if resolvers[identifier] != nil { + let message = + errorMessage("Given type is already registered -- TYPE=\(type) ID=\(id?.rawValue ?? "nil")") + fatalError(message) + } + resolvers[identifier] = makeResolver(scope ?? defaultScope, closure: closure) + } + + private func tryResolve(type: T.Type, id: Identifier? = nil, container: Container) -> T? { + lock.lock(); defer { lock.unlock() } + if let resolver = resolvers[identifier(of: type, id: id)] { + return resolver.resolve(with: container) as? T + } + if let parent { + return parent.tryResolve(type: type, id: id, container: container) + } + return nil + } + + private func makeResolver(_ scope: Scope, closure: @escaping (Resolver) -> some Any) -> ReferenceResolver { + switch scope { + case .transient: + TransientReferenceResolver(factory: closure) + case .container: + ContainerReferenceResolver(factory: closure) + } + } + + private func identifier(of type: (some Any).Type, id: Identifier?) -> ResolverIdentifier { + ResolverIdentifier( + id: id, + typeIdentifier: ObjectIdentifier(type), + description: String(describing: type) + ) + } + + private struct ResolverIdentifier: Hashable { + let id: Identifier? + let typeIdentifier: ObjectIdentifier + let description: String + } + + private func errorMessage(_ message: String) -> String { + "SCInjectError - \(message)" + } +} + +private protocol ReferenceResolver { + func resolve(with resolver: Resolver) -> Any +} + +private final class TransientReferenceResolver: ReferenceResolver { + private let factory: (Resolver) -> Any + + init(factory: @escaping (Resolver) -> Any) { + self.factory = factory + } + + func resolve(with resolver: Resolver) -> Any { + factory(resolver) + } +} + +private final class ContainerReferenceResolver: ReferenceResolver { + private var instance: Any? + + private let factory: (Resolver) -> Any + + init(factory: @escaping (Resolver) -> Any) { + self.factory = factory + } + + func resolve(with resolver: Resolver) -> Any { + if let instance { + return instance + } + let newInstance = factory(resolver) + instance = newInstance + return newInstance + } +} diff --git a/Sources/SCInject/Identifier.swift b/Sources/SCInject/Identifier.swift new file mode 100644 index 0000000..b0d9901 --- /dev/null +++ b/Sources/SCInject/Identifier.swift @@ -0,0 +1,29 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public struct Identifier: RawRepresentable, CustomStringConvertible, Hashable { + public let rawValue: String + + public init(rawValue string: String) { + rawValue = string + } + + public var description: String { + "SCInject.Identifier(\(rawValue))" + } +} diff --git a/Sources/SCInject/Registry.swift b/Sources/SCInject/Registry.swift new file mode 100644 index 0000000..a0dec6f --- /dev/null +++ b/Sources/SCInject/Registry.swift @@ -0,0 +1,28 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public protocol Registry { + func register(_ type: T.Type, closure: @escaping (Resolver) -> T) + func register(_ type: T.Type, _ scope: Scope, closure: @escaping (Resolver) -> T) + + func register(_ type: T.Type, id: String, closure: @escaping (Resolver) -> T) + func register(_ type: T.Type, id: String, _ scope: Scope, closure: @escaping (Resolver) -> T) + + func register(_ type: T.Type, id: Identifier, closure: @escaping (Resolver) -> T) + func register(_ type: T.Type, id: Identifier, _ scope: Scope, closure: @escaping (Resolver) -> T) +} diff --git a/Sources/SCInject/Resolver.swift b/Sources/SCInject/Resolver.swift new file mode 100644 index 0000000..580348e --- /dev/null +++ b/Sources/SCInject/Resolver.swift @@ -0,0 +1,23 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public protocol Resolver: AnyObject { + func resolve(_ type: T.Type) -> T + func resolve(_ type: T.Type, id: String) -> T + func resolve(_ type: T.Type, id: Identifier) -> T +} diff --git a/Sources/SCInject/Scope.swift b/Sources/SCInject/Scope.swift new file mode 100644 index 0000000..ad33092 --- /dev/null +++ b/Sources/SCInject/Scope.swift @@ -0,0 +1,20 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public enum Scope { + case transient + case container +} diff --git a/Tests/SCInject/ContainerTests.swift b/Tests/SCInject/ContainerTests.swift new file mode 100644 index 0000000..de4f0f8 --- /dev/null +++ b/Tests/SCInject/ContainerTests.swift @@ -0,0 +1,88 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation +@testable import SCInject +import XCTest + +// swiftlint:disable identifier_name +final class ContainerTests: XCTestCase { + func testRegister_transientClass() { + // Given + let container = DefaultContainer() + container.register(TestClass1.self) { _ in + TestClass1(value: "TestClass1_Instance") + } + container.register(TestClass2.self) { r in + TestClass2(value: r.resolve(TestClass1.self)) + } + + // When + let class1 = container.tryResolve(TestClass1.self) + let class2 = container.tryResolve(TestClass2.self) + let class1_id = container.tryResolve(TestClass1.self, id: "Test") + let class2_id = container.tryResolve(TestClass2.self, id: "Test") + let class1_id_second = container.tryResolve(TestClass1.self, id: .init(rawValue: "Test")) + let class2_id_second = container.tryResolve(TestClass2.self, id: .init(rawValue: "Test")) + let class1_second = container.tryResolve(TestClass1.self) + let class2_second = container.tryResolve(TestClass2.self) + + // Then + XCTAssertNotNil(class1) + XCTAssertNotNil(class2) + XCTAssertNotNil(class1_second) + XCTAssertNotNil(class2_second) + XCTAssertNil(class1_id) + XCTAssertNil(class2_id) + XCTAssertNil(class1_id_second) + XCTAssertNil(class2_id_second) + XCTAssertTrue(class1 !== class1_second) + XCTAssertTrue(class2 !== class2_second) + XCTAssertTrue(class2?.value !== class1) + } + + func testRegister_transientClassWithId() { + let second: Identifier = .init(rawValue: "second") + let container = DefaultContainer() + container.register(TestClass1.self) { _ in + TestClass1(value: "TestClass1_Instance") + } + container.register(TestClass1.self, id: second) { _ in + TestClass1(value: "TestClass1_Second_Instance") + } + container.register(TestClass2.self) { r in + TestClass2(value: r.resolve(TestClass1.self, id: second)) + } + + // When + let class1 = container.tryResolve(TestClass1.self) + let class2 = container.tryResolve(TestClass2.self) + let class1_id = container.tryResolve(TestClass1.self, id: second) + let class2_id = container.tryResolve(TestClass2.self, id: second) + + // Then + XCTAssertNotNil(class1) + XCTAssertNotNil(class2) + XCTAssertNotNil(class1_id) + XCTAssertNil(class2_id) + XCTAssertEqual(class1_id?.rawValue, "TestClass1_Second_Instance") + XCTAssertEqual(class2?.value.rawValue, "TestClass1_Second_Instance") + XCTAssertTrue(class1 !== class1_id) + XCTAssertTrue(class2?.value !== class1_id) + } +} + +// swiftlint:enable identifier_name diff --git a/Tests/SCInject/TestUtils/Stubs.swift b/Tests/SCInject/TestUtils/Stubs.swift new file mode 100644 index 0000000..177ff20 --- /dev/null +++ b/Tests/SCInject/TestUtils/Stubs.swift @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Marcin Iwanicki and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +class TestClass1 { + let rawValue: String + + init(value: String) { + rawValue = value + } +} + +class TestClass2 { + let value: TestClass1 + + init(value: TestClass1) { + self.value = value + } +}