Why property observer doesn't work with classes? [duplicate] - swift

This question already has an answer here:
Is there a way to get didSet to work when changing a property in a class?
(1 answer)
Closed 4 years ago.
Using Playground.
I have the following structure:
struct Foo {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
I declared an instance from it as property observer with default values as "James" for the name and 33 for the age:
var myFoo = Foo(name: "James", age: 33) {
willSet {
print("my foo will be: \(newValue)")
}
didSet {
print("my foo was: \(oldValue)")
}
}
When trying to edit the name of the instance:
myFoo.name = "John"
I could see on the log:
my foo will be: Foo(name: "John", age: 33)
my foo was: Foo(name: "James", age: 33)
which is my expectation. However, when declaring Foo as a class instead of structure: class Foo and running the exact same code, nothing will appear on the log.
What is the reason behind it?

Because a class is a reference type, whereas a struct is a value type.
Suppose Foo is a class. myFoo is merely a reference. Saying myFoo.name = "John" mutates the object in place; no new value is assigned to myFoo, so the setter observer has nothing to observe.
Suppose Foo is a struct. That's a value type. It cannot be mutated in place. Thus, saying myFoo.name = "John" actually replaces the struct in the variable myFoo with a new struct; that sets the variable and the setter observer fires.
That is also why you can say myFoo.name = "John" for a class even if myFoo was declared with let (i.e. the object is merely mutated in place), but you can't do that with a struct — you have to have declared myFoo with var so that the variable value can be replaced.

Related

Swift variable type setting to superclass handle subclasses

How do I work with variables in Swift that have the type of a main class but are passed an instance of a subclass?
Here is a piece of example code:
class MainClass {
var name: String
init(name: String) {
self.name = name
}
}
class Num1: MainClass {
var num: Int = 1
}
class Num2: MainClass {
var num: Int = 2
}
struct ExampleView: View {
var subClassInstance: MainClass
var body: some View {
Text(subClassInstance.name)
Text(subClassInstance.num) // trying to access this property
}
}
let example = ExampleView(subClassInstance: Num1(name: "test"))
Specifically, I want to be able to access subclass properties from a variable with the type of a main class. In the context of my example, I want to be able to access the "num" property of the passed subclass instance from the variable in the view with type of MainClass. Is this possible? The motivation for doing this is having one view that works with similar subclasses of a main class--not having to write two views, one with a variable set to the type of each subclass.
You could have num as a property in MainClass. This means you can access num from MainClass itself or any sub-class.
Example:
class MainClass {
var name: String
var num: Int
init(name: String, num: Int) {
self.name = name
self.num = num
}
}
class Num1: MainClass {
init(name: String) {
super.init(name: name, num: 1)
}
}
class Num2: MainClass {
init(name: String) {
super.init(name: name, num: 2)
}
}
struct ExampleView: View {
var subClassInstance: MainClass
var body: some View {
Text(subClassInstance.name)
Text(String(subClassInstance.num))
}
}
let example = ExampleView(subClassInstance: Num1(name: "test"))
See edit history for old answer
It is common practice to include properties in the superClass (your MainClass) if the property is used by different subclasses, I'll not replicate other answers here, George explained it pretty well
(This Answer is about what to do if Georges answer is not suitable, such as when you need different properties in the subclasses)
For your own understanding, It should be possible to Type Cast your ExampleView.subClassInstance to Num1 or Num2
2 ways to do this are
let numInstance = subClassInstance as? Num1
let numInstance = subClassInstance as! Num1
as? will try to downcast from MainClass to Num1 and will return nil if this fails for some reason
as! will try to downcast from MainClass to Num1 and throw an error, causing your app to crash if this fails
if all goes successful you should then be able to use
Text(numInstance.num)

How can I initialize a struct or class in Swift?

I have a struct called Person in this code in the down, I am creating an instance of it, like this one:
let peson: Person = Person(name: "Dan", age: 21)
But I noticed that we can make it with this code as well:
let peson: Person = { Person(name: "Dan", age: 21) }()
So what is the difference? When I should use first way and when I should use second way?
struct Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
This is the Swift equivalent of what JS calls an Immediately invoked funciton expression (IIFE)
{ Person(name: "Dan", age: 21) } is a closure, of type () -> Person.
{ Person(name: "Dan", age: 21) }() calls this closure, passing no arguments (since it has no parameters), and returns the new Person. The result evaluated to just a Person.
You could nest this any number of times. You could even do:
let person: Person = {{{{{{{{{{ Person(name: "Dan", age: 21) }()}()}()}()}()}()}()}()}()}()
or
let person: Person = {{{{{{{{{{ Person(name: "Dan", age: 21) }}}}}}}}}}()()()()()()()()()()
But there's obviously no point. You code would be most idomaticly written as:
let person = Person(name: "Dan", age: 21)
The anonymous closure is an unnamed function. It's useful whenever you want to initialize something with the result of calling a function, but you don't want to write a function like:
func makeDan() -> Person {
...
}
let person = makeDan()
That would mean you have to come up with a name for the function and find some place to put it, which means it's also visible and can be called by other pieces of code that don't need anything to do with makeDan. It's often useful to have functions that have no names.
Needing a function to initialise something is useful when you have something complex that needs some kind of computation, so multiple lines of stuff. It's also useful when the initialization is only wanted to be done if/when required:
lazy var dan: Person = { ... }()
Perhaps because the computation is expensive in some kind of resource use, like cpu or memory. Or possibly because the computation involves some kind of side effect, like opening a database or something, that's only wanted if/when the property is used.
When you want to perform multiple operations while initializing, using closures will help you keep the code clean. See example snippet below -
struct Person {
var name: String
var age: Int
var maritalStatus: MaritalStatus?
init(name: String, age: Int) {
self.name = name
self.age = age
}
init (name: String, age: Int, maritalStatus: MaritalStatus) {
self.init(name: name, age: age)
self.maritalStatus = maritalStatus
}
enum MaritalStatus: String {
case single = "Single"
case married = "Married"
case divorced = "Divorced"
}
}
let person1 = Person(name: "Jonn", age: 10)
let person2: Person = {
var person = Person(name: "Bob", age: 26)
person.maritalStatus = .married
return person
}()
let person3 = Person(name: "Sam", age: 45, maritalStatus: .divorced)
In the above example, person1 and person3 are initialozed with different initilizers, but person 2 assigns the maritalStatus property differently.
Now consider initializations when you want to change mulitple properties on a object, for example UIView - initializing it, changing the corner radius, assigning a background view, adding pre-selected sub views etc., such closure style of initialization is very helpful.

Swift: implement a protocol variable as a lazy var?

It seems that it's not possible to implement a variable that's required by a protocol, with a lazy variable. For example:
protocol Foo {
var foo: String { get }
}
struct Bar: Foo {
lazy var foo: String = "Hello World"
}
Compiler complains that Type 'Bar' does not conform to protocol 'Foo'.
It's also not possible to add the lazy keyword in the protocol declaration, since then you get 'lazy' isn't allowed on a protocol requirement error.
So is this not at all possible?
Citing the Language Guide - Properties - Lazy Stored Properties [emphasis mine]:
A lazy stored property is a property whose initial value is not
calculated until the first time it is used.
I.e., the value is mutated upon first usage. Since foo has been blueprinted in the Foo protocol as get, implicitly nonmutating get, the value type Bar does not fulfil this promise with its lazy property foo, a property with a mutating getter.
Changing Bar to a reference type will allow it to fulfil the Foo blueprint (as mutating a property of a reference type doesn't mutate the type instance itself):
protocol Foo {
var foo: String { get }
}
class Bar: Foo {
lazy var foo: String = "Hello World"
}
Alternative, specify in the blueprint of the foo property of Foo that it has a mutating getter.
protocol Foo {
var foo: String { mutating get }
}
struct Bar: Foo {
lazy var foo: String = "Hello World"
}
See the following Q&A for some additional details of the mutating/nonmutating specifiers for getters and setters:
Swift mutable set in property

readonly mutable fields in Swift

When defining a class in Swift, you can have var properties which are like normal fields in other OOP languages, but also let properties which are both read-only and immutable (like T const * const in C++).
However is there a Swift equivalent of C++'s T * const? (That is, the field itself is immutable, but the object it points to is mutable)?
Here's a representation of my scenario:
class Foo {
let bar: Bar
init(bar: Bar) {
self.bar = bar
}
}
protocol Bar {
var fleem: Int? { get set }
}
class ConcreteBar : Bar {
var fleem: Int? = nil
}
var foo: Foo = Foo( bar: ConcreteBar() )
foo.bar.fleem = 123
(Playground link: https://iswift.org/playground?3jKAiu&v=2 )
Presently this gives me this compiler error:
Swift:: Error: cannot assign to property: 'bar' is a 'let' constant`
foo.bar.fleem = 123
Note that I am not actually setting bar, I'm only setting bar.fleem. I don't know why the compiler is complaining about assigning to bar.
If I change Foo to use this:
class Foo {
var bar: Bar
// ...
...then it compiles okay, but then I lose the guarantee that Foo.bar always has the same instance.
I know I could also change it to private(set):
class Foo {
public private(set) var bar: Bar
// ...
...but Foo itself is still free to overwrite the bar object-reference, and the use of var means that the compiler cannot assume the reference is immutable either, so some optimizations may be skipped.
I'm looking for something like a hypothetical let mutable or var readonly keyword or modifier.
By default, protocol typed objects have value value semantics. As a consequence, they're not mutable if the variable is a let constant.
To introduce reference semantics (and by extension, the mutability of objects referred to be a let constant), you need to make your protocol into a class protocol:
protocol Bar: class {
var fleem: Int? { get set }
}
You need to add the class attribute to the protocol to make it reference type compliant:
protocol Bar : class { ...

Syntactic sugar for init properties in Swift?

class Person {
var name: String
var age: Int
func init(age: Int, name: String, /** ... **/) {
self.age = age
self.name = name
// ... much typing, much boring.
}
}
I may be a bit lazy but explicitly typing each property out feels a lot like the human compiler at work. Is there any syntax sugar for assigning constructor argument to an instance in Swift?
Check out the Default Initializers section of the Swift language book. If you were to make Person a struct instead of a class, it would automatically get a memberwise initializer:
struct Person {
var name: String
var age: Int
}
let p = Person(name: "Joe", age: 30)
Classes or structs that define default values for all their stored properties get a default initializer:
class Person {
var name: String = ""
var age: Int = 0
}
var p = Person()
p.name = "Joe"
p.age = 30
These automatically generated initializers disappear if you declare any of your own initializers in the original type declaration, but you can add other initializers in an extension.