I don't understand why the following doesn't work in Swift:
class SomeClass {
var foo = 1
var bar = self.foo + 1
}
and what's the way around it?
It doesn't work because you cannot use self in that scope to define default values for properties. I believe it is due to the fact that you cannot use self before the object is properly initialized. You could use an explicit initializer instead.
class SomeClass {
var foo: Int
var bar: Int
init() {
self.foo = 1
self.bar = self.foo + 1
}
}
You can, however, access static members.
class SomeClass {
static let initialValue = 1
var foo = initialValue
var bar = initialValue + 1
}
Related
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)
I was trying to access subclass members using KeyPath when I encountered something strange. Look at these 2 simple classes:
class A {
var a:String {
get {
return "str"
}
}
}
class B: A {
override var a:String {
get {
return "str1"
}
}
var c = 10
}
Now if I have the following code:
var m: A = B()
var k = \B.a
print(m[keyPath: k])
I will get a runtime error. It seems that you cannot access to subclass members using a WriteableKeyPath. But the following code works:
var m: A = B()
var k: AnyKeyPath = \B.a
print(m[keyPath: k]!)
I can use this code to access both \B.a and \B.c. Any idea why it is like this?
Since returned object by using AnyKeyPath is immutable, I will not be able to use it for updating object. I wonder if there is any workaround for updating subclass members using a KeyPath on parent class or not.
Update:
In order to elaborate more, here is another example:
var v: UIView = UILabel()
var k = \UILabel.text
v[keyPath: k] = "test" // Error cannot do this
I think this can be very useful.
What's difference between final variables and non-final variables :
var someVar = 5
final var someFinalVar = 5
and
let someLet = 5
final let someFinalLet = 5
The final modifier is described in the Swift Language Reference, which says
final
Apply this modifier to a class or to a property, method, or subscript member of a class. It’s applied to a class to indicate that the class can’t be subclassed. It’s applied to a property, method, or subscript of a class to indicate that a class member can’t be overridden in any subclass.
This means without final we can write:
class A {
var x: Int {return 5}
}
class B : A {
override var x: Int {return 3}
}
var b = B()
assert(b.x == 3)
but if we use final in class A
class A {
final var x: Int {return 5}
}
class B : A {
// COMPILER ERROR
override var x: Int {return 3}
}
then this happens:
$ swift final.swift
final.swift:6:18: error: var overrides a 'final' var
override var x: Int {return 3}
^
final.swift:2:15: note: overridden declaration is here
final var x: Int {return 5}
Final variables can't be overridden in subclasses. It also hints this to the compiler which allows it to inline the variable. It other words every time the compiler sees a final variable being used somewhere, it can immediately substitute the value. Whether or not the compiler actually does this is up to the compiler and whatever optimisations it knows/uses.
import Cocoa
public class Ut {
public func foo(m: Int) -> Int {
return m*m
}
}
class ViewController: NSViewController {
let j = 3
let k = Ut.foo(j) // 'ViewController.Type' does not have a member named 'j'
...
Unfortunately, you can’t access other properties when giving properties their initial values:
struct S {
let a = 1
// error: S.Type does not have a member named a
let b = a + 1
}
Instead, you have to initialize these values inside init:
struct S {
let a: Int
let b: Int
init() {
// note, a must be initialized in here
// too if b relies on it
a = 1
b = a + 1
}
}
(also, it looks like you’re using Ut.foo as a class-level function but it’s a member function - but this particular error is about the property init)
You have to make your foo(m: Int) as class function in order to call directly like that. Otherwise, you have to create an instance of Ut then call foo() on to this instance
class func foo(m: Int) -> Int {
return m*m
}
then in other place:
let k = Ut.foo(j)
If you pass j as parameter, this call must be placed inside a function, not at class level. If you want to call at class level, pass a value (like: let k = Ut.foo(5) )
Consider this example
class Foo {
private let bar = "bar"
lazy var baz : String = {
return "baz \(bar)"
}()
}
Unfortunately this won't compile and give the following error
'Foo.Type' does not have a member named 'bar'
I really do not want to declare bar outside the class (globally). Is there no other way to keep this inside the class and why isn't bar accessible in the first place?
TL;DR: preface with self
Swift can be quite misleading with error messages, but in this case, the answer can be deduced from the message. It is looking for bar on type Foo.Type, whereas you are trying to reference an instance variable. Here is code that works:
class Foo {
private let bar = "bar"
lazy var baz : String = {
return "baz \(self.bar)"
}()
}
In lazy props you need to say self
lazy var baz : String = {
let bar = self.bar
return "baz \(bar)"
}()