-
I have a dependency that should receive input parameters on specific events, and only then operate using these input parameters. I think of it as a dependency with a state, but it just needs input parameters to operate. Consider this example:enum ScreenSource { case auth, home }
struct ServiceX {
let screenSource: ScreenSource
init(screenSource: ScreenSource) { self.screenSource = screenSource }
var doWork(prompt: String) {
switch screenSource // ...
}
} Input here is QuestionThe question is how stateful services operate with Swift Dependencies? Like the one I added above. Let's also consider this is used in TCA, and the I know possible solutions, but I would love to hear your opinion on this and choose wisely. Option 1.The service wouldn't have state, and the input should be passed as a parameter to the method. struct ServiceX {
var doWork(prompt: String, screenSource: ScreenSource) {
switch screenSource // ...
}
} That way we have to pass the screenSource inside the call site. This path could be long:
That would add one pass of It might be a positive thing, because then we can see all the dependencies of the method. Option 2.The service could have state, and the state should be passed in the init. But we can avoid using struct ServiceX {
let screenSource: ScreenSource
init(screenSource: ScreenSource) { self.screenSource = screenSource }
var doWork(prompt: String) {
switch screenSource // ...
}
} Then we'll pass this service into struct Feature {
let serviceX: ServiceX
init(serviceX: ServiceX) { self.serviceX = serviceX }
} That way we won't know about ServiceX parameter that Feature doesn't need to know about. Consider this feature some generic inner feature that doesn't know about the source where it opened. We would have to somehow create unimplemented/testValue version of ServiceX, and here Option 3.The service is a struct ServiceX {
let screenSource: ScreenSource
init(screenSource: ScreenSource) { self.screenSource = screenSource }
var doWork(prompt: String) {
switch screenSource // ...
}
}
// later in auth screen
withDependencies {
$0.serviceX = ServiceX(screenSource: .auth)
} operation: {
Feature()
} I want to avoid that option, because it could lead to unexpected behaviour when someone forgets to provide a proper setup for a dependency. Also it raises a question what is the live version then?. Probably we could only conform to My first thought was to go with Option 1 and I actually think all parameters that affect a method would be the best option here. The service would signal to everyone about every dependency it needs to operate. I would love to hear your opinion on this. I'm not sure which way to go, and I'm not sure if I'm missing something. I would love to hear your thoughts on this. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
If different places in your app require different values of ScreenSource you actually can create a dependency of type not just Then you can provide it in runtime like
Also, your use case looks a lot like onboarding in isowords, so I recommend checking it out. It is most similar to your third option |
Beta Was this translation helpful? Give feedback.
-
Good question! Both options have their pros and cons. Personally, I lean towards Option 1 because I greatly value the transparency it provides. Being able to clearly see what the operation depends on makes the code easier to understand and maintain. As long as the number of parameters remains manageable, Option 1 strikes the right balance between simplicity and clarity. |
Beta Was this translation helpful? Give feedback.
If different places in your app require different values of ScreenSource you actually can create a dependency of type not just
ServiceX
, but(ScreenSource) -> ServiceX
Then you can provide it in runtime like
Also, your use case looks a lot like onboarding in isowords, so I recommend checking it out. It is most similar to your third option