Difference between CurrentValueSubject and #Published - ios

So I'm digging into combine and this question came up.
Is there any real difference between using CurrentValueSubject (and setting its value using currentValueSubject.value) or using a #Published var and accessing its publisher with a $? I mean I know one returns a Subject instead of a Publisher, but the only real difference I could find is that CurrentValueSubject is way more useful because you can declare it on a protocol.
I really don't understand how #Published can be useful if we can just use PassthroughSubject, am I missing something here?
Mind you, this is using UIKit, it may have other uses for SwiftUI.
Thank you.

CurrentValueSubject is a value, a publisher and a subscriber all in one.
Sadly it doesn’t fire objectWillChange.send() when used inside an ObservableObject.
You can specify an error type.
#Published is a property wrapper, thus:
It is not yet supported in top-level code.
It is not supported in a protocol declaration.
It can only be used within a class.
#Published automatically fires objectWillChange.send() when used inside an ObservableObject.
Xcode will emit a warning if your try to publish to #Published wrapped property from a background queue. Probably because objectWillChange.send() must be called from the main thread.
The error type of its publisher is Never
My biggest beef against #Published is that it can’t behave as a subscriber and setting up Combine pipelines requires additional plumbing compared to a Current Value Subject.
We can declare a #Published property inside a protocol. Not very pretty...
protocol TestProtocol {
var isEnabled: Bool { get }
var isEnabledPublished: Published<Bool> { get }
var isEnabledPublisher: Published<Bool>.Publisher { get }
}
class Test: ObservableObject, TestProtocol {
#Published var isEnabled: Bool = false
var isEnabledPublished: Published<Bool> { _isEnabled }
var isEnabledPublisher: Published<Bool>.Publisher { $isEnabled }
}

#Published is just a quick way to use CurrentValueSubject a little neater. When I debug one of my apps and look at the type returned by $paramName , it's actually just a CurrentValueSubject:
po self.$books
▿ Publisher
▿ subject : <CurrentValueSubject<Array<Book>, Never>: 0x6000034b8910>
I guess one benefit of using CurrentValueSubject instead of #Published may be to allow you to use the error type?
Note: Despite being a CurrentValueSubject right now I'd never rely on that assumption.

I found myself coming back to this post so felt I'd add some extra insight in to the difference between #Published and CurrentValueSubject.
One main difference can be found in the documentation for #Published:
When the property changes, publishing occurs in the property’s willSet block, meaning subscribers receive the new value before it’s actually set on the property.
Additionally, conversation on the Swift Forums note that #Published is intended for use with SwiftUI.
With regards to #Published publishing in the willSet block of it's property, consider the following example:
class PublishedModel {
#Published var number: Int = 0
}
let pModel = PublishedModel()
pModel.$number.sink { number in
print("Closure: \(number)")
print("Object: \(pModel.number) [read via closure]")
}
pModel.number = 1
print("Object: \(pModel.number) [read after assignment]")
This produces the following output:
Closure: 0
Object: 0 [read via closure]
Closure: 1
Object: 0 [read via closure]
Object: 1 [read after assignment]
Contrast this with another example where we keep everything the same, except replacing #Published with CurrentValueSubject:
class CurrentValueSubjectModel {
var number: CurrentValueSubject<Int, Never> = .init(0)
}
let cvsModel = CurrentValueSubjectModel()
cvsModel.number.sink { number in
print("Closure: \(number)")
print("Object: \(cvsModel.number.value) [read via closure]")
}
cvsModel.number.send(1)
print("Object: \(cvsModel.number.value) [read after assignment]")
Output:
Closure: 0
Object: 0 [read via closure]
Closure: 1
Object: 1 [read via closure] // <— Here is the difference
Object: 1 [read after assignment]
After updating number to 1, reading the object's CurrentValueSubject's value property within the closure prints the new value instead of the old value as with #Published.
In summary, use #Published within your ObservableObjects for your SwiftUI views. If you're looking to create some sort of model object with an instance property that holds a current value and also publishes it's changes after they are set, use CurrentValueSubject.

One advantage on #Published is that it can act as a private-mutable, public-immutable CurrrentValueSubject.
Compare:
#Published private(set) var text = "someText"
with:
let text = CurrentValueSubject<String, Never>("someText")
When designing APIs you often want to allow clients to read the current value and subscribe to updates but prevent them from setting values directly.

There is one limitation when using #Published.
You can only use #Published on properties of a Class whereas CurrentValueSubject can be used for struct as well

Related

Updating a #State var in SwiftUI from an async method doesn't work

I have the following View:
struct ContentView: View {
#State var data = [SomeClass]()
var body: some View {
List(data, id: \.self) { item in
Text(item.someText)
}
}
func fetchDataSync() {
Task.detached {
await fetchData()
}
}
#MainActor
func fetchData() async {
let data = await SomeService.getAll()
self.data = data
print(data.first?.someProperty)
// > Optional(115)
print(self.data.first?.someProperty)
// > Optional(101)
}
}
now the method fetchDataSync is a delegate that gets called in a sync context whenever there is new data. I've noticed that the views don't change so I've added the printouts. You can see the printed values, which differ. How is this possible? I'm in a MainActor, and I even tried detaching the task. Didn't help. Is this a bug?
It should be mentioned that the objects returned by getAll are created inside that method and not given to any other part of the code. Since they are class objects, the value might be changed from elsewhere, but if so both references should still be the same and not produce different output.
My theory is that for some reason the state just stays unchanged. Am I doing something wrong?
Okay, wow, luckily I ran into the Duplicate keys of type SomeClass were found in a Dictionary crash. That lead me to realize that SwiftUI is doing some fancy diffing stuff, and using the == operator of my class.
The operator wasn't used for actual equality in my code, but rather for just comparing a single field that I used in a NavigationStack. Lesson learned. Don't ever implement == if it doesn't signify true equality or you might run into really odd bugs later.

Is there any way to optimize struct copy in Swift?

I'm developing an iOS app and have the following data model:
struct Student {
var name: String?
var age: UInt?
var hobbies: String?
...
}
This model is used as the data source in one view controller, where each property value will be filled in an UITextfield instance so that the user can edit a student's information. Every time a user finishes typing an item, e.g. the name, the new value will override the old model's corresponding property.
The problem is, since struct is a value type instead of a reference type, a new model instance is generated every time I assign a new property value to it. There may be above 20 properties in my model and I think so many copies are quite a waste. For some reasons I'm not allowed to use class. Is there any way to optimize this? Will these copies cause any performance issues?
you can create a func with mutating keyword like below
struct Point {
var x = 0.0
mutating func add(_ t: Double){
x += t
}
}
find more here

SwiftUI Change Observed Object From Another View

I have an observed object in my ContentView which contains tabviews.
class GlobalValue: ObservableObject {
#Published var stringValue = ""
#Published var intValue = 0
}
struct ContentView: View {
#ObservedObject var globalValue = GlobalValue()
}
From one of the tabs I need to change ContentView's observed object value.
ContentView().globalValue.intValue=25
It is not changing the value. How can I change that observed object value? thanks...
It is changing the value. The problem is you are instantiating an instance of GlobalValue locally in your struct. It has no life outside of the struct. I you want to use an Observable Object as a global store like that, you need to create it once and use that instance.
The easiest way to do this is to add
static let shared = GlobalValue() in your class, and in your struct use globalValue = GlobalValue.shared which essentially gives you a singleton. You will have one instance that all the views can read and write to.

Constrained protocol still complaining as being generic [duplicate]

I've got the following snippet:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
var test: [MyProtocol] = []
Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Why doesn't this work? Shouldn't the where ID == UUID remove the ambiguity the error is concerned with? Am I missing something here?
I think this question is similar to this one: Usage of protocols as array types and function parameters in swift
However, I would have assumed that adding where ID == UUID should fix the problem? Why is that not the case?
Thanks!
Edit
So, this problem has occurred while experimenting with SwiftUI and struct data models. I've always used classes for any kind of data model but it seems like SwiftUI wants to get you to use structs as often as possible (I still don't see how that's realistically possible but that's why I'm experimenting with it).
In this particular case, I tried to have a manager that contains structs that all conform to MyProtocol. For example:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
struct A: MyProtocol { // First data model
var id: UUID = UUID()
}
struct B: MyProtocol { // Second data model
var id: UUID = UUID()
}
class DataManager: ObservableObject {
var myData: [MyProtocol]
}
...
I don't actually have to declare Identifiable on MyProtocol but I thought it would be nicer and cleaner.
Because this is not a current feature of Swift. Once there is an associated type, there is always an associated type. It doesn't go away just because you constrain it. And once it has an associated type, it is not concrete.
There is no way to "inherit" protocols this way. What you mean is:
protocol MyProtocol {
var id: UUID { get }
}
And then you can attach Identifiable to structs that require it:
struct X: MyProtocol, Identifiable {
var id: UUID
}
(note that no where clause is required.)
There is no Swift feature today that allows you to say "types that conform to X implicitly conform to Y." There is also no Swift feature today that allows for an Array of "things that conform to Identifiable with ID==UUID." (That's called a generalized existential, and it's not currently available.)
Most likely you should go back to your calling code and explore why you require this. If you post the code that iterates over test and specifically requires the Identifiable conformance, then we may be able to help you find a design that doesn't require that.

how to observer variable value changes over time in xcode

I'm currently learning ios developement using swift and I was just wondering if there is a way in xcode to
1) set a breakpoint on a variable whenever the value of the variable changes
OR
2) somehow track the change to variable value over time
Method with Swift didSet and willSet
You can use the print in the console:
class Observable {
static var someProperty: String? {
willSet {
print("Some property will be set.")
}
didSet {
print("Some property has been set.")
}
}
}
Method with watchpoints
Watchpoints are a tool you can use to monitor the value of a variable
or memory address for changes and trigger a pause in the debugger when
changes happen. They can be very helpful in identifying problems with
the state of your program that you might not know precisely how to
track down.
You can find a great guide here
I think you should learn about willSet and didSet concepts.
Swift has a simple and classy solution called property observers, and it lets you execute code whenever a property has changed. To make them work, you need to declare your data type explicitly, then use either didSet to execute code when a property has just been set, or willSet to execute code before a property has been set.
Update the value whenever the value was changed. So, change property to this:
var score: Int = 0 {
didSet {
scoreLabel.text = "Score: \(score)"
}
}
There is already a good question and answers which expalin this concept.
What is the purpose of willSet and didSet in Swift?

Resources