ReactiveSwift mutable property with read only public access - swift

I have a class with enum property state. This property's value (by value I mean ReactiveSwift.Property's value) needs to be accessed and observed by other classes, but value change should be private. Currently it is implemented in a such way:
enum State {
case stopped, running, paused
}
var state: Property<State> {
return Property(mutableState)
}
fileprivate let mutableState = MutableProperty<State>(.stopped)
This pattern allows me to modify mutableState property within class file. At the same time outside the class state is available only for reading and observing.
The question is whether there is a way to implement a similar thing using single property? Also probably someone can suggest a better pattern for the same solution?

I can't think of any way to do this with a single property. The only adjustment I would make to your code is to make state a stored property rather than a computed property that gets newly created each time it is accessed:
class Foo {
let state: Property<State>
fileprivate let mutableState = MutableProperty<State>(.stopped)
init() {
state = Property(mutableState)
}
}

Depending where you want to mutate the state, you can try doing either of:
private(set) var state: Property<State>
or if you modify it from an extension but still the same file
fileprivate(set) var state: Property<State>

Related

Is it possible to initialize properties at the beginning of a class?

I am writing my project and wondered.
When I read literature or watch videos, I see that this is bad practice. Why? Is this bad for the system?
What is the difference between this
class SomeClass {
var someView = SomeView()
var someViewModel = SomeViewModel()
// ...
}
and this
class SomeClass {
var someView: SomeView!
var someViewModel: SomeViewModel?
// ...
}
How to get used to it better?
You have to initialize all instance properties somehow. And you have to do it right up front, either in the declaration line or in your init method.
But what if you don't actually have the initial value until later, like in viewDidLoad? Then it is silly to supply a real heavyweight value only to replace it later:
var v = MyView()
override func viewDidLoad() {
self.v = // get _real_ MyView and assign it in place of that
}
Instead, we use an Optional to mark the fact that we have no value yet; until we obtain and assign one, it will be nil:
var v : MyView? // means it is initially `nil`
override func viewDidLoad() {
self.v = // get _real_ MyView and assign it to our property
}
There's nothing wrong with the first way (which is called a "default property value", by the way), and in fact, often times it's preferable. But of course, the devil is in the details:
How would the initialization of a SomeViewModel work? Without acess the initializer parameters of SomeClass, you're stuck with only being able to construct an instance from a parameter-less init, like SomeViewModel(). What exactly could that do? Suppose it was a person view model, and you had PersonViewModel(). What person? Whats their name? What will this default value do at all?
It's not a great pattern if it requires overwriting the default value with some other value in the initializer
It initializes the value up-front, where sometimes a lazy or computed value might be more appropriate.

Do all #Published variables need to have an initial value in a view model for SwiftUI?

Do all #Published variables need to have an initial value in a view model (I am conforming to MVVM) for SwiftUI?
Why can't I just say that I want the #Published variable of type string with no initial value?
So does that mean that I need to have:
If not how can I get around this?
I was thinking about making an init() for the class, but I would still need to input default values when I initialize the class.
Unlike SwiftUI views, which are Structs, Observable Objects are always classes, and in Swift, all classes must have initializers.
A) Consider making your #Published variable an optional
#Published var title: String?
B) Add an init method
init() { self.title = "" }
Else, there's way to not have an initial value for a class' property.
You may find that force unwrapping with "!" will "solve" your problem, but that's a bad practice, don't do it; if you don't have an initial value for your variable, then it must be optional in your case.
But why are you designing a Model, as an observable object, for SwiftUI, consider using simple Structs if you are not intending on persisting (saving to disk), your data, else use Core Data and it's NSManagedObject class, that is already conforming to ObservableObject.
All stored properties should be initialised somehow. You can delay initialization till construction, like
final class ViewModel: ObservableObject {
#Published var title: String
init(title: String) {
self.title = title
}
}
and later, somewhere in SwiftUI View, create it with value needed in context
ViewModel(title: "some value")

Why does EnvironmentKey have a defaultValue property?

Here's the definition of EnvironmentKey:
public protocol EnvironmentKey {
associatedtype Value
static var defaultValue: Self.Value { get }
}
I'm confused as to the purpose of the defaultValue property. Or perhaps I'm confused about the intended use of #Environment. Let me explain...
Based on my testing, I know that the defaultValue is being accessed. If you, for example, define the defaultValue as a getter:
protocol MyInjector: EnvironmentKey {
static var defaultValue: Foo {
print("get default value!")
return Foo()
}
}
you'll find that SwiftUI frequently calls this property (because of this, in practice I define my defaultValue as static let).
When you add a breakpoint on the print statement and move down the stack it stops on the arrow below:
extension EnvironmentValues {
var myInjector: Foo {
get { self[MyInjector.self] } <-----------------
set { self[MyInjector.self] = newValue }
}
}
Further down in the stack is the line in my component's init where I access the following variable:
#Environment(\.myInjector) var foo: Foo
That is, it seems to access defaultValue any time a SwiftUI view with an #Environment variable is re-rendered.
I think that the internal implementation of #Environment needs to set this default value before it determines the actual value as a way to avoid making the variable declaration optional.
Finally, I also experimented with adding #Environment to an ObservableObject in the hopes that it would have access to the same "environment" as the SwiftUI view tree.
class Bar: ObservableObject {
#Environmenet(\.myInjector var foo: Foo
}
Unfortunately, that was not the case and the instance that Bar received was different than the instance I had injected via View.environment(\.myInjector, Foo()).
I also found, btw that I could use #Environment to inject a global singleton by using the shared variable pattern:
protocol MyInjector: EnvironmentKey {
static let defaultValue = Foo()
}
and the same instance was available to SwiftUI views and any other class, but I'm not sure how that could be useful in any way.
So what is the purpose of defaultValue? Is it simply, as I suspect, so that the internal implementation can assign an arbitrary value to the variable before the true value is determined, or is there something else going on?
IMO there are two reasons to have defaultValue in this pattern (and I assume this was the intention):
1) to specify corresponding value type
2) to make environment value always valid (in usage of #Environment)
In SwiftUI: Set Status Bar Color For a Specific View post you can find example of usage of this defaultValue feature to easily connect via environment different parts of application.

Is there an alternative to Combine's #Published that signals a value change after it has taken place instead of before?

I would like to use Combine's #Published attribute to respond to changes in a property, but it seems that it signals before the change to the property has taken place, like a willSet observer. The following code:
import Combine
class A {
#Published var foo = false
}
let a = A()
let fooSink = a.$foo.dropFirst().sink { _ in // `dropFirst()` is to ignore the initial value
print("foo is now \(a.foo)")
}
a.foo = true
outputs:
foo is now false
I'd like the sink to run after the property has changed like a didSet observer so that foo would be true at that point. Is there an alternative publisher that signals then, or a way of making #Published work like that?
There is a thread on the Swift forums for this issue. Reasons of why they made the decision to fire signals on "willSet" and not "didSet" explained by Tony_Parker
We (and SwiftUI) chose willChange because it has some advantages over
didChange:
It enables snapshotting the state of the object (since you
have access to both the old and new value, via the current value of
the property and the value you receive). This is important for
SwiftUI's performance, but has other applications.
"will" notifications are easier to coalesce at a low level, because you can
skip further notifications until some other event (e.g., a run loop
spin). Combine makes this coalescing straightforward with operators
like removeDuplicates, although I do think we need a few more grouping
operators to help with things like run loop integration.
It's easier to make the mistake of getting a half-modified object with did,
because one change is finished but another may not be done yet.
I do not intuitively understand that I'm getting willSend event instead of didSet, when I receive a value. It does not seem like a convenient solution for me. For example, what do you do, when in ViewController you receiving a "new items event" from ViewModel, and should reload your table/collection? In table view's numberOfRowsInSection and cellForRowAt methods you can't access new items with self.viewModel.item[x] because it's not set yet. In this case, you have to create a redundant state variable just for the caching of the new values within receiveValue: block.
Maybe it's good for SwiftUI inner mechanisms, but IMHO, not so obvious and convenient for other usecases.
User clayellis in the thread above proposed solution which I'm using:
Publisher+didSet.swift
extension Published.Publisher {
var didSet: AnyPublisher<Value, Never> {
self.receive(on: RunLoop.main).eraseToAnyPublisher()
}
}
Now I can use it like this and get didSet value:
self.viewModel.$items.didSet.sink { [weak self] (models) in
self?.updateData()
}.store(in: &self.subscriptions)
I'm not sure if it is stable for future Combine updates, though.
UPD: Worth to mention that it can possibly cause bugs (races) if you set value from a different thread than the main.
Original topic link: https://forums.swift.org/t/is-this-a-bug-in-published/31292/37?page=2
You can write your own custom property wrapper:
import Combine
#propertyWrapper
class DidSet<Value> {
private var val: Value
private let subject: CurrentValueSubject<Value, Never>
init(wrappedValue value: Value) {
val = value
subject = CurrentValueSubject(value)
wrappedValue = value
}
var wrappedValue: Value {
set {
val = newValue
subject.send(val)
}
get { val }
}
public var projectedValue: CurrentValueSubject<Value, Never> {
get { subject }
}
}
Further to Eluss's good explanation, I'll add some code that works. You need to create your own PassthroughSubject to make a publisher, and use the property observer didSet to send changes after the change has taken place.
import Combine
class A {
public var fooDidChange = PassthroughSubject<Void, Never>()
var foo = false { didSet { fooDidChange.send() } }
}
let a = A()
let fooSink = a.fooDidChange.sink { _ in
print("foo is now \(a.foo)")
}
a.foo = true
Before the introduction of ObservableObject SwiftUI used to work the way that you specify - it would notify you after the change has been made. The change to willChange was made intentionally and is probably caused by some optimizations, so using ObservableObjsect with #Published will always notify you before the changed by design. Of course you could decide not to use the #Published property wrapper and implement the notifications yourself in a didChange callback and send them via objectWillChange property, but this would be against the convention and might cause issues with updating views. (https://developer.apple.com/documentation/combine/observableobject/3362556-objectwillchange) and it's done automatically when used with #Published.
If you need the sink for something else than ui updates, then I would implement another publisher and not go agains the ObservableObject convention.
Another alternative is to just use a CurrentValueSubject instead of a member variable with the #Published attribute. So for example, the following:
#Published public var foo: Int = 10
would become:
public let foo: CurrentValueSubject<Int, Never> = CurrentValueSubject(10)
This obviously has some disadvantages, not least of which is that you need to access the value as object.foo.value instead of just object.foo. It does give you the behavior you're looking for, however.

Swift property observers, initial value

The Apple documentation states:
The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.
which means if I have some type:
enum State {
case disabled, enabled
}
and some variable that has a willSet or didSet observer:
var state: State = .disabled {
willSet {
// do something
}
}
the willSet observer won't get called until after I explicitly set state in during or after initialization of that particular instance.
Why does it work this way? As a developer, I would look at the above code and make the assumption, not unreasonably, that the observer block gets called for the original value, irrespective of instance initialization. It seems like one heck of an anti-pattern to have to set state = .disabled in the initializer to trigger the observer for the initial value.
As Hamish's comment points out, in the case of willSet there's not a valid value that state could have here (and in the case of didSet, there's not a valid value the newValue argument could have).
There's no restriction on whether willSet/didSet can access other properties of the instance. For that reason, all the instance properties need to be properly initialised before any observers are called.
On top of that, if the observer didSet was called when first setting a property's value, the oldValue variable would contain garbage, as it would never have been set.
The way properties are handled in Swift is roughly analogous to the recommended behavior for initialization in Objective-C, notably the section "Don't Use Accessors in Initializer Methods and Dealloc" found on this page. If you set a property foo in your init method, it's equivalent to setting the _foo instance variable in Objective-C, whereas setting foo outside of init is analogous to calling foo's accessors. Basically, what used to be considered a best practice is now actually enforced by the compiler.
The reason for this is to avoid aberrant side effects caused by accessors assuming that the rest of the object's state is set up already, when in actuality it is not.
This can be worked around fairly easily, though; you can make a fooDidSet() method, call that from within foo's didSet, and then also call it at the end of your initializer after calling super's designated init. Alternatively, you can just set the property to itself after calling super's init to cause its didSet to fire.
Think what would happen in a situation like this:
class Person {
var firstName: String {
didSet {
print("New full name:", firstName, lastName)
}
}
var lastName: String {
didSet {
print("New full name:", firstName, lastName)
}
}
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
You'd end up using an uninitalized value for lastName. Which can could very well crash the app.
Swift wants to ensure object integrity, and executing observers from init can't guarantee this, as observers have access to all class members.