Compare commits

...

10 Commits

Author SHA1 Message Date
Ilya Puchka 1356a8056f Merge pull request #70 from ilyapuchka/release/4.4.0
Release 4.4.0
2016-03-31 22:55:38 +02:00
Ilya Puchka 0ddb37bea3 Bumped version to 4.4.0 2016-03-31 22:39:00 +02:00
Ilya Puchka 537cad5923 updated docs 2016-03-31 22:37:53 +02:00
Ilya Puchka 0c4ce2213b bootstrap method can throw 2016-03-31 22:19:29 +02:00
Ilya Puchka a91dacb29c Merge pull request #65 from ilyapuchka/feature/eager-singleton-scope
EagerSingleton scope and bootstrap method
2016-03-31 10:03:59 +02:00
Ilya Puchka 895a6f2583 Merge pull request #67 from ilyapuchka/feature/resolvable-calls-order
Reversed order of Resolvable callback calls
2016-03-31 10:03:52 +02:00
Ilya Puchka 73f71a99b2 Reversed order of Resolvable callback calls 2016-03-30 21:56:45 +02:00
Ilya Puchka 41664914f4 EagerSingleton scope and bootstrap method 2016-03-25 10:24:49 +01:00
Ilya Puchka 53bc97ba63 Update README.md
Added some fancy badges
2016-03-24 14:34:38 +01:00
Ilya Puchka c321189b66 Merge branch 'release/4.3.1' into develop 2016-03-24 12:24:05 +01:00
11 changed files with 218 additions and 78 deletions
+8 -1
View File
@@ -1,8 +1,15 @@
# CHANGELOG
## 4.4.0
* Added `.EagerSingleton` scope for objectes requiring early instantiation and `bootstrap()` method on `DepenencyContainer`.
[#65](https://github.com/AliSoftware/Dip/pull/65), [@ilyapuchka](https://github.com/ilyapuchka)
* Reverted order of `Resolvable` callbacks.
[#67](https://github.com/AliSoftware/Dip/pull/67), [@ilyapuchka](https://github.com/ilyapuchka)
## 4.3.1
* Fix Swift 2.2 compile errors in tests
* Fix Swift 2.2 compile errors in tests.
[#62](https://github.com/AliSoftware/Dip/pull/62), [@mwoollard](https://github.com/mwoollard)
## 4.3.0
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Dip"
s.version = "4.3.1"
s.version = "4.4.0"
s.summary = "A simple Dependency Resolver: Dependency Injection using Protocol resolution."
s.description = <<-DESC
+2 -2
View File
@@ -866,7 +866,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 4.3.1;
CURRENT_PROJECT_VERSION = 4.4.0;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -915,7 +915,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 4.3.1;
CURRENT_PROJECT_VERSION = 4.4.0;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
+116 -59
View File
@@ -96,78 +96,104 @@ class ComponentScopeTests: XCTestCase {
}
func testThatItReusesInstanceForSingletonScope() {
//given
container.register(.Singleton) { ServiceImp1() as Service }
func test(scope: ComponentScope) {
//given
container.register(scope) { ServiceImp1() as Service }
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertTrue(service1 === service2)
}
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertTrue(service1 === service2)
test(.Singleton)
test(.EagerSingleton)
}
func testThatSingletonIsNotReusedAcrossContainers() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let secondContainer = DependencyContainer()
secondContainer.register(def, forTag: nil)
func test(scope: ComponentScope) {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let secondContainer = DependencyContainer()
secondContainer.register(def, forTag: nil)
//when
let service1 = try! container.resolve() as Service
let service2 = try! secondContainer.resolve() as Service
//then
XCTAssertTrue(service1 !== service2, "Singleton instances should not be reused across containers")
}
//when
let service1 = try! container.resolve() as Service
let service2 = try! secondContainer.resolve() as Service
//then
XCTAssertTrue(service1 !== service2, "Singleton instances should not be reused across containers")
test(.Singleton)
test(.EagerSingleton)
}
func testThatSingletonIsReleasedWhenDefinitionIsRemoved() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
func test(scope: ComponentScope) {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.remove(def, forTag: nil)
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is removed from the container")
}
//when
container.remove(def, forTag: nil)
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is removed from the container")
test(.Singleton)
test(.EagerSingleton)
}
func testThatSingletonIsReleasedWhenDefinitionIsOverridden() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
func test(scope: ComponentScope) {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is overridden")
}
//when
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is overridden")
test(.Singleton)
test(.EagerSingleton)
}
func testThatSingletonIsReleasedWhenContainerIsReset() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
func test(scope: ComponentScope) {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.reset()
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when container is reset")
}
//when
container.reset()
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when container is reset")
test(.Singleton)
test(.EagerSingleton)
}
func testThatItReusesInstanceInObjectGraphScopeDuringResolve() {
//given
container.register(.ObjectGraph) { Client(server: try self.container.resolve()) as Client }
container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
server.client = try container.resolve() as Client
container.register(.ObjectGraph) { Server() as Server }
.resolveDependencies { container, server in
server.client = try container.resolve() as Client
}
//when
@@ -181,8 +207,9 @@ class ComponentScopeTests: XCTestCase {
func testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve() {
//given
container.register(.ObjectGraph) { Client(server: try self.container.resolve()) as Client }
container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
server.client = try container.resolve() as Client
container.register(.ObjectGraph) { Server() as Server }
.resolveDependencies { container, server in
server.client = try container.resolve() as Client
}
//when
@@ -200,14 +227,15 @@ class ComponentScopeTests: XCTestCase {
func testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag() {
//given
var service2: Service?
container.register(.ObjectGraph) { ServiceImp1() as Service }.resolveDependencies { (c, _) in
service2 = try c.resolve(tag: "service") as Service
//then
//when service1 is resolved using this definition due to fallback to nil tag
//we don't want every next resolve of service reuse it
XCTAssertTrue(service2 is ServiceImp2)
container.register(.ObjectGraph) { ServiceImp1() as Service }
.resolveDependencies { (c, _) in
service2 = try c.resolve(tag: "service") as Service
//then
//when service1 is resolved using this definition due to fallback to nil tag
//we don't want every next resolve of service reuse it
XCTAssertTrue(service2 is ServiceImp2)
}
container.register(tag: "service", .ObjectGraph) { ServiceImp2() as Service}
@@ -217,6 +245,35 @@ class ComponentScopeTests: XCTestCase {
//then
XCTAssertTrue(service1 is ServiceImp1)
}
func testThatOnlyEagerSingletonIsCreatedWhenContainerIsBootsrapped() {
//given
var eagerSingletonResolved = false
container.register(tag: "eager", .EagerSingleton) { ServiceImp1() as Service }
.resolveDependencies { container, service in eagerSingletonResolved = true }
container.register(tag: "singleton", .Singleton) { ServiceImp1() as Service }
.resolveDependencies { container, service in XCTFail() }
container.register(tag: "prototype", .Prototype) { ServiceImp1() as Service }
.resolveDependencies { container, service in XCTFail() }
container.register(tag: "graph", .ObjectGraph) { ServiceImp1() as Service }
.resolveDependencies { container, service in XCTFail() }
//when
try! container.bootstrap()
XCTAssertTrue(eagerSingletonResolved)
}
func testThatContainerCanBeBootstrappedAgainAfterReset() {
try! container.bootstrap()
XCTAssertTrue(container.bootstrapped)
container.reset()
XCTAssertFalse(container.bootstrapped)
}
}
+28
View File
@@ -54,6 +54,7 @@ class DipTests: XCTestCase {
("testThatItThrowsErrorIfConstructorThrows", testThatItThrowsErrorIfConstructorThrows),
("testThatItThrowsErrorIfFailsToResolveDependency", testThatItThrowsErrorIfFailsToResolveDependency),
("testThatItCallsDidResolveDependenciesOnResolvableIntance", testThatItCallsDidResolveDependenciesOnResolvableIntance),
("testThatItCallsDidResolveDependenciesInReverseOrder", testThatItCallsDidResolveDependenciesInReverseOrder),
("testThatItResolvesCircularDependencies", testThatItResolvesCircularDependencies)
]
}
@@ -256,6 +257,33 @@ class DipTests: XCTestCase {
XCTAssertTrue((singletonService as! ResolvableService).didResolveDependenciesCalled)
}
func testThatItCallsDidResolveDependenciesInReverseOrder() {
class ResolvableService: Service, Resolvable {
static var resolved: [Service] = []
func didResolveDependencies() {
ResolvableService.resolved.append(self)
}
}
var resolveDependenciesCalled = false
var service2: Service!
container.register { ResolvableService() as Service }
.resolveDependencies { _, service in
if !resolveDependenciesCalled {
resolveDependenciesCalled = true
service2 = try! self.container.resolve() as Service
}
return
}
let service1 = try! container.resolve() as Service
XCTAssertTrue(ResolvableService.resolved.first === service2)
XCTAssertTrue(ResolvableService.resolved.last === service1)
}
func testThatItResolvesCircularDependencies() {
class ResolvableServer: Server, Resolvable {
@@ -13,6 +13,7 @@ Dip supports three different scopes of objects: _Prototype_, _ObjectGraph_ and _
* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. This is the default scope.
* The `.ObjectGraph` scope is like `.Prototype` scope, but it will make the `DependencyContainer` to reuse resolved instances during one (recursive) call to `resolve` method. When this call returns, all resolved instances will be discarded and next call to `resolve` will produce new instances. This scope should be used to resolve [circular dependencies](Circular%20dependencies).
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime.
* The `.EagerSingleton` scope is the same as `.Singleton` scope but instances with this cope will be created when you call `bootstrap()` method on the container.
The `.Prototype` scope is the default. To set a scope you pass it as an argument to `register` method.
*/
@@ -43,5 +44,23 @@ let sameSharedService = try! container.resolve(tag: "shared instance") as Servic
// same instances, the singleton scope keep and reuse instances during the lifetime of the container
sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3
/*:
### Bootstrapping
You can use `bootstrap()` method to fix your container setup and initialise components registered with `EagerSingleton` scope.
After bootstrapping if you try to add or remove any definition it will cause runtime exception. Call `boostrap` when you registered all the components, for example at the end of initialization block if you use `init(configBlock:)`.
*/
var resolvedEagerSingleton = false
let definition = container.register(tag: "eager shared instance", .EagerSingleton) { ServiceImp1() as Service }
.resolveDependencies { _ in resolvedEagerSingleton = true }
try! container.bootstrap()
resolvedEagerSingleton
let eagerSharedService = try! container.resolve(tag: "eager shared instance") as Service
container.remove(definition)
//: [Next: Circular Dependencies](@next)
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
+5 -3
View File
@@ -5,6 +5,8 @@
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![License](https://img.shields.io/cocoapods/l/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Platform](https://img.shields.io/cocoapods/p/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Swift Version](https://img.shields.io/badge/Linux-compatible-4BC51D.svg?style=flat)](https://developer.apple.com/swift)
[![Swift Version](https://img.shields.io/badge/Swift-2.2-F16D39.svg?style=flat)](https://developer.apple.com/swift)
![Animated Dipping GIF](cinnamon-pretzels-caramel-dipping.gif)
_Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipes/appetizer/homemade-soft-cinnamon-sugar-pretzel-bites-with-salted-caramel-dipping-sauce.html)_
@@ -56,7 +58,7 @@ If you use [_Swift Package Manager_](https://swift.org/package-manager/) add Dip
let package = Package(
name: "MyPackage",
dependencies: [
.Package(url: "https://github.com/AliSoftware/Dip.git", "4.3.1")
.Package(url: "https://github.com/AliSoftware/Dip.git", "4.4.0")
]
)
```
@@ -113,11 +115,11 @@ let service: ServiceImp = try! container.resolve()
### Scopes
Dip provides three _scopes_ that you can use to register dependencies:
Dip provides four _scopes_ that you can use to register dependencies:
* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. It's a default scope.
* The `.ObjectGraph` scope is like `.Prototype` scope but it will make the `DependencyContainer` to reuse resolved instances during one call to `resolve` method. When this call returns all resolved insances will be discarded and next call to `resolve` will produce new instances. This scope _must_ be used to properly resolve circular dependencies.
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime.
* The `.Singleton` and `.EagerSingleton` scopes will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime.
You specify scope when you register dependency like that:
+2 -2
View File
@@ -49,6 +49,7 @@ extension DependencyContainer {
instead of using `Injected<T>` or `InjectedWeak<T>` types.
**Example**:
```swift
class MyCustomBox<T> {
private(set) var value: T?
@@ -75,7 +76,7 @@ public protocol AutoInjectedPropertyBox: class {
- parameter container: A container to be used to resolve an instance
-note: This method is not intended to be called manually, `DependencyContainer` will call it by itself.
- note: This method is not intended to be called manually, `DependencyContainer` will call it by itself.
*/
func resolve(container: DependencyContainer) throws
}
@@ -93,7 +94,6 @@ public protocol AutoInjectedPropertyBox: class {
class ClientImp: Client {
var service = Injected<Service>()
}
```
- seealso: `InjectedWeak`
+5
View File
@@ -131,6 +131,11 @@ public enum ComponentScope {
```
*/
case Singleton
/**
The same scope as `Singleton`, but instance will be created when container is bootstrapped.
*/
case EagerSingleton
}
/**
+32 -4
View File
@@ -43,6 +43,9 @@ public final class DependencyContainer {
let resolvedInstances = ResolvedInstances()
let lock = RecursiveLock()
private(set) var bootstrapped = false
private var bootstrapQueue: [() throws -> ()] = []
/**
Designated initializer for a DependencyContainer
@@ -58,6 +61,22 @@ public final class DependencyContainer {
configBlock(self)
}
/**
Call this method to complete container setup. After container is bootstrapped
you can not add or remove definitions. Trying to do so will cause runtime exception.
You can completely reset container, after reset you can bootstrap it again.
During bootsrap container will instantiate components registered with `EagerSingleton` scope.
- throws: `DipError` if failed to instantiate any component
*/
public func bootstrap() throws {
try threadSafe {
bootstrapped = true
try bootstrapQueue.forEach({ try $0() })
bootstrapQueue.removeAll()
}
}
private func threadSafe<T>(@noescape closure: () throws -> T) rethrows -> T {
lock.lock()
defer {
@@ -172,9 +191,15 @@ extension DependencyContainer {
public func register<T, F>(definition: DefinitionOf<T, F>, forTag tag: DependencyTagConvertible? = nil) {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag?.dependencyTag)
register(definition, forKey: key)
if case .EagerSingleton = definition.scope {
bootstrapQueue.append({ let _ = try self.resolve(tag: tag) as T })
}
}
func register(definition: Definition, forKey key: DefinitionKey) {
func register(definition: _Definition, forKey key: DefinitionKey) {
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
threadSafe {
definitions[key] = definition
resolvedInstances.singletons[key] = nil
@@ -312,7 +337,7 @@ extension DependencyContainer {
func storeResolvedInstance<T>(instance: T, forKey key: DefinitionKey, inScope scope: ComponentScope) {
switch scope {
case .Singleton: singletons[key] = instance
case .Singleton, .EagerSingleton: singletons[key] = instance
case .ObjectGraph: resolvedInstances[key] = instance
case .Prototype: break
}
@@ -324,7 +349,7 @@ extension DependencyContainer {
func previouslyResolvedInstance<T>(forKey key: DefinitionKey, inScope scope: ComponentScope) -> T? {
switch scope {
case .Singleton: return singletons[key] as? T
case .Singleton, .EagerSingleton: return singletons[key] as? T
case .ObjectGraph: return resolvedInstances[key] as? T
case .Prototype: return nil
}
@@ -340,7 +365,7 @@ extension DependencyContainer {
if depth == 0 {
// We call didResolveDependencies only at this point
// because this is a point when dependencies graph is complete.
for resolvedInstance in resolvableInstances {
for resolvedInstance in resolvableInstances.reverse() {
resolvedInstance.didResolveDependencies()
}
resolvedInstances.removeAll()
@@ -372,6 +397,8 @@ extension DependencyContainer {
}
func remove(definitionForKey key: DefinitionKey) {
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
threadSafe {
definitions[key] = nil
resolvedInstances.singletons[key] = nil
@@ -385,6 +412,7 @@ extension DependencyContainer {
threadSafe {
definitions.removeAll()
resolvedInstances.singletons.removeAll()
bootstrapped = false
}
}