Cannot use instance member 'ie' within property initializer [duplicate] - swift

This question already has answers here:
How to initialize properties that depend on each other
(4 answers)
Closed 5 years ago.
The code:
class man: human {
var ie = "ie/1/3/1///"
let pe = "pe/1/3/3///"
let ol = "ol/1/1/1///"
let sejong = "sejong/3/1/1///"
let gong = "gong/1/1/1///"
let mans = [ie, pe, ol, sejong, gong]
}
The error:
Col 15: cannot use instance member 'ie' within property initializer;
property initializers run before 'self' is available
How can I debug this?

Instance variables cannot be used upon non-lazy initialization of each other.
Consider your code taking into account the following thing: it doesn't matter in which order you define and initialize a variable. From your (developer) perspective, they will all get initialized simultaneously (they are, of course, not from low-level perspective).
It means that when you write:
let mans = [ie,pe,ol,sejong,gong]
You basically tell compiler to make up an array of something not yet initialized. None of the constituents of your array are existent when you make this assignment.
Common solution is to make your initialization - that which relies on other instance variables - lazy:
class man {
var ie = "ie/1/3/1///"
let pe = "pe/1/3/3///"
let ol = "ol/1/1/1///"
let sejong = "sejong/3/1/1///"
let gong = "gong/1/1/1///"
lazy var mans : [String] = {
[ie,pe,ol,sejong,gong]
}()
}
Unlike ordinary, lazy variable obtains its value on first use of this variable, but not instantly once an object has been created. You tell compiler: I don't mean to make up an array right away, but make me one when I firstly use this variable, after the object has already been created:
let theMan = man()
On this stage, theMan.mans property is not yet initialized. However, suffice it to perform even basic operation, like printing:
print(theMan.mans)
that this property is initialized. This is called lazy initialization and this is the way you can make up an array consisting of other instance variables upon initialization. Of course, you should remember that any dependent data may be modified and affect the init.

Related

Global 'let' declaration requires an initializer expression

I am working on iOS code with Xcode 10.2.1.
I have an issue when declaring a let global variable:
import UIKit
import CouchbaseLiteSwift
private let database: Database
This gives the compiler error below:
Global 'let' declaration requires an initializer expression
I don't know how to make database a global variable in this case. I searched about it, but all says about some int or string initialization but not when initializing classes like Database. Because, Couchbase's Database class is a big class. I can't do it with "= null"
Any help please?
You need a variable if you're not sure what it is so that later you can change it:
private var database: Database?
There's no point in defining a constant if you're not going to actually define it as something.
The distinguishing feature of a let variable is that it is assigned to exactly once before being read.
When a let declaration is a local variable, Swift can analyze the code to make sure that the variable will be initialized exactly once before it is used. This is why you can declare a local let variable with no initial value. For example:
let greeting: String
if(light.isOn()) {
greeting = "Hello!"
} else {
greeting = "Who’s there?!"
}
// Now it is OK to use greeting, because we _know_ the code took
// exactly one branch of the if, and each branch initializes greeting
print(greeting) // OK
With an instance variable, you don't need to specify an initial value as long as every initializer sets it exactly once.
However, with a global variable, the compiler can't really make any guarantees about who will assign to it when. You therefore have to give it an initial value right on the spot.
As #rmaddy points out in the comments, it might look like this:
private let database = Database(name: "foo")
If initialization takes multiple statements, you can batch them up in a closure like this:
private let database = {
let config = readConfigFile()
guard let dbName = config["database"] else {
fatalError("Missing config for 'database'")
}
return Database(name: dbName)
}()
If you must make it a global, but it just isn't possible to initialize it until later, you must make it a var:
private var database: Database?
…or if you want any attempt to use it before it’s initialized to be a crasher:
private var database: Database!

Swift static property initilizers are lazy why I could declared it as a constant

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.

Initializing class constants in Swift

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

Class Instantiation in Apple Swift [duplicate]

This question already has answers here:
What is the difference between `let` and `var` in Swift?
(32 answers)
Closed 8 years ago.
Why to instantiate a class I have to do it as a constant with let,
class car {
var type: Int?
var wheels: Int?
}
let auto = car()
I can use var as well:
var auto = car()
What is the difference?, thanks
A constant can only be assigned to, or initialized, once:
let constantAuto = car()
constantAuto.type = 1 // changing properties is fine
constantAuto.wheels = 4
constantAuto = car() // error - can't do this
whereas a variable can be assigned to multiple times:
var variableAuto = car()
variableAuto.type = 1 // changing properties is fine here too
// etc
// need to reset:
variableAuto = car()
Essentially, when you know you're only going to need to create the instance once, use let, so the compiler can be more efficient about the code it creates.
if you're using let you're defining a constant, whereas with var you're declaring a variable.
"A constant declaration defines an immutable binding between the constant name and the value of the initializer expression; after the value of a constant is set, it cannot be changed. That said, if a constant is initialized with a class object, the object itself can change, but the binding between the constant name and the object it refers to can’t."
from https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Declarations.html
to sum it up:
you can change the object a variable refers to, but you can't do that to a constant

Using "let" inside Struct - Swift

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"