diff --git a/.gitignore b/.gitignore index 47042c2..81bf7ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store xcuserdata +.build/ diff --git a/FieryCrucible.xcodeproj/project.pbxproj b/FieryCrucible.xcodeproj/project.pbxproj index ebd47e6..5a81a12 100644 --- a/FieryCrucible.xcodeproj/project.pbxproj +++ b/FieryCrucible.xcodeproj/project.pbxproj @@ -8,14 +8,29 @@ /* Begin PBXBuildFile section */ E53405B01A3020CE00A474C1 /* FieryCrucible.h in Headers */ = {isa = PBXBuildFile; fileRef = E53405AF1A3020CE00A474C1 /* FieryCrucible.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E5A928C81A32C0B60071875D /* DependencyFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5A928C71A32C0B60071875D /* DependencyFactory.swift */; }; + E55D501E1D84AD4700860E02 /* FieryCrucible.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E53405AA1A3020CE00A474C1 /* FieryCrucible.framework */; }; + E55D50251D84AD8800860E02 /* DependencyFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55D50241D84AD8800860E02 /* DependencyFactory.swift */; }; + E55D50271D84AD9C00860E02 /* FieryCrucibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E55D50261D84AD9C00860E02 /* FieryCrucibleTests.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + E55D501F1D84AD4700860E02 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E53405A11A3020CE00A474C1 /* Project object */; + proxyType = 1; + remoteGlobalIDString = E53405A91A3020CE00A474C1; + remoteInfo = FieryCrucible; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ E53405AA1A3020CE00A474C1 /* FieryCrucible.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FieryCrucible.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E53405AE1A3020CE00A474C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E53405AF1A3020CE00A474C1 /* FieryCrucible.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FieryCrucible.h; sourceTree = ""; }; - E5A928C71A32C0B60071875D /* DependencyFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DependencyFactory.swift; sourceTree = ""; }; + E55D50191D84AD4700860E02 /* FieryCrucibleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FieryCrucibleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + E55D501D1D84AD4700860E02 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E55D50241D84AD8800860E02 /* DependencyFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DependencyFactory.swift; path = Sources/DependencyFactory.swift; sourceTree = SOURCE_ROOT; }; + E55D50261D84AD9C00860E02 /* FieryCrucibleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FieryCrucibleTests.swift; path = Tests/FieryCrucibleTests/FieryCrucibleTests.swift; sourceTree = SOURCE_ROOT; }; E5F98A491C5B13300072BAF8 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; E5F98A4B1C5B13370072BAF8 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; /* End PBXFileReference section */ @@ -28,6 +43,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E55D50161D84AD4700860E02 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E55D501E1D84AD4700860E02 /* FieryCrucible.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -37,6 +60,7 @@ E5F98A4B1C5B13370072BAF8 /* README.md */, E5F98A491C5B13300072BAF8 /* LICENSE */, E53405AC1A3020CE00A474C1 /* FieryCrucible */, + E55D501A1D84AD4700860E02 /* FieryCrucibleTests */, E53405AB1A3020CE00A474C1 /* Products */, ); sourceTree = ""; @@ -45,6 +69,7 @@ isa = PBXGroup; children = ( E53405AA1A3020CE00A474C1 /* FieryCrucible.framework */, + E55D50191D84AD4700860E02 /* FieryCrucibleTests.xctest */, ); name = Products; sourceTree = ""; @@ -53,7 +78,7 @@ isa = PBXGroup; children = ( E53405AF1A3020CE00A474C1 /* FieryCrucible.h */, - E5A928C71A32C0B60071875D /* DependencyFactory.swift */, + E55D50241D84AD8800860E02 /* DependencyFactory.swift */, E53405AD1A3020CE00A474C1 /* Supporting Files */, ); path = FieryCrucible; @@ -67,6 +92,15 @@ name = "Supporting Files"; sourceTree = ""; }; + E55D501A1D84AD4700860E02 /* FieryCrucibleTests */ = { + isa = PBXGroup; + children = ( + E55D50261D84AD9C00860E02 /* FieryCrucibleTests.swift */, + E55D501D1D84AD4700860E02 /* Info.plist */, + ); + path = FieryCrucibleTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -99,20 +133,44 @@ productReference = E53405AA1A3020CE00A474C1 /* FieryCrucible.framework */; productType = "com.apple.product-type.framework"; }; + E55D50181D84AD4700860E02 /* FieryCrucibleTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = E55D50231D84AD4700860E02 /* Build configuration list for PBXNativeTarget "FieryCrucibleTests" */; + buildPhases = ( + E55D50151D84AD4700860E02 /* Sources */, + E55D50161D84AD4700860E02 /* Frameworks */, + E55D50171D84AD4700860E02 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + E55D50201D84AD4700860E02 /* PBXTargetDependency */, + ); + name = FieryCrucibleTests; + productName = FieryCrucibleTests; + productReference = E55D50191D84AD4700860E02 /* FieryCrucibleTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ E53405A11A3020CE00A474C1 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; + LastSwiftUpdateCheck = 0800; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = "Justin Kolb"; TargetAttributes = { E53405A91A3020CE00A474C1 = { CreatedOnToolsVersion = 6.1.1; LastSwiftMigration = 0800; }; + E55D50181D84AD4700860E02 = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 3M27LW784R; + LastSwiftMigration = 0800; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = E53405A41A3020CE00A474C1 /* Build configuration list for PBXProject "FieryCrucible" */; @@ -128,6 +186,7 @@ projectRoot = ""; targets = ( E53405A91A3020CE00A474C1 /* FieryCrucible */, + E55D50181D84AD4700860E02 /* FieryCrucibleTests */, ); }; /* End PBXProject section */ @@ -140,6 +199,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + E55D50171D84AD4700860E02 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -147,12 +213,28 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E5A928C81A32C0B60071875D /* DependencyFactory.swift in Sources */, + E55D50251D84AD8800860E02 /* DependencyFactory.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E55D50151D84AD4700860E02 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E55D50271D84AD9C00860E02 /* FieryCrucibleTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + E55D50201D84AD4700860E02 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = E53405A91A3020CE00A474C1 /* FieryCrucible */; + targetProxy = E55D501F1D84AD4700860E02 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ E53405BE1A3020CF00A474C1 /* Debug */ = { isa = XCBuildConfiguration; @@ -167,8 +249,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -178,6 +262,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -214,8 +299,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -224,6 +311,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -244,6 +332,8 @@ E53405C11A3020CF00A474C1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -254,6 +344,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.franticapparatus.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; }; name = Debug; @@ -261,6 +352,8 @@ E53405C21A3020CF00A474C1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -275,6 +368,51 @@ }; name = Release; }; + E55D50211D84AD4700860E02 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 3M27LW784R; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = FieryCrucibleTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.franticapparatus.FieryCrucibleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + E55D50221D84AD4700860E02 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 3M27LW784R; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = FieryCrucibleTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.franticapparatus.FieryCrucibleTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -296,6 +434,14 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + E55D50231D84AD4700860E02 /* Build configuration list for PBXNativeTarget "FieryCrucibleTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E55D50211D84AD4700860E02 /* Debug */, + E55D50221D84AD4700860E02 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; /* End XCConfigurationList section */ }; rootObject = E53405A11A3020CE00A474C1 /* Project object */; diff --git a/FieryCrucible.xcodeproj/xcshareddata/xcschemes/FieryCrucible.xcscheme b/FieryCrucible.xcodeproj/xcshareddata/xcschemes/FieryCrucible.xcscheme index 56f26bd..0053ee3 100644 --- a/FieryCrucible.xcodeproj/xcshareddata/xcschemes/FieryCrucible.xcscheme +++ b/FieryCrucible.xcodeproj/xcshareddata/xcschemes/FieryCrucible.xcscheme @@ -1,6 +1,6 @@ @@ -42,6 +42,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + CFBundlePackageType FMWK CFBundleShortVersionString - 1.3.2 + 2.0.0 CFBundleSignature ???? CFBundleVersion - 1.3.2 + 2.0.0 NSPrincipalClass diff --git a/FieryCrucibleTests/Info.plist b/FieryCrucibleTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/FieryCrucibleTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..81dbd65 --- /dev/null +++ b/Package.swift @@ -0,0 +1,5 @@ +import PackageDescription + +let package = Package( + name: "FieryCrucible" +) diff --git a/README.md b/README.md index 6dea722..fca453d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,13 @@ A minimalist type safe Swift dependency injector factory. Where all true instanc #### Changelog +#####Version 2.0.0 +* Updated for Swift 3.0 and Xcode 8 GM release +* Initial attempt at Swift Package Manager support +* Added tests to help make sure everything works as expected and to provide examples of usage +* Cleaned up the API to remove the method that required a specific name parameter (left over from before #function was used for the name) +* A belated thanks to Anton Beloglazov for help with fixing circular dependencies back around 1.0.0 + #####Version 1.3.3 * Thanks to Or Rosenblatt for updating code to support Swift 3.0. diff --git a/FieryCrucible/DependencyFactory.swift b/Sources/DependencyFactory.swift similarity index 63% rename from FieryCrucible/DependencyFactory.swift rename to Sources/DependencyFactory.swift index 0a3c219..6526042 100644 --- a/FieryCrucible/DependencyFactory.swift +++ b/Sources/DependencyFactory.swift @@ -48,12 +48,12 @@ private class WeakContainer : InstanceContainer { } } -private func ==(lhs: DependencyFactory.InstanceKey, rhs: DependencyFactory.InstanceKey) -> Bool { +private func ==(lhs: DependencyFactory.InstanceKey, rhs: DependencyFactory.InstanceKey) -> Bool { return (lhs.lifecycle == rhs.lifecycle) && (lhs.name == rhs.name) } -public class DependencyFactory { - private enum Lifecyle : String, CustomStringConvertible { +open class DependencyFactory { + fileprivate enum Lifecyle : String, CustomStringConvertible { case Shared = "shared" case WeakShared = "weakShared" case Unshared = "unshared" @@ -64,7 +64,7 @@ public class DependencyFactory { } } - private struct InstanceKey : Hashable, CustomStringConvertible { + fileprivate struct InstanceKey : Hashable, CustomStringConvertible { let lifecycle: Lifecyle let name: String @@ -77,24 +77,20 @@ public class DependencyFactory { } } - private var sharedInstances: [String:AnyObject] = [:] - private var weakSharedInstances: [String:AnyObject] = [:] - private var scopedInstances: [String:AnyObject] = [:] - private var instanceStack: [InstanceKey] = [] - private var configureStack: [() -> ()] = [] - private var requestDepth = 0 + fileprivate var sharedInstances: [String:AnyObject] = [:] + fileprivate var weakSharedInstances: [String:AnyObject] = [:] + fileprivate var scopedInstances: [String:AnyObject] = [:] + fileprivate var instanceStack: [InstanceKey] = [] + fileprivate var configureStack: [() -> ()] = [] + fileprivate var requestDepth = 0 public init() { } - public final func shared(factory: @noescape () -> T, name: String = #function, configure: ((T) -> ())? = nil) -> T { - return shared(name, factory: factory(), configure: configure) + public final func shared(name: String = #function, factory: () -> T, configure: ((T) -> Void)? = nil) -> T { + return shared(name: name, factory(), configure: configure) } - public final func shared(factory: @autoclosure () -> T, name: String = #function, configure: ((T) -> ())? = nil) -> T { - return shared(name, factory: factory, configure: configure) - } - - public final func shared(_ name: String, factory: @autoclosure () -> T, configure: ((T) -> ())? = nil) -> T { + public final func shared(name: String = #function, _ factory: @autoclosure () -> T, configure: ((T) -> Void)? = nil) -> T { return inject( lifecyle: .Shared, name: name, @@ -105,15 +101,11 @@ public class DependencyFactory { ) } - public final func weakShared(factory: @noescape () -> T, name: String = #function, configure: ((T) -> ())? = nil) -> T { - return weakShared(name, factory: factory(), configure: configure) - } - - public final func weakShared(factory: @autoclosure () -> T, name: String = #function, configure: ((T) -> ())? = nil) -> T { - return weakShared(name, factory: factory, configure: configure) + public final func weakShared(name: String = #function, factory: () -> T, configure: ((T) -> Void)? = nil) -> T { + return weakShared(name: name, factory(), configure: configure) } - public final func weakShared(_ name: String, factory: @autoclosure () -> T, configure: ((T) -> ())? = nil) -> T { + public final func weakShared(name: String = #function, _ factory: @autoclosure () -> T, configure: ((T) -> Void)? = nil) -> T { return inject( lifecyle: .WeakShared, name: name, @@ -124,15 +116,11 @@ public class DependencyFactory { ) } - public final func unshared(factory: @noescape () -> T, name: String = #function, configure: ((T) -> ())? = nil) -> T { - return unshared(name, factory: factory(), configure: configure) + public final func unshared(name: String = #function, factory: () -> T, configure: ((T) -> Void)? = nil) -> T { + return unshared(name: name, factory(), configure: configure) } - public final func unshared(factory: @autoclosure () -> T, name: String = #function, configure: ((T) -> ())? = nil) -> T { - return unshared(name, factory: factory, configure: configure) - } - - public final func unshared(_ name: String, factory: @autoclosure () -> T, configure: ((T) -> ())? = nil) -> T { + public final func unshared(name: String = #function, _ factory: @autoclosure () -> T, configure: ((T) -> Void)? = nil) -> T { var unsharedInstances: [String:AnyObject] = [:] return inject( lifecyle: .Unshared, @@ -144,15 +132,11 @@ public class DependencyFactory { ) } - public final func scoped(factory: @noescape () -> T, name: String = #function, configure: ((T) -> ())? = nil) -> T { - return scoped(name, factory: factory(), configure: configure) - } - - public final func scoped(factory: @autoclosure () -> T, name: String = #function, configure: ((T) -> ())? = nil) -> T { - return scoped(name, factory: factory, configure: configure) + public final func scoped(name: String = #function, factory: () -> T, configure: ((T) -> Void)? = nil) -> T { + return scoped(name: name, factory(), configure: configure) } - public final func scoped(_ name: String, factory: @autoclosure () -> T, configure: ((T) -> ())? = nil) -> T { + public final func scoped(name: String = #function, _ factory: @autoclosure () -> T, configure: ((T) -> Void)? = nil) -> T { return inject( lifecyle: .Scoped, name: name, @@ -163,7 +147,7 @@ public class DependencyFactory { ) } - private final func inject(lifecyle: Lifecyle, name: String, instancePool: inout [String:AnyObject], containerFactory: (T) -> C, factory: @autoclosure () -> T, configure: ((T) -> ())?) -> T { + fileprivate final func inject(lifecyle: Lifecyle, name: String, instancePool: inout [String:AnyObject], containerFactory: (T) -> C, factory: @autoclosure () -> T, configure: ((T) -> Void)?) -> T where C.InstanceType == T { if let container = instancePool[name] as? C { if let instance = container.instance { return instance diff --git a/Tests/FieryCrucibleTests/FieryCrucibleTests.swift b/Tests/FieryCrucibleTests/FieryCrucibleTests.swift new file mode 100644 index 0000000..b1ad439 --- /dev/null +++ b/Tests/FieryCrucibleTests/FieryCrucibleTests.swift @@ -0,0 +1,213 @@ +import XCTest +@testable import FieryCrucible + +class FieryCrucibleTests: XCTestCase { + var testFactory: TestFactory! + + override func setUp() { + super.setUp() + + testFactory = TestFactory() + TestInstance.clearInitCounts() + } + + override func tearDown() { + super.tearDown() + } + + func testShared() { + // Once instance per factory instance (effectively a singleton), useful for NSFormatter instances. + // If needed by multiple threads make an instance of the factory per thread so that each thread can + // get its own distict instance of the object. + let instanceA1 = testFactory.sharedA1() + let instanceA2 = testFactory.sharedA2() + let instanceB1 = testFactory.sharedB1() + let instanceB2 = testFactory.sharedB2() + + XCTAssertEqual(instanceA1.name, "A1") + XCTAssertEqual(instanceA2.name, "A2") + XCTAssertEqual(instanceB1.name, "B1") + XCTAssertEqual(instanceB2.name, "B2") + + XCTAssertEqual(instanceA1.initCount, 1) + XCTAssertEqual(instanceA2.initCount, 1) + XCTAssertEqual(instanceB1.initCount, 1) + XCTAssertEqual(instanceB2.initCount, 1) + + XCTAssertTrue(instanceA1.dependency! === instanceA2) + XCTAssertNil(instanceA1.delegate) + XCTAssertNil(instanceA2.dependency) + XCTAssertTrue(instanceA2.delegate! === instanceA1) + XCTAssertNil(instanceB1.dependency) + XCTAssertNil(instanceB1.delegate) + XCTAssertTrue(instanceB2.dependency! === instanceB1) + XCTAssertTrue(instanceB2.delegate! === instanceA1) + } + + func testUnshared() { + // New instance every time + let instanceA1 = testFactory.unsharedA1() + let instanceA2 = testFactory.unsharedA2() + + XCTAssertEqual(instanceA1.name, "A1") + XCTAssertEqual(instanceA2.name, "A2") + + XCTAssertEqual(instanceA1.initCount, 1) + XCTAssertEqual(instanceA2.initCount, 2) + + XCTAssertTrue(instanceA1.dependency! !== instanceA2) + XCTAssertNil(instanceA1.delegate) + XCTAssertNil(instanceA2.dependency) + XCTAssertNil(instanceA2.delegate) + } + + func testScoped() { + // Same instance returned only if in the same dependency request, otherwise makes a new instance + // Useful for creating instances of view controllers that will be assigned as the delegate of a view + // REQ# A1 A2 A3 + let instanceA1 = testFactory.scopedA1() // 1 Y Y Y (one A3 for both A1 and A2 for this dependency fulfilling request) + let instanceA2 = testFactory.scopedA2() // 2 N Y Y (a different A3 for this second instance of A2 since this is a different request) + let instanceA3 = testFactory.scopedA3() // 3 N N Y (a third instance of A3) + + XCTAssertEqual(instanceA1.name, "A1") + XCTAssertEqual(instanceA2.name, "A2") + XCTAssertEqual(instanceA3.name, "A3") + + XCTAssertEqual(instanceA1.initCount, 1) + XCTAssertEqual(instanceA2.initCount, 2) + XCTAssertEqual(instanceA3.initCount, 3) + + XCTAssertTrue(instanceA1.dependency?.delegate === instanceA1.delegate) + XCTAssertTrue(instanceA1.delegate !== instanceA2.delegate) + } + + func testWeakShared() { + // Same as shared, but if no other objects retain the instance then it will be + // released and free up memory (the factory doesn't retain it) + var instanceA1: TestInstance? = testFactory.weakSharedA1() + + XCTAssertEqual(instanceA1?.name, "A1") + XCTAssertEqual(instanceA1?.initCount, 1) + + instanceA1 = nil + XCTAssertEqual(TestInstance.countForName("A1"), 0) + } + + static var allTests : [(String, (FieryCrucibleTests) -> () throws -> Void)] { + return [ + ("testShared", testShared), + ("testUnshared", testUnshared), + ("testScoped", testScoped), + ("testWeakShared", testWeakShared), + ] + } +} + +class TestInstance { + private static var initCountByName = [String : Int](minimumCapacity: 16) + public let name: String + public var dependency: TestInstance? + public weak var delegate: TestInstance? + public var requiredDuringInit = false + public var initCount: Int { + return TestInstance.initCountByName[name] ?? 0 + } + + public init(name: String, dependency: TestInstance? = nil) { + self.name = name + self.dependency = dependency + + if let count = TestInstance.initCountByName[name] { + TestInstance.initCountByName[name] = count + 1 + } + else { + TestInstance.initCountByName[name] = 1 + } + } + + deinit { + if let count = TestInstance.initCountByName[name] { + TestInstance.initCountByName[name] = count - 1 + } + } + + public static func countForName(_ name: String) -> Int { + return TestInstance.initCountByName[name] ?? 0 + } + + public static func clearInitCounts() { + initCountByName.removeAll(keepingCapacity: true) + } +} + +class TestFactory : DependencyFactory { + public func sharedA1() -> TestInstance { + return shared(TestInstance(name: "A1", dependency: sharedA2())) + } + + public func sharedA2() -> TestInstance { + return shared(TestInstance(name: "A2")) { (instance) in + instance.delegate = self.sharedA1() + } + } + + public func sharedB1() -> TestInstance { + return shared( + factory: { () -> TestInstance in + // Only use this form if you need to configure an instance that will be passed into + // another objects initializer. The configure block only gets called after all init + // methods have been called and is useful for setting properties that would cause + // dependency cycles like delegates. + let instance = TestInstance(name: "B1") + instance.requiredDuringInit = true + return instance + } + ) + } + + public func sharedB2() -> TestInstance { + return shared( + factory: { () -> TestInstance in + // Only use this form if you need to configure an instance that will be passed into + // another object's initializer. The configure block only gets called after all init + // methods have been called and is useful for setting properties that would cause + // dependency cycles like delegates. + precondition(sharedB1().requiredDuringInit, "Fake a situation where bad program behavior would occur if property is not set before now") + let instance = TestInstance(name: "B2", dependency: sharedB1()) + return instance + }, + configure: { (instance) in + instance.delegate = self.sharedA1() + } + ) + } + + public func unsharedA1() -> TestInstance { + return unshared(TestInstance(name: "A1", dependency: unsharedA2())) + } + + public func unsharedA2() -> TestInstance { + return unshared(TestInstance(name: "A2")) + } + + public func scopedA1() -> TestInstance { + return scoped(TestInstance(name: "A1", dependency: scopedA2())) { (instance) in + instance.delegate = self.scopedA3() + } + } + + public func scopedA2() -> TestInstance { + return scoped(TestInstance(name: "A2")) { (instance) in + instance.dependency = self.scopedA3() // A bit of a hack to keep A3 alive long enough to assert with + instance.delegate = self.scopedA3() + } + } + + public func scopedA3() -> TestInstance { + return scoped(TestInstance(name: "A3")) + } + + public func weakSharedA1() -> TestInstance { + return weakShared(TestInstance(name: "A1")) + } +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 0000000..2db34cb --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,6 @@ +import XCTest +@testable import FieryCrucibleTests + +XCTMain([ + testCase(FieryCrucibleTests.allTests), +])