Why constant constraints the property from a structure instance but not the class instance?
Structures in Swift are value types – and, semantically speaking, values (i.e 'instances' of value types) are immutable.
A mutation of a value type, be it through directly changing the value of a property, or through using a mutating
method, is equivalent to just assigning a completely new value to the variable that holds it (plus any side effects the mutation triggered). Therefore the variable holding it needs to be a var
. And this semantic is nicely showcased by the behaviour of property observers around value types, as iGodric points out.
So what this means is that you can think of this:
struct Foo {
var bar = 23
var baz = 59
}
// ...
let foo = Foo()
foo.bar = 7 // illegal
as doing this:
let foo = Foo()
var fooCopy = foo // temporary mutable copy of foo.
fooCopy.bar = 7 // mutate one or more of the of the properties
foo = fooCopy // re-assign back to the original (illegal as foo is declared as
// a let constant)
And as you can clearly see – this code is illegal. You cannot assign fooCopy
back to foo
– as it's a let
constant. Hence, you cannot change the property of a value type that is declared as a let
, and would therefore need make it a var
.
(It's worth noting that the compiler doesn't actually go through this palaver; it can mutate the properties of structures directly, which can be seen by looking at the SIL generated. This doesn't change the semantics of value types though.)
The reason you can change a mutable property of a let
constant class instance, is due to the fact that classes are reference types. Therefore being a let
constant only ensures that the reference stays the same. Mutating their properties doesn't in any way affect your reference to them – you're still referring to the same location in memory.
You can think of a reference type like a signpost, therefore code like this:
class Foo {
var bar = 23
var baz = 59
}
// ...
let referenceToFoo = Foo()
you can think of the memory representation like this:
| referenceToFoo | ---> | Underlying Foo instance |
| (a reference to 0x2A) | |<----------------------->|
|0x2A |0x32 |0x3A
| bar: Int | baz : Int |
| 23 | 59 |
And when you mutate a property:
referenceToFoo.bar = 203
The reference (referenceToFoo
) itself isn't affected – you're still pointing to the same location in memory. It's the property of the underlying instance that's changed (meaning the underlying instance was mutated):
| referenceToFoo | ---> | Underlying Foo instance |
| (a reference to 0x2A) | |<----------------------->|
|0x2A |0x32 |0x3A
| bar: Int | baz : Int |
| 203 | 59 |
Only when you attempt to assign a new reference to referenceToFoo
will the compiler give you an error, as you're attempting to mutate the reference itself:
// attempt to assign a new reference to a new Foo instance to referenceToFoo.
// will produce a compiler error, as referenceToFoo is declared as a let constant.
referenceToFoo = Foo()
You would therefore need to make referenceToFoo
a var
in order to make this assignment legal.
Structure instance property value
struct
s are Value types, while class
es are References.
While you can set properties of a constant reference type, you can not change the reference itself.
let someVideoMode = VideoMode()
someVideoMode = VideoMode()
would cause an error.
You can find a detailed description in the documentation you actually got this sample from.
Confusion about constant class instance in Swift
The class instance is not constant. Just the reference is.
This means you cannot do this:
let myClassInstance = MyClass()
myClassInstance = MyClass()
Why is constant instance of a value type can NOT change its properties while constant instance of a reference type can?
why is constant instance of a value type can NOT change its properties
Because value type is treated as an indivisible unit: it gets copied on assignment, passing it as a parameter behaves like a copy, and so using const-ness locks down the entire struct
. In a sense, rangeOfFourItems
variable represents the structure itself, not a pointer or a reference to it.
while constant instance of a reference type can?
This is not entirely correct to say that declaring a const variable of reference type makes the instance constant as well. Only the reference is constant, not the instance.
If you think about it, that is the only way this could be meaningfully implemented, because multiple variables can reference the same by-reference instance. If one of these variables is constant and the other one is not constant, assigning a non-const reference to a constant variable could not possibly lock down the referenced object, which would lock out the non-const reference as well:
var a = ByRefType()
let b = a; // b is a constant reference to the same instance as "a"
a.property = newValue; // Prohibiting this assignment would be inconsistent
Of course the constant variable itself (e.g. b
above) could not be re-assigned, unlike the non-constant variable a
.
Why I can change/reassigned a constant value that Instantiated from a class
As @Wain has said – it's due to the nature of reference types. The instance being a let
constant only means you cannot assign a new reference to it – but says nothing about the actual mutability of the instance itself.
If you change your class to a struct, you'll see how the behaviour differs with value types, as changing a property changes the actual value of your Person
– therefore you are unable to do so if it's a let
constant. However I somewhat doubt you'll want to make your Person
a struct, as two people with the same name shouldn't be considered to be the same person.
If you only wish your properties to be assigned upon initialisation (and then read-only for the lifetime of the instance), then I would recommend making them let
constants (instead of making their setters private). This will ensure that you cannot even change their value from within your class, once assigned.
The rule is as long you give a property a value before the super.init()
call – you can make it a let
constant (in this case, you just have to assign them in the initialiser before using self
).
class Person {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
...
Setting Part of Swift Computed Structure
The way to understand this is to consider a similar but simpler case without your extra complications. Let's just talk about this:
struct S {
var name = "matt"
}
var s = S()
s.name = "mogelbuster"
How does that work? What happens when we set s.name
? First, pull out the current value of s
; then, we set its name
property; then, we set the value of s
to this new struct. (You can easily confirm that last part by putting a setter observer on s
.)
Setting a struct's property by way of a reference, then involves getting the reference's value (the struct), setting the property, and setting the reference's value with the new struct. That's because a struct is a value type. It cannot be mutated in place, so setting a property by way of a reference to a struct involves setting into the reference.
What you are doing with testPoint
is merely a computed-variable version of the same process. The mere act of speaking of testPoint.x
means that we must get testBacking
, to find out what it is. Thus, the getter is called. Then you set into testPoint.x
, thus calling the setter to write the new value back into testBacking
.
Note that the same thing would not be true if we were working with a class. Here's a variation on your original example:
class PointHolder {
var point = CGPoint(x:3, y:5)
var x : CGFloat {
get { return point.x }
set { point.x = newValue }
}
}
var testBacking = PointHolder()
var testPoint:PointHolder {
get {
print("getter called")
return testBacking
} set {
print("setter called with newValue = \(newValue)")
testBacking = newValue
}
}
testPoint.x = 10 // getter called, but _not_ setter
In the last line, the testPoint
setter is not called — because a class instance is a reference type and is mutable in place. testBacking
is changed without setting, because what we got from testBacking
when we said testPoint.x
, and the getter was called, is a reference; a change to this changes the thing testBacking
points to without setting into it.
How to mute the warning of never mutated in Swift?
Because actually you don't change the Person
object,
With let
you can change the properties
of the object. But you can't change the object
it self.
So change your code to what the warning lead you.
And of course you can try before asking this question.
Why does a property observer run when a member of the existing value is changed?
Swift needs to treat the mutation of myValue.msgStr
as having value semantics; meaning that a property observer on myValue
needs to be triggered. This is because:
myValue
is a protocol-typed property (which also just happens to be optional). This protocol isn't class-bound, so conforming types could be both value and reference types.The
myStr
property requirement has an implicitlymutating
setter because of both (1) and the fact that it hasn't been markednonmutating
. Therefore the protocol-typed value may well be mutated on mutating though itsmyStr
requirement.
Consider that the protocol could have been adopted by a value type:
struct S : MyProtocol {
var msgStr: String?
}
In which case a mutation of msgStr
is semantically equivalent to re-assigning an S
value with the mutated value of msgStr
back to myValue
(see this Q&A for more info).
Or a default implementation could have re-assigned to self
:
protocol MyProtocol {
init()
var msgStr: String? { get set }
}
extension MyProtocol {
var msgStr: String? {
get { return nil }
set { self = type(of: self).init() }
}
}
class MyClass : MyProtocol {
required init() {}
}
class MyWrapperClass {
// consider writing an initialiser rather than using an IUO as a workaround.
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
In which case the mutation of myValue.myStr
re-assigns a completely new instance to myValue
.
If MyProtocol
had been class-bound:
protocol MyProtocol : class {
var msgStr: String? { get set }
}
or if the msgStr
requirement had specified that the setter must be non-mutating:
protocol MyProtocol {
var msgStr: String? { get nonmutating set }
}
then Swift would treat the mutation of myValue.msgStr
as having reference semantics; that is, a property observer on myValue
won't get triggered.
This is because Swift knows that the property value cannot change:
In the first case, only classes can conform, and property setters on classes cannot mutate
self
(as this is an immutable reference to the instance).In the second case, the
msgStr
requirement can only either be satisfied by a property in a class (and such properties don't mutate the reference) or by a computed property in a value type where the setter is non-mutating (and must therefore have reference semantics).
Alternatively, if myValue
had just been typed as MyClass!
, you would also get reference semantics because Swift knows you're dealing with a class:
class MyClass {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}
class MyWrapperClass {
var myValue: MyClass! {
didSet {
print("In MyWrapperClass didSet")
}
}
}
let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
wrapperObj.myValue.msgStr = "Some other string" // Line2
// In MyWrapperClass didSet
// In MyClass didSet
Reference a constant field of another class when using a generic property named after it
While Rufu's Answer resolve the problem, it does not explain why the problem exists.
The problem exists because your property:
public IArbitraryQualifier ArbitraryQualifier { get; }
Is the same name as your concrete class:
public class ArbitraryQualifier
Since the code is contained within the Person
class, the compiler will resolve names to (locally=this) Locally-Scoped variables (var... etc), Then Local Fields/Properties, then to Accessible Named classes within the current namespace, then up the namespace chain.
Renaming the property name on the local/this class also solves the problem.
public interface IArbitraryQualifier
{
int Qualification
{
get;
}
}
public class ArbitraryQualifier : IArbitraryQualifier
{
public const int MIN_QUALITY = 1;
public int Qualification
{
get;
}
}
public class Person
{
public IArbitraryQualifier ArbitraryQualifier2
{
get;
}
public bool AmIARealBoy
{
get
{
return this.ArbitraryQualifier2.Qualification >= ArbitraryQualifier.MIN_QUALITY;
}
}
}
Related Topics
Usb Connection Delegate on Swift
Is This Response from the Compiler Valid
How to Pass Multiple Enum Values as a Function Parameter
Weak References in Swift Playground Don't Work as Expected
In Swiftui, How to Increase the Height of a Button
Using Navigationlink in Menu (Swiftui)
Having Hard Time Implement a Simple Singleton in Swift
How to Make My Exponentiation Operator Work With All Numeric Types in Swift
How to Import a Swift File from Another Swift File
Swift 3.0: Convert Server Utc Time to Local Time and Vice-Versa
How to Check If a Text Field Is Empty or Not in Swift
JavaScript Synchronous Native Communication to Wkwebview
How Does One Make an Optional Closure in Swift
How to Determine If a String Contains a Character from a Set in Swift