diff --git a/EasyDi.xcodeproj/project.pbxproj b/EasyDi.xcodeproj/project.pbxproj index 41083f5..509f0e9 100644 --- a/EasyDi.xcodeproj/project.pbxproj +++ b/EasyDi.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 5C88753F236FD22500019260 /* Test_CrossAssemblyInjections_WeakSingletonCycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C88753E236FD22500019260 /* Test_CrossAssemblyInjections_WeakSingletonCycle.swift */; }; + 5C887540236FD22500019260 /* Test_CrossAssemblyInjections_WeakSingletonCycle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C88753E236FD22500019260 /* Test_CrossAssemblyInjections_WeakSingletonCycle.swift */; }; 8BEE13521F9A27C800A02331 /* EasyDi.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BEE13501F9A27C800A02331 /* EasyDi.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8BEE13561F9A27EA00A02331 /* EasyDi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEE13551F9A27EA00A02331 /* EasyDi.swift */; }; 8BEE13571F9A27F200A02331 /* EasyDi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BEE13551F9A27EA00A02331 /* EasyDi.swift */; }; @@ -52,6 +54,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 5C88753E236FD22500019260 /* Test_CrossAssemblyInjections_WeakSingletonCycle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Test_CrossAssemblyInjections_WeakSingletonCycle.swift; sourceTree = ""; }; 8BEE13501F9A27C800A02331 /* EasyDi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EasyDi.h; sourceTree = ""; }; 8BEE13511F9A27C800A02331 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8BEE13551F9A27EA00A02331 /* EasyDi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasyDi.swift; sourceTree = ""; }; @@ -133,6 +136,7 @@ C3614B451F1C8B5F00B1F4A1 /* Test_Context.swift */, C3614B461F1C8B5F00B1F4A1 /* Test_CrossAssemblyInjections.swift */, E6DCF5C41F2F62A000D9F8BC /* Test_CrossAssemblyInjections_SingletonCycle.swift */, + 5C88753E236FD22500019260 /* Test_CrossAssemblyInjections_WeakSingletonCycle.swift */, C3614B471F1C8B5F00B1F4A1 /* Test_Injections.swift */, C3614B481F1C8B5F00B1F4A1 /* Test_Patches.swift */, C3614B491F1C8B5F00B1F4A1 /* Test_ProtocolBasedInjection.swift */, @@ -373,6 +377,7 @@ A5ABB84321A5522400C96320 /* Test_Threadsafety.swift in Sources */, E6DCF5C61F2F62A600D9F8BC /* Test_CrossAssemblyInjections_SingletonCycle.swift in Sources */, C3614B581F1C8B6800B1F4A1 /* Test_ProtocolBasedInjection.swift in Sources */, + 5C88753F236FD22500019260 /* Test_CrossAssemblyInjections_WeakSingletonCycle.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -403,6 +408,7 @@ C37DEA321F1E9B2100279AD3 /* Test_Injections.swift in Sources */, C37DEA301F1E9B2100279AD3 /* Test_Context.swift in Sources */, C37DEA341F1E9B2100279AD3 /* Test_ProtocolBasedInjection.swift in Sources */, + 5C887540236FD22500019260 /* Test_CrossAssemblyInjections_WeakSingletonCycle.swift in Sources */, E6DCF5C71F2F62A700D9F8BC /* Test_CrossAssemblyInjections_SingletonCycle.swift in Sources */, C37DEA351F1E9B2100279AD3 /* Test_Scope.swift in Sources */, ); diff --git a/Sources/EasyDi.swift b/Sources/EasyDi.swift index 5f3ae0c..03dc89d 100644 --- a/Sources/EasyDi.swift +++ b/Sources/EasyDi.swift @@ -20,6 +20,10 @@ public struct DIContextEmptyLocker { extension NSRecursiveLock: DIContextLocker { } +struct WeakSingletonWrapper { + weak var instance: AnyObject? +} + /// This class is used to join assembly instances into separated shared group. /// /// All assemblies with one context shares object graph stack. @@ -36,10 +40,10 @@ extension NSRecursiveLock: DIContextLocker { /// ``` /// public final class DIContext { - + public static var defaultInstance = DIContext() fileprivate var assemblies: [String: Assembly] = [:] - + var objectGraphStorage: [String: InjectableObject] = [:] var objectGraphStackDepth: Int = 0 let locker: DIContextLocker @@ -48,16 +52,17 @@ public final class DIContext { /// /// Dictionary key is **key** parameter from **define** method var singletons: [String: InjectableObject] = [:] + var weakSingletons: [String: WeakSingletonWrapper] = [:] /// Array of applyed substitutions /// /// Dictionary key is **key** parameter from **define** method var substitutions: [String: UntypedPatchClosure] = [:] - + public init(locker: DIContextLocker = NSRecursiveLock()) { self.locker = locker } - + /// This method creates assembly instance based on it's return type. /// /// - returns: Assembly instance @@ -69,7 +74,7 @@ public final class DIContext { let instance = self.instance(for: AssemblyType.self) return castAssemblyInstance(instance, asType: AssemblyType.self) } - + /// This method creates assembly instance by type /// /// - parameter assemblyType: Class of the assembly @@ -81,7 +86,7 @@ public final class DIContext { /// ``` public func instance(for assemblyType: Assembly.Type) -> Assembly { locker.lock(); defer { locker.unlock() } - + let assemblyClassName = String(reflecting: assemblyType) if let existingInstance = self.assemblies[assemblyClassName] { return existingInstance @@ -132,6 +137,9 @@ public enum Scope { /// /// [Singleton] description contains short example with memory graph illustration case lazySingleton + + /// [WeakSingleton] description contains short example with memory graph illustration + case weakSingleton } @@ -154,9 +162,9 @@ public enum Scope { /// /// ``` open class Assembly: AssemblyInternal { - + public internal(set) weak var context: DIContext! - + /// This method creates assembly for specified context or default context if no parameters provided /// /// - parameter context: DIContext object which assembly should belong to @@ -167,16 +175,16 @@ open class Assembly: AssemblyInternal { let instance = context.instance(for: self) return castAssemblyInstance(instance, asType: self) } - + /// Helper internal method to create assembly internal static func newInstance() -> Self { return self.init() } - + /// Initialiser public required init() {} - + /// This method forces assembly to return result of closure instead of the assembly's dependency. /// /// It's usefull to stub objects and make A / B testing @@ -187,21 +195,21 @@ open class Assembly: AssemblyInternal { public func addSubstitution( for simpleDefinitionKey: String, with substitutionClosure: @escaping SubstitutionClosure) { - + let definitionKey = String(reflecting: self).replacingOccurrences(of: ".", with: "") + simpleDefinitionKey context.substitutions[definitionKey] = substitutionClosure } - + /// This method removes substitution from assembly /// /// - parameter definitionKey: should exactly match method or property name of substituting dependency /// public func removeSubstitution(for simpleDefinitionKey: String) { - + let definitionKey = String(reflecting: self).replacingOccurrences(of: ".", with: "") + simpleDefinitionKey context.substitutions[definitionKey] = nil } - + /// The method defines return-only placeholder for object. /// /// Use this method to inject something, created or injected with runtime parameters. @@ -319,13 +327,13 @@ open class Assembly: AssemblyInternal { scope: Scope = .objectGraph, init initClosure: @autoclosure @escaping () -> ObjectType, inject injectClosure: ObjectInjectClosure? = nil ) -> ResultType { - + return define(key: key, definitionKey: definitionKey, scope: scope) { (definition:Definition) in definition.initClosure = initClosure definition.injectClosure = injectClosure } } - + /// Internal method where main injection logic is performed. /// /// - parameter key: name of the method or property. Default value should be used in most cases @@ -345,19 +353,19 @@ open class Assembly: AssemblyInternal { definitionKey simpleDefinitionKey: String = #function, scope: Scope = .objectGraph, definitionClosure: DefinitionClosure? = nil) -> ResultType { - + guard let context = self.context else { fatalError("Associated context doesn't exists anymore") } - + context.locker.lock(); defer { context.locker.unlock() } - + // Objects are stored in context by key made of Assembly class name and name of var or method let key: String = String(reflecting: self).replacingOccurrences(of: ".", with: "") + simpleKey let definitionKey: String = String(reflecting: self).replacingOccurrences(of: ".", with: "") + simpleDefinitionKey var result: ObjectType - + // First of all it checks if there's substitution for this var or method if let substitutionClosure = context.substitutions[definitionKey] { @@ -366,23 +374,26 @@ open class Assembly: AssemblyInternal { fatalError("Expected type: \(ResultType.self), received: \(type(of: substitutionObject))") } return object - - // Next check for existing singletons + + // Next check for existing singletons } else if scope == .lazySingleton, let singleton = context.singletons[key] { - + result = singleton as! ObjectType - // And trying to return object from graph + // And trying to return object from graph + } else if scope == .weakSingleton, let wrapper = context.weakSingletons[key], let weakSingletion = wrapper.instance { + + result = weakSingletion as! ObjectType + } else if let objectFromStack = context.objectGraphStorage[key], scope != .prototype, let unwrappedObject = objectFromStack as? ObjectType { result = unwrappedObject } else { - // Create Definition object to store injections and dependencies information let definition = Definition() definitionClosure?(definition) - + context.objectGraphStackDepth += 1 guard var object = definition.initObject() else { fatalError("Failed to initialize object") @@ -396,7 +407,7 @@ open class Assembly: AssemblyInternal { context.objectGraphStackDepth += 1 object = definition.injectObject(object: object) context.objectGraphStackDepth -= 1 - + result = object as! ObjectType } @@ -404,12 +415,21 @@ open class Assembly: AssemblyInternal { if context.objectGraphStackDepth == 0 { context.objectGraphStorage.removeAll() } - + // And save singletons if context.singletons[key] == nil, scope == .lazySingleton { context.singletons[key] = result } + if context.weakSingletons[key] == nil, scope == .weakSingleton { + context.weakSingletons[key] = WeakSingletonWrapper(instance: result as AnyObject) + } + + if var wrapper = context.weakSingletons[key], scope == .weakSingleton, wrapper.instance == nil { + wrapper.instance = result as AnyObject + context.weakSingletons[key] = wrapper + } + guard let finalResult = result as? ResultType else { fatalError("Failed to build result object. Expected \(ResultType.self) received: \(result)") } @@ -420,32 +440,32 @@ open class Assembly: AssemblyInternal { /// This is type-erasing protocol used to store definition generics and access them internal protocol DefinitionInternal { - + func initObject() -> InjectableObject? func injectObject(object: InjectableObject) -> InjectableObject } /// Definition object is used to store initialization and injection closures public final class Definition: DefinitionInternal { - + public var initClosure: ObjectInitClosure? public var injectClosure: ObjectInjectClosure? - + func initObject() -> InjectableObject? { - + return self.initClosure?() } - + func injectObject(object: InjectableObject) -> InjectableObject { - + guard let injectableObject = object as? ObjectType else { fatalError() } - + guard let actualInjectClosure = self.injectClosure else { return object } - + return actualInjectClosure(injectableObject) } } diff --git a/Tests/Test_Context.swift b/Tests/Test_Context.swift index 8759d08..96c08ce 100644 --- a/Tests/Test_Context.swift +++ b/Tests/Test_Context.swift @@ -10,11 +10,8 @@ import XCTest import EasyDi class Test_Context: XCTestCase { - func testDefaultContext() { - - class TestAssembly: Assembly { - } + class TestAssembly: Assembly { } let assemblyInstance1 = TestAssembly.instance() let assemblyInstance2 = TestAssembly.instance() @@ -22,11 +19,8 @@ class Test_Context: XCTestCase { XCTAssertTrue(assemblyInstance1 === assemblyInstance2) } - func testSameContext() { - - class TestAssembly: Assembly { - } + class TestAssembly: Assembly { } let context: DIContext = DIContext() let assemblyInstance1 = TestAssembly.instance(from: context) @@ -36,9 +30,7 @@ class Test_Context: XCTestCase { } func testDifferentContexts() { - - class TestAssembly: Assembly { - } + class TestAssembly: Assembly { } let assemblyInstance1 = TestAssembly.instance() @@ -47,5 +39,4 @@ class Test_Context: XCTestCase { XCTAssertFalse(assemblyInstance1 === assemblyInstance2) } - } diff --git a/Tests/Test_CrossAssemblyInjections.swift b/Tests/Test_CrossAssemblyInjections.swift index 946c0fa..78226e3 100644 --- a/Tests/Test_CrossAssemblyInjections.swift +++ b/Tests/Test_CrossAssemblyInjections.swift @@ -10,24 +10,18 @@ import XCTest import EasyDi class Test_CrossAssemblyInjections: XCTestCase { - - class TestObject { - - var childObject:TestChildObject? = nil - var childObjects:[TestChildObject] = [] - + fileprivate class TestObject { + var childObject: TestChildObject? = nil + var childObjects: [TestChildObject] = [] } - class TestChildObject { - + fileprivate class TestChildObject { weak var parentObject: TestObject? = nil } - class TestObjectAssembly: Assembly { - + fileprivate class TestObjectAssembly: Assembly { + lazy var testChildObjectAssembly: TestChildObjectAssembly = self.context.assembly() - lazy var testChildObjectAssembly:TestChildObjectAssembly = self.context.assembly() - var testObject: TestObject { return define(init: TestObject()) { @@ -42,9 +36,8 @@ class Test_CrossAssemblyInjections: XCTestCase { } } - class TestChildObjectAssembly: Assembly { - - var testObjectAssembly:TestObjectAssembly { + fileprivate class TestChildObjectAssembly: Assembly { + var testObjectAssembly: TestObjectAssembly { return TestObjectAssembly.instance(from: self.context) } @@ -71,7 +64,6 @@ class Test_CrossAssemblyInjections: XCTestCase { } func testParentObjectAssignedAndValud() { - let context = DIContext() let testObject = TestObjectAssembly.instance(from: context).testObject XCTAssertNotNil(testObject.childObject?.parentObject) @@ -79,14 +71,12 @@ class Test_CrossAssemblyInjections: XCTestCase { } func testChildNotRecreated() { - let context = DIContext() let testObject = TestObjectAssembly.instance(from: context).testObject XCTAssert(testObject.childObject === testObject.childObjects.first) } func testParentIsSameForAllChildren() { - let context = DIContext() let testObject = TestObjectAssembly.instance(from: context).testObject for child in testObject.childObjects { diff --git a/Tests/Test_CrossAssemblyInjections_SingletonCycle.swift b/Tests/Test_CrossAssemblyInjections_SingletonCycle.swift index 6753ec7..da291ef 100644 --- a/Tests/Test_CrossAssemblyInjections_SingletonCycle.swift +++ b/Tests/Test_CrossAssemblyInjections_SingletonCycle.swift @@ -10,23 +10,17 @@ import XCTest import EasyDi class Test_CrossAssemblyInjections_SingletonCycle: XCTestCase { - - class TestObject { - - var childObject:TestChildObject? = nil - var childObjects:[TestChildObject] = [] + fileprivate class TestObject { + var childObject: TestChildObject? = nil + var childObjects: [TestChildObject] = [] } - class TestChildObject { - + fileprivate class TestChildObject { weak var parentObject: TestObject? = nil } - class TestObjectAssembly: Assembly { - - - lazy var testChildObjectAssembly:TestChildObjectAssembly = self.context.assembly() - + fileprivate class TestObjectAssembly: Assembly { + lazy var testChildObjectAssembly: TestChildObjectAssembly = self.context.assembly() var testObject: TestObject { return define(scope: .lazySingleton, init: TestObject()) { @@ -41,9 +35,8 @@ class Test_CrossAssemblyInjections_SingletonCycle: XCTestCase { } } - class TestChildObjectAssembly: Assembly { - - var testObjectAssembly:TestObjectAssembly { + fileprivate class TestChildObjectAssembly: Assembly { + var testObjectAssembly: TestObjectAssembly { return TestObjectAssembly.instance(from: self.context) } @@ -70,7 +63,6 @@ class Test_CrossAssemblyInjections_SingletonCycle: XCTestCase { } func testParentObjectAssignedAndValud() { - let context = DIContext() let testObject = TestObjectAssembly.instance(from: context).testObject XCTAssertNotNil(testObject.childObject?.parentObject) @@ -78,14 +70,12 @@ class Test_CrossAssemblyInjections_SingletonCycle: XCTestCase { } func testChildNotRecreated() { - let context = DIContext() let testObject = TestObjectAssembly.instance(from: context).testObject XCTAssert(testObject.childObject === testObject.childObjects.first) } func testParentIsSameForAllChildren() { - let context = DIContext() let testObject = TestObjectAssembly.instance(from: context).testObject for child in testObject.childObjects { diff --git a/Tests/Test_CrossAssemblyInjections_WeakSingletonCycle.swift b/Tests/Test_CrossAssemblyInjections_WeakSingletonCycle.swift new file mode 100644 index 0000000..267fa4c --- /dev/null +++ b/Tests/Test_CrossAssemblyInjections_WeakSingletonCycle.swift @@ -0,0 +1,85 @@ +// +// Test_CrossAssemblyInjections_WeakSingletonCycle.swift +// EasyDi +// +// Created by a.s.markov on 04.11.2019. +// Copyright © 2019 AndreyZarembo. All rights reserved. +// + +import Foundation +import XCTest +import EasyDi + +class Test_CrossAssemblyInjections_WeakSingletonCycle: XCTestCase { + fileprivate class TestObject { + var childObject: TestChildObject? = nil + var childObjects: [TestChildObject] = [] + } + + fileprivate class TestChildObject { + weak var parentObject: TestObject? = nil + } + + fileprivate class TestObjectAssembly: Assembly { + lazy var testChildObjectAssembly: TestChildObjectAssembly = self.context.assembly() + + var testObject: TestObject { + return define(scope: .weakSingleton, init: TestObject()) { + $0.childObject = self.testChildObjectAssembly.testChildObject + $0.childObjects = [ + self.testChildObjectAssembly.testChildObject, + self.testChildObjectAssembly.testChildObjectPrototype, + self.testChildObjectAssembly.testChildObjectPrototype + ] + return $0 + } + } + } + + fileprivate class TestChildObjectAssembly: Assembly { + var testObjectAssembly: TestObjectAssembly { + return TestObjectAssembly.instance(from: self.context) + } + + var testChildObject: TestChildObject { + return define(init: TestChildObject()) { + $0.parentObject = self.testObjectAssembly.testObject + return $0 + } + } + + var testChildObjectPrototype: TestChildObject { + return define(scope: .prototype, init: TestChildObject()) { + $0.parentObject = self.testObjectAssembly.testObject + return $0 + } + } + } + + func testChildObjectIsCreates() { + let context = DIContext() + let testObject = TestObjectAssembly.instance(from: context).testObject + XCTAssertNotNil(testObject.childObject) + } + + func testParentObjectAssignedAndValud() { + let context = DIContext() + let testObject = TestObjectAssembly.instance(from: context).testObject + XCTAssertNotNil(testObject.childObject?.parentObject) + XCTAssert(testObject === testObject.childObject?.parentObject) + } + + func testChildNotRecreated() { + let context = DIContext() + let testObject = TestObjectAssembly.instance(from: context).testObject + XCTAssert(testObject.childObject === testObject.childObjects.first) + } + + func testParentIsSameForAllChildren() { + let context = DIContext() + let testObject = TestObjectAssembly.instance(from: context).testObject + for child in testObject.childObjects { + XCTAssert(testObject === child.parentObject) + } + } +} diff --git a/Tests/Test_ImplicitlyUnwrappedOptional.swift b/Tests/Test_ImplicitlyUnwrappedOptional.swift index 9a844ee..5cfbf95 100644 --- a/Tests/Test_ImplicitlyUnwrappedOptional.swift +++ b/Tests/Test_ImplicitlyUnwrappedOptional.swift @@ -10,24 +10,20 @@ import Foundation import XCTest import EasyDi /* -As part of fully implementing (SE–0054), ImplicitlyUnwrappedOptional is now an unavailable typealias of Optional. Declarations annotated with ‘!’ have the type Optional. If an expression involving one of these values will not compile successfully with the type Optional, it is implicitly unwrapped, producing a value of type T. In some cases this will cause code that previously compiled to require updating. Please see this blog post for more information: (Reimplementation of Implicitly Unwrapped Optionals). (33272674) + As part of fully implementing (SE–0054), ImplicitlyUnwrappedOptional is now an unavailable typealias of Optional. Declarations annotated with ‘!’ have the type Optional. If an expression involving one of these values will not compile successfully with the type Optional, it is implicitly unwrapped, producing a value of type T. In some cases this will cause code that previously compiled to require updating. Please see this blog post for more information: (Reimplementation of Implicitly Unwrapped Optionals). (33272674) https://swift.org/blog/iuo/ -*/ + */ class Test_ImplicitlyUnwrappedOptional: XCTestCase { - - // Setup - - class ObjectGraphAssembly: Assembly { - + fileprivate class ObjectGraphAssembly: Assembly { var testObject: TestObject { return define(init: TestObject.object()) { return $0 } } } - - class TestObject { + + fileprivate class TestObject { class func object() -> TestObject! { return TestObject() } diff --git a/Tests/Test_Injections.swift b/Tests/Test_Injections.swift index c626690..e97094b 100644 --- a/Tests/Test_Injections.swift +++ b/Tests/Test_Injections.swift @@ -10,18 +10,14 @@ import XCTest import EasyDi class Test_Injection: XCTestCase { - - class TestObject { - + fileprivate class TestObject { var intParameter: Int = 0 var stringParamter: String = "" var arrayParameter: [String] = [] - weak var selfParameter: TestObject? = nil } func testInitWithInjection() { - class TestAssembly: Assembly { var testObject: TestObject { @@ -40,9 +36,8 @@ class Test_Injection: XCTestCase { XCTAssertEqual(testObject.stringParamter, "TestString") XCTAssertEqual(testObject.arrayParameter, ["a","b","c"]) } - + func testInjectionInExistingObject(){ - class TestAssembly: Assembly { func inject(into testObject: TestObject) { let _:TestObject = define(init: testObject) { @@ -54,7 +49,6 @@ class Test_Injection: XCTestCase { } } - // Test let testObject = TestObject() TestAssembly.instance().inject(into: testObject) XCTAssertEqual(testObject.intParameter, 10) @@ -63,9 +57,7 @@ class Test_Injection: XCTestCase { } func testReinjectionWithKey() { - class TestAssembly: Assembly { - func inject(into testObject: TestObject) { defineInjection(key: "testObject", into: testObject) { $0.intParameter = 10 @@ -81,13 +73,11 @@ class Test_Injection: XCTestCase { } } - // Test let testObject = TestObject() TestAssembly.instance().inject(into: testObject) XCTAssertEqual(testObject.intParameter, 10) XCTAssertEqual(testObject.stringParamter, "TestString") XCTAssertEqual(testObject.arrayParameter, ["a","b","c"]) XCTAssertTrue(testObject === testObject.selfParameter) - } } diff --git a/Tests/Test_Patches.swift b/Tests/Test_Patches.swift index cddc9a0..c7940bc 100644 --- a/Tests/Test_Patches.swift +++ b/Tests/Test_Patches.swift @@ -9,30 +9,27 @@ import Foundation import XCTest import EasyDi -protocol ITestSubstitutionObject { +fileprivate protocol ITestSubstitutionObject { var intParameter: Int { get } var testChild: ChildTestSubstitutionObject? { get } } -class ChildTestSubstitutionObject { +fileprivate class ChildTestSubstitutionObject { var parent: ITestSubstitutionObject? } class Test_Substitutions: XCTestCase { - - - - class TestObject: NSObject, ITestSubstitutionObject { + fileprivate class TestObject: NSObject, ITestSubstitutionObject { @objc var intParameter: Int = 0 var testChild: ChildTestSubstitutionObject? } - class TestObject2: NSObject, ITestSubstitutionObject { + + fileprivate class TestObject2: NSObject, ITestSubstitutionObject { @objc var intParameter: Int = 0 var testChild: ChildTestSubstitutionObject? } - class TestAssembly: Assembly { - + fileprivate class TestAssembly: Assembly { var testObject: ITestSubstitutionObject { return define(init: TestObject()) { $0.intParameter = 10 @@ -54,7 +51,6 @@ class Test_Substitutions: XCTestCase { } func testSubstitutionWithSimpleObject() { - let context = DIContext() let testAssembly = TestAssembly.instance(from: context) testAssembly.addSubstitution(for: "testObject") { ()->TestObject in @@ -74,7 +70,6 @@ class Test_Substitutions: XCTestCase { } func testSubstitutionWithDefinition() { - let context = DIContext() let testAssembly = TestAssembly.instance(from: context) testAssembly.addSubstitution(for: "testObject") { @@ -82,7 +77,7 @@ class Test_Substitutions: XCTestCase { testObj.intParameter = testAssembly.testInteger testObj.testChild = testAssembly.childTestObject return testObj - } as ITestSubstitutionObject + } as ITestSubstitutionObject } let SubstitutionedObject = testAssembly.testObject diff --git a/Tests/Test_ProtocolBasedInjection.swift b/Tests/Test_ProtocolBasedInjection.swift index ba423c4..b453321 100644 --- a/Tests/Test_ProtocolBasedInjection.swift +++ b/Tests/Test_ProtocolBasedInjection.swift @@ -9,20 +9,16 @@ import Foundation import XCTest import EasyDi -protocol TestProtocol { - +fileprivate protocol TestProtocol { var intParameter: Int { get } } class Test_ProtocolInjection: XCTestCase { - - class TestObject: TestProtocol { - + fileprivate class TestObject: TestProtocol { var intParameter: Int = 0 } - class TestAssembly: Assembly { - + fileprivate class TestAssembly: Assembly { var testObject: TestProtocol { return define(init: TestObject()) { $0.intParameter = 10 diff --git a/Tests/Test_Scope.swift b/Tests/Test_Scope.swift index 586abd1..f4813b6 100644 --- a/Tests/Test_Scope.swift +++ b/Tests/Test_Scope.swift @@ -10,13 +10,8 @@ import XCTest import EasyDi class Test_Scope: XCTestCase { - - // Test that Singleton object initializes only once func testSingleton() { - - // SETUP class TestSingletonObject: NSObject { - @objc static var initCallsCount: Int = 0 override init() { super.init() @@ -27,7 +22,6 @@ class Test_Scope: XCTestCase { } class SingletonAssembly: Assembly { - var singleton: TestSingletonObject { return define(scope: .lazySingleton, init: TestSingletonObject()) { $0.injected = true @@ -36,7 +30,6 @@ class Test_Scope: XCTestCase { } } - // TEST let singletonInstance1 = SingletonAssembly.instance().singleton XCTAssertTrue(singletonInstance1.injected) @@ -46,12 +39,8 @@ class Test_Scope: XCTestCase { XCTAssertEqual(TestSingletonObject.initCallsCount, 1) } - // Test that ObjectGraph object creates only 1 instance per graph func testObjectGraph() { - - - // Setup class ParentObject: NSObject { @objc static var initCallsCount: Int = 0 @@ -83,7 +72,6 @@ class Test_Scope: XCTestCase { } } - // Test let parentInstance1 = ObjectGraphAssembly.instance().parentObject XCTAssertEqual(ParentObject.initCallsCount, 1) XCTAssertNotNil(parentInstance1.child) @@ -96,8 +84,6 @@ class Test_Scope: XCTestCase { // Test that prototype recreated each time func testPrototype() { - - // Setup class PrototypeObject: NSObject { @objc static var initCallsCount: Int = 0 @@ -128,14 +114,45 @@ class Test_Scope: XCTestCase { } } } - - // Test let prototypeInstance1 = ObjectGraphAssembly.instance().prototypeObject XCTAssertEqual(PrototypeObject.initCallsCount, 2) XCTAssertNotNil(prototypeInstance1.child) XCTAssertNotNil(prototypeInstance1.child?.parent) XCTAssertNotEqual(prototypeInstance1.child?.parent, prototypeInstance1) } + + // Test that container stores week reference to the resolved instance + func testWeakSingleton() { + class WeakSingletonObject: NSObject { + @objc static var initCallsCount: Int = 0 + override init () { + super.init(); + WeakSingletonObject.initCallsCount += 1 + } + @objc var injected: Bool = false + } + + class WeakSingletonAssembly: Assembly { + var weakSingleton: WeakSingletonObject { + return define(scope: .weakSingleton, init: WeakSingletonObject()) { + $0.injected = true + return $0 + } + } + } + + weak var weakSingletonInstance = WeakSingletonAssembly.instance().weakSingleton + XCTAssertNil(weakSingletonInstance) + + let firstWeakSingletonInstance = WeakSingletonAssembly.instance().weakSingleton + let secondWeakSingletonInstance = WeakSingletonAssembly.instance().weakSingleton + + XCTAssertTrue(firstWeakSingletonInstance.injected) + XCTAssertTrue(secondWeakSingletonInstance.injected) + + XCTAssertEqual(firstWeakSingletonInstance, secondWeakSingletonInstance) + XCTAssertEqual(WeakSingletonObject.initCallsCount, 2) + } } diff --git a/Tests/Test_StructsInjection.swift b/Tests/Test_StructsInjection.swift index 75874a3..72f0e09 100644 --- a/Tests/Test_StructsInjection.swift +++ b/Tests/Test_StructsInjection.swift @@ -9,19 +9,17 @@ import Foundation import XCTest import EasyDi -protocol TestStructsInjectionProtocol { +fileprivate protocol TestStructsInjectionProtocol { var intValue: Int? { get } var stringValue: String? { get } } -protocol TestStructProtocol { +fileprivate protocol TestStructProtocol { var anotherStruct: TestStructProtocol? { get } } class Test_StructsInjection: XCTestCase { - func testInjectionIntoStructure() { - struct TestStruct: TestStructsInjectionProtocol { var intValue: Int? var stringValue: String? @@ -86,5 +84,4 @@ class Test_StructsInjection: XCTestCase { XCTAssertNotNil(object.anotherStruct) XCTAssertNotNil(object.anotherStruct?.anotherStruct) } - } diff --git a/Tests/Test_Threadsafety.swift b/Tests/Test_Threadsafety.swift index 7f204c4..6b4e162 100644 --- a/Tests/Test_Threadsafety.swift +++ b/Tests/Test_Threadsafety.swift @@ -17,7 +17,6 @@ fileprivate class SomeObject: SomeProtocol { } fileprivate class TestAssembly: Assembly { - var someObject: SomeProtocol { return define(init: SomeObject()) { for i in 0 ..< $0.values.count { @@ -34,7 +33,6 @@ fileprivate class TestAssembly: Assembly { } final class Test_Threadsafety: XCTestCase { - func test_ThreadSafety() { let context = DIContext()