How should we initialize stored properties in Structs and Classes? - swift

Apple's documentation states:
Classes and structures must set all of their stored properties to an
appropriate initial value by the time an instance of that class or
structure is created. Stored properties cannot be left in an
indeterminate state.
I have also seen this question, but still wasn't able to find an answer.
// struct
struct FlickrObj { // No Error ????
var title : String
var photographer : String
}
// class
class FlickrObj { // will create error: Class 'FlickrObj' has no initializers
var title : String
var photographer : String
}
Apple says both classes and structs must set their stored properties but why doesn't the Struct give any compile time errors?‌

It's because, in the absence of any explicitly declared initializers, the struct itself declares the implicit memberwise initializer init(title:photographer:).
But classes don't do that.
So the class is failing to fulfill the initialization contract (all instance properties must be initialized before the instance itself finishes initializing), but the struct is not failing the contract.

Related

swift how to update viewModel parameter while init

I have encountered the problem "Cannot assign to property: 'self' is an immutable capture". How can i call API to check status and update "getUpdateSuccess" parameter
struct HomepageViewModel {
var getUpdateSuccess: Bool = false
init() {
getStatusUpdated.execute().done {
[self] isUpdated in
// Cannot assign to property: 'self' is an immutable capture
self.getUpdateSuccess = isUpdated
}
}
}
If you want to “reference” self later (e.g., update the same instance later), self should be a reference type, not a value type.
So you can solve this by making the view model a class (a reference type) rather than a struct (a value type).
“Model” objects are good candidates for value types, but “view models” are generally reference types. See Value and Reference Types for a discussion of when you might use a value type and when you might use a reference type. For a general discussion, see The Swift Programming Language: Structures and Classes.
By the way, the closure’s capture list syntax (the stuff in between the [ and the ]) has different practical implications if the captured variable is a reference type vs when it is a value type.
With a value type, the capture list makes sure you have a copy of the value:
struct Person {…}
var person = Person(…)
foo { [person] in
// this code will be dealing with a safe copy of the original `Person`
}
// maybe you’ll mutate the original `Person` instance here,
// but the above closure will have a copy of the original one
Contrast that with a reference type, where the capture list makes sure you have a strong reference to the reference type:
class ViewModel {
func bar() {
baz { [self] in
// this has a strong reference to this view model instance, not a copy of the instance
}
}
}
let viewModel = ViewModel(…)
viewModel.bar() // any updates that bar does asynchronously, will update this instance of the view model, not a copy of it
As an aside, with reference types, you will often see a capture list with the weak keyword, e.g., [weak self], if you want a “weak” reference to the class rather than a strong reference. This is common if you need to prevent a “strong reference cycle” (a topic beyond the scope of this conversation). This is discussed in The Swift Programming Language: Strong Reference Cycles for Closures.
But, whether a strong reference or weak reference, you are not dealing with a copy of the object, but rather a “reference” to the same original instance.

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")

Cannot assign to property: 'self' is immutable, I know how to fix but needs understanding

I have a struct :
public struct MyStruct {
public var myInt: Int = 0
...
}
I have a extension of MyStruct:
extension MyStruct {
public func updateValue(newValue: Int) {
// ERROR: Cannot assigned to property: 'self' is immutable
self.MyInt = newValue
}
}
I got the error showing above, I know I can fix the error by several ways, e.g. add a mutating keyword before func.
I am here not asking how to fix the error, but ask why swift doesn't allow this kind of value assignment ? I need an explanation besides a fix.
struct is a value type. For value types, only methods explicitly marked as mutating can modify the properties of self, so this is not possible within a computed property.
If you change struct to be a class then your code compiles without problems.
Structs are value types which means they are copied when they are passed around.So if you change a copy you are changing only that copy, not the original and not any other copies which might be around.If your struct is immutable then all automatic copies resulting from being passed by value will be the same.If you want to change it you have to consciously do it by creating a new instance of the struct with the modified data. (not a copy)
A tricky one: mark it as #State
Because a Struct is a value type, and therefore should be immutable

Using switch to assign an instance variable

I'd be grateful for any help - been racking my brains for days and I can't see why this isn't working.
Essentially, I have a main view controller which will be controlled by different classes depending on which game the user selects
'classic'
'unlimited'
'timed'
When the user button is pushed, it needs to flick through the options and assign an instance of the class to a variable 'brain'.
this is what I have:
var brain = GuessMeComparer()
func switcher (random:String) {
switch random {
case "Classic": self.brain = ClassicBrain()
case "unlimited": self.brain = GuessMeComparer()
case "timed": self.brain = TimedBrain()
default:break
}
}
I get the error 'cannot assign a value of type 'ClassicBrain' to a value of type 'GuessMeComparer'.
All I can think of is that you cannot assign instance variables using switch?
Any help would be great, cheers!
Using AnyObject will work but – as vadian is saying – will force you to cast to a specific type later. A better option will be abstract a common interface for all the brain classes in a swift protocol, e.g.:
protocol BrainProtocol {
// common interface here
...
}
class /* or struct */ ClassicBrain : BrainProtocol {
// classic implementation here
...
}
class /* or struct */ TimedBrain : BrainProtocol {
// timed implementation here
...
}
...
var brain : BrainProtocol
Swift is a strong type language, the variable brain is declared as type GuessMeComparer.
Once declared you cannot change the type.
To consider different types, declare the variable explicitly as generic type AnyObject.
var brain : AnyObject = GuessMeComparer()
Now you can assign different types to the variable, but in many cases you have to cast the variable to a specific type later in the code.

Class or struct for hierarchy model?

I understand difference between class and struct in Swift. Now I'm wondering what to use for hierarchy model.
To define a class is pretty simple (setting connections on properties set is now irrelevant).
class XYClass {
var title: String
var subinstances: [XYClass]
weak var superinstance: XYClass?
}
But it looks like pretty fine model for struct. Especially if I need to instantiate a lots of these and frequently. But I'm wondering if I can somehow safely point to superinstance or I need to store whole object graph to every instance on every change... Should I use class or struct and if struct, how to define it?
You are making a linked list. If you were to try to form a linked list of structs of a single type, memory management would not be feasible, and the compiler would stop you dead in your tracks. This won't compile:
struct XYClass {
var title: String
var subinstances: [XYClass]
var superinstance: XYClass?
}
The compiler has spotted the problem. You cannot refer to an instance of a struct as a property of that struct. (The compiler calls this a "recursive value type".)
Thus, for your situation, you must use a class, because only then can you get a weak reference and avoid a retain cycle. Only a reference to a class can be weak (and only if the reference is typed as an Optional).
This will compile, and will give your linked list coherent memory management:
class XYClass {
var title: String = ""
var subinstances: [XYClass] = []
weak var superinstance: XYClass?
}