
13 March 2021
Dependency Injection for Property Wrappers
My past sprint was nothing special. I had to implement an onboarding flow with saving a completion flag so that a user can see this for the first time only. Sounds like a typical work for iOS developers for saving something in UserDefaults, doesn’t it? So, I prepared a special class with the most popular (IMHO) property wrapper inside:
If you haven’t heard about Property Wrappers so far, I recommend reading this article first.
@propertyWrapper
struct UserDefault<Value> {
private let key: String
private let defaultValue: Value
private let storage: UserDefaults
var wrappedValue: Value {
get {
return storage.object(forKey: key) as? Value ?? defaultValue
}
set {
storage.set(newValue, forKey: key)
}
}
init(defaultValue: Value, key: String, storage: UserDefaults = .standard) {
self.defaultValue = defaultValue
self.key = key
self.storage = storage
}
}
class Onboarding {
@UserDefault(defaultValue: false, key: "isOnboardingCompleted")
var isOnboardingCompleted: Bool
}
Done, looks as simple as possible. The second step is to cover this code by tests. In order to run tests safely - to have stable tests, I decided to pass different domains of User Defaults. So the first code I wrote was the following one:
class Onboarding {
@UserDefault(defaultValue: false, key: "isOnboardingCompleted", storage: storage)
var isOnboardingCompleted: Bool
private let storage: UserDefaults
init(storage: UserDefaults) {
self.storage = storage
}
}
Obviously it didn’t work. The red error was flashing to my eyes:
Cannot use instance member 'storage' within property initializer; property initializers run before 'self' is available
After that I used to try a few approaches to solve this issue without luck until I remembered one important thing: using an underscore within a class allowing to access a property wrapper class itself rather than a general property.
The final solution:
class Onboarding {
@UserDefault
var isOnboardingCompleted: Bool
init(storage: UserDefaults) {
_isOnboardingCompleted = UserDefault(defaultValue: false, key: "isOnboardingCompleted", storage: storage)
}
}
This article doesn’t look like a long step-by-step tutorial, it’s just a quick tip for a problem I faced recently. So I decided to share a non-obvious solution for me, maybe it will save some time for someone. Let me know if the article was useful to you :)
© 2024 Nikita Ermolenko. Some rights reserved.