Consider the below Observable Object.
class User: ObservableObject {
@Published var age: Int
@Published var name: String {
didSet {
objectWillChange.send()
}
}
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
The below code prints blank value or Void block. Any reason why? If we change Integer value age it should simply print that value.
let userJohnCancellable = userJohn.objectWillChange.sink { val in
print("Changed Value \(val)")
}
userJohn.age = 21
userJohn.age = 39
We can try to print the values in the closure using userJohn.age
. But why does val not return a Integer value in this case.
Also what would be the best way to handle sink
changes for age and name, both, one is String other is Int.
CodePudding user response:
When you look in the documentation for ObservableObject
you will find thatobjectWillChange
is ObservableObjectPublisher
/// A publisher that emits before the object has changed.
public var objectWillChange: ObservableObjectPublisher { get }
which in turn is defined as having an output of type Void
:
final public class ObservableObjectPublisher : Publisher {
/// The kind of values published by this publisher.
public typealias Output = Void
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Never
}
There is no need to send objectWillChange
from didSet
- each time any of the @Published
values changes objectWillChange
will emit a value.
If you want to get notified when a particular property marked as @Published
changes and receive the new value you have to subscribe to that particular property:
let userJohn = User(name: "Johnny", age: 17)
let objectWillChangeCancellable = userJohn
.objectWillChange
.sink {
print("object will change")
}
let ageCancellable = userJohn
.$age
.sink { age in
print("new value of age is \(age)")
}
let nameCancellable = userJohn
.$name
.sink { name in
print("new value of name is \(name)")
}
This will get printed:
new value of age is 17
new value of name is Johnny
if you add:
userJohn.name = "John"
you will see the following printed:
object will change
new value of name is John
if you add:
userJohn.age = 21
you will see the following printed:
object will change
new value of age is 21
CodePudding user response:
You seem to be confused about ObservableObject. It is for use with SwiftUI. But your code is not SwiftUI code, so you don't need ObservableObject and you really can't use it in any meaningful way. If the goal is to be able to subscribe to the properties of User so as to be notified when one of them changes, then it suffices to make those properties Published:
class User {
@Published var age: Int
@Published var name: String
init(age: Int, name: String) {
self.age = age; self.name = name
}
}
Here's an example of using it; I will assume we have a user
property of a UIViewController:
class ViewController: UIViewController {
var cancellables = Set<AnyCancellable>()
var user = User(age: 20, name: "Bill")
override func viewDidLoad() {
super.viewDidLoad()
user.$age.sink {print("age:", $0)}.store(in: &cancellables)
user.$name.sink {print("name:", $0)}.store(in: &cancellables)
}
}
If this view controller's user
has its age
or name
changed, you'll see the print
output in the console.
If the question is how to handle both changes in a single pipeline, they have different types, as you observe, so you'd need to define a union so that both types can come down the same pipeline:
class User {
@Published var age: Int
@Published var name: String
enum Union {
case age(Int)
case name(String)
}
var unionPublisher: AnyPublisher<Union, Never>?
init(age: Int, name: String) {
self.age = age; self.name = name
let ageToUnion = $age.map { Union.age($0) }
let nameToUnion = $name.map { Union.name($0) }
unionPublisher = ageToUnion.merge(with: nameToUnion).eraseToAnyPublisher()
}
}
And again, here's an example of using it:
class ViewController: UIViewController {
var cancellables = Set<AnyCancellable>()
var user = User(age: 20, name: "Bill")
override func viewDidLoad() {
super.viewDidLoad()
user.unionPublisher?.sink { union in
switch union {
case .age(let age): print ("age", age)
case .name(let name): print ("name", name)
}
}.store(in: &cancellables)
}
}
Again, change the user
property's name
or age
and you'll get an appropriate message in the console.