Property initializers run before 'self' is available - swift

Seems like I'm having a problem with something that shouldn't be the case... But I would like to ask for some help.
There are some explanations here on the Stack I don't get.
Having two simple classes where one refers to another, as per below:
class User {
lazy var name: String = ""
lazy var age: Int = 0
init (name: String, age: Int) {
self.name = name
self.age = age
}
}
class MyOwn {
let myUser: User = User(name: "John", age: 100)
var life = myUser.age
//Cannot use instance member 'myUser' within property initializer
//property initializers run before 'self' is available
}
I get the commented compile error. May someone please tell me what should I do to solve the case?

As correctly pointed out by vadian you should create an init in such scenarios:
class MyOwn {
let myUser: User
var life: Int
init() {
self.myUser = User(name: "John", age: 100)
self.life = myUser.age
}
}
You can't provide a default value for a stored property that depends on another instance property.

You should declare life like this:
lazy var life:Int = {
return self.myUser.age
}()
Because you are trying to initialise one property(variable) with another during initialisation process. At this time variables are not available yet.

Related

SwiftUI : #State var - Cannot use instance member 'cercax' within property initializer; property initializers run before 'self' is available [duplicate]

Seems like I'm having a problem with something that shouldn't be the case... But I would like to ask for some help.
There are some explanations here on the Stack I don't get.
Having two simple classes where one refers to another, as per below:
class User {
lazy var name: String = ""
lazy var age: Int = 0
init (name: String, age: Int) {
self.name = name
self.age = age
}
}
class MyOwn {
let myUser: User = User(name: "John", age: 100)
var life = myUser.age
//Cannot use instance member 'myUser' within property initializer
//property initializers run before 'self' is available
}
I get the commented compile error. May someone please tell me what should I do to solve the case?
As correctly pointed out by vadian you should create an init in such scenarios:
class MyOwn {
let myUser: User
var life: Int
init() {
self.myUser = User(name: "John", age: 100)
self.life = myUser.age
}
}
You can't provide a default value for a stored property that depends on another instance property.
You should declare life like this:
lazy var life:Int = {
return self.myUser.age
}()
Because you are trying to initialise one property(variable) with another during initialisation process. At this time variables are not available yet.

Is it possible to have default member initialization with a class in Swift (like with a struct)

In the following example code, I create a struct and a class with similar members. With the struct I can initialize an instance by any number of the members into its constructor, and the rest will default. With a class, I have to specify every version of init I want to use. Seems like I must be missing some way to do it with a class though -- is there any way to do this? It looks like in 2016 there was not, but I know Swift has changed a ton since then. I'm hoping there is a way now.
import Foundation
struct FooStruct {
var id: UUID = UUID()
var title = ""
}
// these statements both work fine
let a = FooStruct(id: UUID())
let a2 = FooStruct(title: "bar")
class FooClass {
var id: UUID = UUID()
var title = ""
}
// these statements both give the same error:
// Argument passed to call that takes no arguments
let b = FooClass(id: UUID())
let b2 = FooClass(title: "bar")
What you are seeing with Structure types is what is called a memberwise initializer. Swift does not provide one of these to Class types because of the more complex way Classes are initialized, due to their inheritance model.
Swift provides a default initializer—different than a memberwise initializer—for any structure or class that provides default values for all of its properties and doesn’t provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values.
you could just use this:
class FooClass {
var id: UUID = UUID()
var title = ""
init(id: UUID = UUID(), title: String = ""){
self.id = id
self.title = title
}
}
and this will work:
let b = FooClass(id: UUID())
let b2 = FooClass(title: "bar")

Is there any way to make the method return a mutable value?

as shown in the code below:
struct Person {
var name: String
}
struct Group {
var person: Person
func callAsFunction() -> Person {
// Person is immutable value
person
}
}
var james = Person(name: "James")
var group = Group(person: james)
group().name = "Wong" //ERROR: Cannot assign to property: function call returns immutable value
group() return an immutable value, that can't be changed! So Is there any way to make the callAsFunction() method return a mutable value?
Thanks ;)
Updated:
My idea is to transfer all the calls and visits of the Group to the Person object in the Group, just like using Person directly.
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
My needs are a bit like proxy objects in the Ruby language. Accessing an object (Group) actually accesses another object (Person) inside it, as if the Group does not exist.
ruby proxy patterns:
https://refactoring.guru/design-patterns/proxy/ruby/example
CallAsFunction is the closest implementation so far, but requires that Person cannot be a Struct, otherwise it cannot be assigned to its properties.
Maybe it's not possible to implement this feature in Swift yet?
You're using the wrong dynamic method. What you want is dynamicMemberLookup. Watch closely. First, the preparation:
struct Person {
var name: String
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember kp:WritableKeyPath<Person,String>) -> String {
get { self.person[keyPath:kp] }
set { self.person[keyPath:kp] = newValue }
}
}
Now look at what that allows you to say:
var group = Group(person: Person(name: "James"))
group.name = "Wong"
print(group.person) // Person(name: "Wong")
Do you see? We set the name of the Group even though it has no name property, and the result was that we set the name of the Group's person which does have a name property.
The callAsFunction simply returns (a copy of the) Person, which is a value type. You cannot then mutate the property of it like that. It is equivalent to the following:
struct Person {
var name: String
}
Person(name: "Foo").name = "Bar"
That returns the same error:
If Person was a reference type, it would have worked, but not for a value type. And even if you took your value type, and first assigned it to a variable before mutating it, you would only be mutating your copy, not the original.
If you want the behavior you want, you would use a #dynamicMemberLookup as suggested by matt (+1) and outlined in SE-0195.
You said:
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
You do not need “100 subscript methods.” It is the motivating idea behind #dynamicMemberLookup, namely that the properties will be determined dynamically. E.g., here is Person with two properties, but Group only has the one #dynamicMemberLookup.
struct Person {
var name: String
var city: String
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember keyPath: WritableKeyPath<Person, String>) -> String {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}
var group = Group(person: Person(name: "James", city: "New York"))
group.name = "Wong"
group.city = "Los Angeles"
print(group.person) // Person(name: "Wong", city: "Los Angeles")
If you want to handle different types, make it generic:
struct Person {
var name: String
var city: String
var age: Int
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript<T>(dynamicMember keyPath: WritableKeyPath<Person, T>) -> T {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}
And
var group = Group(person: Person(name: "James", city: "New York", age: 41))
group.name = "Wong"
group.city = "Los Angeles"
group.age = 42
print(group.person) // Person(name: "Wong", city: "Los Angeles", age: 42)

swift 3.1.1 ERROR extra argument '{property}' in call

I was testing out inheritance in Swift 3 and ran into an unexpected error. No matter how I alter the super.init() call, I can't make the error trace less problematic than this.
I have read through the documentation and other similar posts here on SO, but they don't have any examples of this particular problem. I am trying to create a class with properties that are either variable or constant and then creating another class that inherits from the first class and adds new properties.
The error comes from my subclass's init function: I set the new properties first, then call super.init() with the superclass's appropriate arguments. Swift then tells me that the third parameter "birthday" is an "extra argument."
Is there some sort of problem between my superclass having three properties and my subclass having two? I can't think of any other problem that could throw an error like this. Not sure which part is confusing me.
Here's my code:
class Person {
var name: String
var age: Int
let birthday: String
init(name: String, age: Int, birthday: String) {
self.name = name
self.age = age
self.birthday = birthday
}
}
class Student: Person {
var isEnrolled: Bool
var numberOfClasses: Int
init(isEnrolled: Bool, numberOfClasses: Int) {
self.isEnrolled = isEnrolled
self.numberOfClasses = numberOfClasses
super.init(name: name, age: age, birthday: birthday) {
self.name = name
self.age = age
self.birthday = birthday
}
}
}
Your initializer does not have a closure, so naturally, your approach cannot work. It says birthday is an extra argument, because the (non-existent) closure is the fourth argument in the list.
Student's initializer does not know what name, age or birthday is. You should include those parameters in the initializer as well and call super.init at the end.
init(name: String, age: Int, birthday: String, isEnrolled: Bool, numberOfClasses: Int) {
self.isEnrolled = isEnrolled
self.numberOfClasses = numberOfClasses
super.init(name: name, age: age, birthday: birthday)
}

How can I create a optional value from a my class

I create a class User, and I want to create an optional out of it, compiler then fires an error
class User {
var firstName: String = ""
var lastName: String = ""
}
var Tom = User?(firstName: "Tom", lastName: "Soya")
error: cannot invoke initializer for type 'User?' with an argument list of type '(firstName: String, lastName: String)'
Question: What's wrong with it? Do I need to put more stuff inside class before I can create an optional value? If so what is it?
Thanks
The problem is that your User class has no initializers. Thus, there is only one way to initialize it: namely, by saying User(). So if you are going to define User like this:
class User {
var firstName: String = ""
var lastName: String = ""
}
Then the best you can do is this:
var tom : User? = User()
tom?.firstName = "Tom"
tom?.lastName = "Soya"
#matt's answer is correct, but here's another way, which I think is clearer:
var tom = Optional(User())
See Optional.init(_:).