I read this sentence in the Swift documentation provided by Apple
“Subclasses are only allowed to modify variable properties of superclasses during initialization.”
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks.
But if I run this code it works with no problems:
class Person{
var name:String
init(name:String){
self.name = name
}
}
class SuperHero:Person{
var power:String?
init(name:String, power:String?){
self.power = power
super.init(name: name)
}
func changeName (){
// HERE I'M CHANGING A SUPERCLASS VARIABLE outside Initialization!!!
self.name = "Mark"
}
}
let superman = SuperHero(name: "Superman", power: "Fly!")
superman.changeName()
println("\(superman.name)")
Have I misinterpreted the documentation?
there is serious misinterpretation in the quoted sentence. that is the original quote in its natural environment.
Subclasses are only allowed to modify variable properties of superclasses during initialization. You can’t modify inherited constant properties of subclasses.
please read in with the correct emphasis, because that sentence means that on other words:
you are allowed to modify the variables of superclass during initilaization, but you are not allowed to do such thing with constants of superclass.
there is no such statement here: you are allowed to modify the variables only during initialization but and later you are not allowed to modify them.
I hope that makes sense now.
Sentence from Swift Language Guide is this:
Subclasses are only allowed to modify variable properties of superclasses during initialization.
Note that "variable" word is in italics. It means that there is an emphasis on this word, and the resulting sentence means that during initialization you can modify only those properties of superclass that are variable.
In expansion of my comment, the book is saying you can't do this:
class Person{
let name:String
init(name:String){
self.name = name // This is fine, but see warnings below
}
}
class SuperHero:Person{
var power:String?
init(name:String, power:String?){
self.power = power
super.init(name: name) // WARNING: Cannot assign to 'name' in 'self'
}
func changeName (){
// HERE I'M CHANGING A SUPERCLASS VARIABLE outside Initialization!!!
self.name = "Mark" // WARNING: Cannot assign to 'name' in 'self'
}
}
let superman = SuperHero(name: "Superman", power: "Fly!")
superman.changeName()
println("\(superman.name)")
As I commented above, the sentence only makes sense when paired with the following one: “You can’t modify inherited constant properties of subclasses.” It's not really saying that you can modify variables only during initialization it's saying you can't modify inherited constants during initialization of a subclass (nor at any point). The word initialization distracts from the point being made.
Related
As far as I known (see reference A), Static property initilizers are lazy, and I found the following description by the office documents
You must always declare a lazy property as a variable (with the var
keyword) because its initial value might not be retrieved until after
instance initialization completes. Constant properties must always
have a value before initialization completes, and therefore cannot be
declared as lazy.
From the above information, I think I couldn't define the static property as a constant variable and I made a tryout, it turns out I can do that without triggering any error from the compiler.
Example:
class Person {
static let firstNaID = "First Name"
static let lastNaID = "Last Name"
}
Question: Is this a bug of the Swift 3.0.1 or I was wrong.
Reference A: Neuburg. M.2016. IOS 10 Programming Fundamental with Swift. P127
Thanks for your time and help
Neuburg M. is drawing a distinction between static properties and instance properties. You are pretending to ignore that distinction. But you cannot ignore it; they are totally different things, used for different purposes.
In this code:
class Person { // let's declare a static property
static let firstNaID = "First Name"
}
... firstNaID is already lazy. But now try to do this:
class Person { // let's declare an instance property
lazy let firstNaID : String = "First Name" // error
}
You can't; as things stand (up thru Swift 3.1), you have to say lazy var instead — and when you do, you get a lazy instance property.
Your static let declaration thus doesn't accomplish what lazy let wanted to accomplish, because a static property is not an instance property.
You are talking about type properties
Form the same chapter of the documentation
Type Properties
... Type properties are useful for defining values that are universal to all instances of a particular type, such as a constant property that all instances can use ...
Stored type properties can be variables or constants. Computed type properties are always declared as variable properties, in the same way as computed instance properties.
NOTE
...
Stored type properties are lazily initialized on their first access. They are guaranteed to be initialized only once, even when accessed by multiple threads simultaneously, and they do not need to be marked with the lazy modifier.
Quote from the Swift 3.0 office document of the Chapter: Initialization
For class instances, a constant property can be modified during initialization only by the class that introduces it. It cannot be modified by a subclass.
To my understanding the modified involves the action after the definition, aka the action after declaring and assigning value, aka re-assigning values, therefore I tried the following code.
class SurveryQuestion {
let text: String
var response: String?
init(text: String) {
self.text = "do you like music?"
self.text = text //Got an error here
}
func ask(){
print(text)
}
}
And I got an error at line self.text = text. The compiler asked me to change the property textfrom constant to variable. Isn't it says that the constant property can be modified by the initializer of the class which originally introduced it?
Question: Am I understand the word modified wrongly? Is it means the action after the declaring rather than the definition which would lead to the modified is meant to by passing a value to the constant.
I think that the documentation is not clear enough. You can set a constant property only once during initializing. You also would not be able to set it during initialization if the property's value was defined inline. Here is example.
class SomeClass {
let someProperty: String = "A"
init() {
self.someProperty = "" //ERROR: Immutable value "self.someProperty" may only be initialized once.
}
}
The compile time error //ERROR: Immutable value "self.someProperty" may only be initialized once. actually explains it well.
I was trying to do something like this (it is a contrived example for demonstration purposes only):
class Test {
let hello = "hello"
let world = "world"
let phrase: String {
return self.hello + self.world
}
}
but you can't use let for computed properties in Swift. Is there a way to do this without having to write an init() method? Thanks!
The reason let doesn't work on a read-only calculated property is because it's used to state that the property's actual value will never change after being set – not that the property is read-only. As the Apple docs say (emphasis mine):
You must declare computed properties — including read-only computed
properties — as variable properties with the var keyword, because their
value is not fixed. The let keyword is only used for constant
properties, to indicate that their values cannot be changed once they
are set as part of instance initialization.
You therefore need to use var in order to reflect the fact that a calculated property's value could change at any time, as you're creating it on the fly when accessing it. Although in your code, this can't happen – as your hello and world properties are let constants themselves. However, Swift is unable to infer this, so you still have to use var.
For example:
class Test {
let hello = "hello"
let world = "world"
var phrase: String {
return self.hello + self.world
}
}
(This doesn't change the readability of the property – as because you haven't provided it with a setter, it's still read-only)
However in your case, you might want to consider using a lazy property instead, as your hello and world properties are constants. A lazy property is created when it's first accessed, and keeps its value for the rest of its lifetime – meaning you won't have to keep on concatenating two constants together every time you access it.
For example:
class Test {
let hello = "hello"
let world = "world"
lazy var phrase: String = {
return self.hello + self.world
}()
}
Another characteristic of let properties is that their value should always be known before initialisation. Because the value of a lazy property might not be known before then, you also need to define it as a var.
If you're still adamant on wanting a let property for this, then as far as I can see, you have two options.
The first is the neatest (although you've said you don't want to do it) – you can assign your phrase property in the initialiser. As long as you do this before the super.init call, you don't have to deal with optionals. For example:
class Test {
let hello = "hello"
let world = "world"
let phrase: String
init() {
phrase = hello+world
}
}
You simply cannot do it inline, as self at that scope refers to the static class, not an instance of the class. Therefore you cannot access the instance members, and have to use init() or a lazy/calculated property.
The second option is pretty hacky – you can mirror your hello and world properties at class level, so you can therefore access them inline in your phrase declaration. For example:
class Test {
static let hello = "hello"
static let world = "world"
// for some reason, Swift has trouble inferring the type
// of the static mirrored versions of these properties
let hello:String = Test.hello
let world:String = Test.world
let phrase = hello+world
}
If you don't actually need your hello or world properties as instance properties, then you can just make them static – which will solve your problem.
Yes to make it work as computed properties, replace let to var.
Like,
class Test {
let hello = "hello"
let world = "world"
var phrase: String {
return self.hello + self.world
}
}
This way you can use it without init()
Is there a way to get the compile time name of a variable in Swift 2?
I mean the first variable name, which references to a new class instance, if any.
Here is a simple example:
public class Parameter : FloatLiteralConvertible {
var name:String?
var value:Double
// init from float literal
public required init (floatLiteral value: FloatLiteralType) {
self.value = Double(value)
self.name = getLiteralName()
}
func getLiteralName () -> String {
var literalName:String = ""
// do some magic to return the name
return literalName
}
}
let x:Parameter = 2.0
print(x.value) // this returns "2.0"
print(x.name!) // I want this to return "x"
I've already checked similar questions on that topic handling mirroring or objective-c reflections. But in all those cases, one can get only the property names in a class - in the example above name and value.
The same question has been asked in 2014 - Swift: Get Variable Actual Name as String
- and I hope, that since then there is a solution in swift 2.
No, there is no way to do that.
You have to understand that in the compiled state that variable usually does not exist. It can be optimized out or it is represented only as an item on the execution stack.
Even in languages with much better reflection that Swift has, usually you cannot inspect local variables.
To be honest, getting the name of a local variable dynamically has no practical use case.
I'm currently practicing examples from Swift Language iBook. My understanding of "let" is that we use "let" to make a constant. Once we assign a value to it, we cannot assign another value to it again. Like the codes below:
let city="NY"
city="LA" <--error (Cannot assign 'let' value city)
But I saw this example on iBook which really confused me:
struct Color{
let red=0.0, green=0.0, blue=0.0 //<---declare variables using "let" and assign value
init(red:Double,green:Double,blue:Double){
self.red=red //<---assign value to variable again?
self.green=green
self.blue=blue
}
}
In this example, it has already assigned values to red, green and blue which use "let".
Why can we assign values to these three variables again in init?
The initialization in the let provides default values if you don't initialize them yourself in the constructor.
Constructors (init) are special. Inside them, you can assign to a constant instance variable. In fact, if you don't have a default value for them, you have to assign to them. (This applies to classes too.)
Thanks to Qwerty Bob for finding this in the docs
Modifying Constant Properties During Initialization
You can modify the value of a constant property at any point during initialization, as long as it is set to a definite value by the time initialization finishes.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l
You may set constant variables during the init process, before the self keyword is used. After this they are then truly 'constant'.
You must do it before the self keyword is used, as if you pass it to another object it could in turn call a method of yours which relies on that constant property
And also: structs get passed by value and not by reference, so you cannot modify the variables in a struct at all after their set. So the let keyword really makes sense.
If you continue reading a few paragraphs after the example you gave from the book (unless they use it in multiple locations), it actually talks about this behavior:
You can modify the value of a constant property at any point during
initialization, as long as it is set to a definite value by the time
initialization finishes.
So basically you can modify constants and upon ending the initialization all constants must have a definitive value. It also goes on to talk about how this works with subclasses too:
For class instances, a constant property can only be modified during
initialization by the class that introduces it. It cannot be modified
by a subclass.
Here is the doc reference for it (same as the book), the quoted sections is under the "Modifying Constant Properties During Initialization" subheading.
Apart from the initialization part, which was answered by Kevin, you're still missing the constant part of let. So to clarify let is not exactly a constant.
According to „The Swift Programming Language.” Apple Inc., 2014-05-27T07:00:00Z. iBooks:
Like C, Swift uses variables to store and refer to values by an
identifying name. Swift also makes extensive use of variables whose
values cannot be changed. These are known as constants, and are much
more powerful than constants in C.
Both var and let are references, therefore let is a const reference.
Using fundamental types doesn't really show how let is different than const.
The difference comes when using it with class instances (reference types):
class CTest
{
var str : String = ""
}
let letTest = CTest()
letTest.str = "test" // OK
letTest.str = "another test" // Still OK
//letTest = CTest() // Error
var varTest1 = CTest()
var varTest2 = CTest()
var varTest3 = CTest()
varTest1.str = "var 1"
varTest2.str = "var 2"
varTest3 = varTest1
varTest1.str = "var 3"
varTest3.str // "var 3"