Global 'let' declaration requires an initializer expression - swift

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!

Related

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

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.

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

Cannot assign to property: function call returns immutable value

Consider the following example.
struct AStruct{
var i = 0
}
class AClass{
var i = 0
var a: A = A(i: 8)
func aStruct() -> AStruct{
return a
}
}
If I try to mutate the the variable of a instance of class AClass it compiles successfully.
var ca = AClass()
ca.a.i = 7
But If I try to mutate the return value of aStruct method, the compile screams
ca.aStruct().i = 8 //Compile error. Cannot assign to property: function call returns immutable value.
Can someone explain this.
This is compiler's way of telling you that the modification of the struct is useless.
Here is what happens: when you call aStruct(), a copy of A is passed back to you. This copy is temporary. You can examine its fields, or assign it to a variable (in which case you would be able to access your modifications back). If the compiler would let you make modifications to this temporary structure, you would have no way of accessing them back. That is why the compiler is certain that this is a programming error.
Try this.
var aValue = ca.aStruct()
aValue.i = 9
Explanation
aStruct() actually returns a copy of the original struct a. it will implicitly be treated as a constant unless you assign it a var.
Try using a class instead of a struct, as it's passed by reference and holds onto the object, while a struct is passed by value (a copy is created).

Swift: get the compile time name of variable (referencing to a class)

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.

How to create a global variable?

I have a global variable that needs to be shared among my ViewControllers.
In Objective-C, I can define a static variable, but I can't find a way to define a global variable in Swift.
Do you know of a way to do it?
From the official Swift programming guide:
Global variables are variables that are defined outside of any
function, method, closure, or type context. Global constants and
variables are always computed lazily.
You can define it in any file and can access it in current module anywhere.
So you can define it somewhere in the file outside of any scope. There is no need for static and all global variables are computed lazily.
var yourVariable = "someString"
You can access this from anywhere in the current module.
However you should avoid this as Global variables are not good for application state and mainly reason of bugs.
As shown in this answer, in Swift you can encapsulate them in struct and can access anywhere.
You can define static variables or constant in Swift also. Encapsulate in struct
struct MyVariables {
static var yourVariable = "someString"
}
You can use this variable in any class or anywhere
let string = MyVariables.yourVariable
println("Global variable:\(string)")
//Changing value of it
MyVariables.yourVariable = "anotherString"
Global variables that are defined outside of any method or closure can be scope restricted by using the private keyword.
import UIKit
// MARK: Local Constants
private let changeSegueId = "MasterToChange"
private let bookSegueId = "MasterToBook"
if you want to use it in all of your classes you can use:
public var yourVariable = "something"
if you want to use just in one class you can use :
var yourVariable = "something"
If you don't actually want a global variable, but instead want to save values that persist even when the app closes you can do this:
If you don't actually want to pass data between view controllers but rather simply want to store a global variable you can do this:
This gives a great explanation for how to do this in Swift 5: https://www.hackingwithswift.com/example-code/system/how-to-save-user-settings-using-userdefaults
Summary:
To set a value:
let defaults = UserDefaults.standard
defaults.set("value", forKey: "key")
To get a String value:
let key = defaults.object(forKey: "StringKey") as? [String] ?? [String]()
To get integer value:
let key = defaults.integer(forKey: "IntegerKey")