hmlongco / Factory

A new approach to Container-Based Dependency Injection for Swift and SwiftUI.
MIT License
1.7k stars 107 forks source link

Resetting instance variables on long lived object when resetting scope #156

Closed AndrewSB closed 8 months ago

AndrewSB commented 8 months ago

Hey! i'm trying to understand the best way to compose something like this. i have a long lived coordinator, that has injected references into objects that are reset. here's a simplified example

/// only one instance ever created, at launch of app
class ApplicationCoordinator {
  @Injected(\.currentLoggedInUserInfoProvider) var currentLoggedInUserInfoProvider
}

extension Container {
  var currentLoggedInUserInfoProvider: Factory<CurrentLoggedInUserInfoProvider> {
    self { CurrentLoggedInUserInfoProvider }.scope(.session)
  } 
}

the issue that i have, is after logging out and resetting .scope(.session), my ApplicationCoordinator still has a reference to that old instance.

how should i solve this problem? is this a case where i can't use @Injected, and instead need to resolve imperatively whenever i want to use currentLoggedInUserInfoProvider in ApplicationCoordinator?

hmlongco commented 8 months ago

Resetting a container (or scope) will only have an effect on future resolutions. Anything already resolved is already resolved.

let cached: Service? = Service()
let reference: Service? = cached
cached = nil
// what is reference?
AndrewSB commented 8 months ago

that makes sense. so following my example above:

extension Container {
  var currentLoggedInUserInfoProvider: Factory<CurrentLoggedInUserInfoProvider> {
    self { CurrentLoggedInUserInfoProvider }.scope(.session)
  } 
}

class ApplicationCoordinator {
  @Injected(\.currentLoggedInUserInfoProvider) var currentLoggedInUserInfoProvider

  func logout() {
    print(currentLoggedInUserInfoProvider) // instance #1
    Container.shared.manager.reset(scope: .session)
    let resolution = Container.shared.currentLoggedInUserInfoProvider.resolve() // new instance #2
    print(currentLoggedInUserInfoProvider) // still instance #1? 
  }
}

i'd like applicationCoordinator.currentLoggedInUserInfoProvider to resolve to the new instance #2, but it looks like it's still holding onto the instance that was resolved at ApplicationCoordinator.init() time. is that correct?

assuming that is the case, i'd like it to point to a new instance, is that possible without adding something like self.currentLoggedInUserInfoProvider = Container.shared.currentLoggedInUserInfoProvider.resolve() right after i run Container.shared.manager.reset(scope: .session)?

hmlongco commented 8 months ago

Look at $currentLoggedInUserInfoProvider.resolve(reset: .scope)

AndrewSB commented 8 months ago

that looks like it allows me to force a clearing of the cache at resolution time. i think i failed to explain my example

    print(currentLoggedInUserInfoProvider) // instance #1
    Container.shared.manager.reset(scope: .session)

    // i don't want to do a new resolution, not every object knows that the scope is being reset. it would be less-than-ideal if every object that's using an `@Injected` value needs to pay attention to the container being reset, and re-resolve then
    let resolution = Container.shared.currentLoggedInUserInfoProvider.resolve() // new instance #2

    // i'd like to, instead, be able to continue using `self.currentLoggedInUserInfoProvider`, and have something dynamic happen at reset time where the wrapped value from the property wrapper knows that it's old instance should be released
    print(currentLoggedInUserInfoProvider) // still instance #1? 
hmlongco commented 8 months ago

You're better off with something like....

class AuthenticatedUser {
    var currentUser = CurrentValueSubject<User?, Never>(nil)
    func logout() {
        currentUser.send(nil)
   }
}

That way everyone can see when a user is logged in, logged out, and get notified of changes when they occur.

It's not the job of the dependency injection system to track every existing resolution and attempt to re-resolve it.

Further some like InjectedObject are effectively immutable.