AliSoftware / Dip

Simple Swift Dependency container. Use protocols to resolve your dependencies and avoid singletons / sharedInstances!
MIT License
978 stars 75 forks source link

Fixed crash for collaborating containers #179

Closed trimmurrti closed 6 years ago

trimmurrti commented 6 years ago

This PR fixes a crash, that happens because of singleton state restoration in collaborating container that corrupts the singletons in the container to collaborate with.

The reproduction was found thanks to the fresh thoughts by @PaulTaykalo, as I was stuck on it.

The reproduction is outlined in testThatItCanHandleSeparateContainersAndTheirCollaboration in DipTests.

For some reason there appeared to be a crash under some really hard to meet conditions. It happened, when restoring state in defer in collaboratingResolve method.

The code below reproduces the crash:

class Manager {}
class AnotherManager {}

class Object {
  let manager: Manager?

  init(with manager: Manager?) {
    self.manager = manager
  }
}

class Owner {
  var manager: Manager?
}

extension DipTests {
  func testThatItCanHandleSeparateContainersAndTheirCollaboration() {
    let container = self.container

    let anotherContainer = DependencyContainer()
    anotherContainer.register { Object(with: anotherContainer) }

    container.collaborate(with: anotherContainer)

    container
      .register { Owner() }
      .resolvingProperties { $1.manager = try $0.resolve() }

    container.register(.singleton) { AnotherManager() }
    container.register(.singleton) { Manager() }

    let manager: Manager? = try? container.resolve()
    let another: AnotherManager? = try? container.resolve()
    var owner: Owner? = try? container.resolve(arguments: 1, "")

    let object: Object? = try? container.resolve()
    owner = try? container.resolve()

    let nonNilValues: [Any?] = [another, manager, owner, object, object?.manager]
    nonNilValues.forEach { XCTAssertNotNil($0) }

    XCTAssertTrue(
      owner?.manager
        .flatMap { value in
          manager.flatMap { $0 === value }
        }
        ?? false
    )
  }
}

FYI: If Object and its registration are changed to the code below, the crash disappears:

class Object {
  let manager: Manager?

  init(with manager: Manager?) {
    self.manager = manager
  }
}

anotherContainer.register { Object(with: try anotherContainer.resolve()) }