class Person {
var name: String
var age: Int
func init(age: Int, name: String, /** ... **/) {
self.age = age
self.name = name
// ... much typing, much boring.
}
}
I may be a bit lazy but explicitly typing each property out feels a lot like the human compiler at work. Is there any syntax sugar for assigning constructor argument to an instance in Swift?
Check out the Default Initializers section of the Swift language book. If you were to make Person a struct instead of a class, it would automatically get a memberwise initializer:
struct Person {
var name: String
var age: Int
}
let p = Person(name: "Joe", age: 30)
Classes or structs that define default values for all their stored properties get a default initializer:
class Person {
var name: String = ""
var age: Int = 0
}
var p = Person()
p.name = "Joe"
p.age = 30
These automatically generated initializers disappear if you declare any of your own initializers in the original type declaration, but you can add other initializers in an extension.
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 have a struct called Person in this code in the down, I am creating an instance of it, like this one:
let peson: Person = Person(name: "Dan", age: 21)
But I noticed that we can make it with this code as well:
let peson: Person = { Person(name: "Dan", age: 21) }()
So what is the difference? When I should use first way and when I should use second way?
struct Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
This is the Swift equivalent of what JS calls an Immediately invoked funciton expression (IIFE)
{ Person(name: "Dan", age: 21) } is a closure, of type () -> Person.
{ Person(name: "Dan", age: 21) }() calls this closure, passing no arguments (since it has no parameters), and returns the new Person. The result evaluated to just a Person.
You could nest this any number of times. You could even do:
let person: Person = {{{{{{{{{{ Person(name: "Dan", age: 21) }()}()}()}()}()}()}()}()}()}()
or
let person: Person = {{{{{{{{{{ Person(name: "Dan", age: 21) }}}}}}}}}}()()()()()()()()()()
But there's obviously no point. You code would be most idomaticly written as:
let person = Person(name: "Dan", age: 21)
The anonymous closure is an unnamed function. It's useful whenever you want to initialize something with the result of calling a function, but you don't want to write a function like:
func makeDan() -> Person {
...
}
let person = makeDan()
That would mean you have to come up with a name for the function and find some place to put it, which means it's also visible and can be called by other pieces of code that don't need anything to do with makeDan. It's often useful to have functions that have no names.
Needing a function to initialise something is useful when you have something complex that needs some kind of computation, so multiple lines of stuff. It's also useful when the initialization is only wanted to be done if/when required:
lazy var dan: Person = { ... }()
Perhaps because the computation is expensive in some kind of resource use, like cpu or memory. Or possibly because the computation involves some kind of side effect, like opening a database or something, that's only wanted if/when the property is used.
When you want to perform multiple operations while initializing, using closures will help you keep the code clean. See example snippet below -
struct Person {
var name: String
var age: Int
var maritalStatus: MaritalStatus?
init(name: String, age: Int) {
self.name = name
self.age = age
}
init (name: String, age: Int, maritalStatus: MaritalStatus) {
self.init(name: name, age: age)
self.maritalStatus = maritalStatus
}
enum MaritalStatus: String {
case single = "Single"
case married = "Married"
case divorced = "Divorced"
}
}
let person1 = Person(name: "Jonn", age: 10)
let person2: Person = {
var person = Person(name: "Bob", age: 26)
person.maritalStatus = .married
return person
}()
let person3 = Person(name: "Sam", age: 45, maritalStatus: .divorced)
In the above example, person1 and person3 are initialozed with different initilizers, but person 2 assigns the maritalStatus property differently.
Now consider initializations when you want to change mulitple properties on a object, for example UIView - initializing it, changing the corner radius, assigning a background view, adding pre-selected sub views etc., such closure style of initialization is very helpful.
My code:
open class Club(name: String, country: String)
class FemaleClub(): Club()
var femaleClub = FemaleClub("Blue", "Australia")
Why is the above code not possible?
Why does it have the error
no value passed for parameter name
and
no value passed for parameter country
in my subclass? The values are set when I initiate femaleClub.
In your example parent class Club has primary constructor which is, by language specification, must be called either from secondary constructors of the same class or primary constructor of subclasses to initialize parameters defined in primary constructor. If you don't want to call a primary constructor of a parent class from subclasses you have a couple of options:
Set default values to parameters of primary constructor:
open class Club(name: String = "DefaultName", country: String = "Country") {
}
In this case you will not be able to call primary constructor with params:
// this is not possible
var femaleClub = FemaleClub("Blue", "Australia")
Create secondary constructor in parent class which calls primary constructor:
open class Club(name: String, country: String) {
constructor(): this("Name", "Country")
}
But also you won't be able to call FemaleClub("Blue", "Australia").
To be able to call constructor with parameters of a subclass FemaleClub("Blue", "Australia") you need explicitly define them in primary constructor of the subclass and call parent's primary constructor, i.e.:
class FemaleClub(name: String, country: String): Club(name, country) {}
There are some problems with your code:
FemaleClub does not have a constructor which accepts two arguments, even if the base class has one.
The primary constructor of the inherited class should call the primary constructor of the base class. Because your base class accepts two non-nullable arguments, you have to provide them to it, otherwise your code wont compile.
These issues can be fixed in the following way:
class FemaleClub(name: String, country: String): Club(name, country) {
}
About Kotlin classes and inheritance, read here Classes and Inheritance and here Kotlin Inheritance.
You can use at least four approaches to get what you want:
First approach (default values):
open class Club(var name: String = "Blue", var country: String = "Australia") { }
public class FemaleClub(): Club() { }
fun main() {
var femaleClub = FemaleClub()
println("RESULT: ${femaleClub.country}")
}
// RESULT: Australia
Second approach (passing values):
open class Club(var name: String, var country: String) { }
class FemaleClub(): Club("Green", "NZ") { }
fun main() {
var femaleClub = FemaleClub()
println("RESULT: ${femaleClub.country}")
}
// RESULT: NZ
Third approach (init block):
open class Club(name: String, country: String) {
var name: String = "Blue"
var country: String = "Australia"
init {
this.name = name
this.country = country
}
}
class FemaleClub(): Club("Green", "NZ") { }
fun main() {
var femaleClub = FemaleClub()
println("RESULT: ${femaleClub.country}")
}
// RESULT: NZ
Fourth approach (class sec.constructor):
open class Club {
var name: String
var country: String
constructor(name: String = "Blue", country: String = "Australia") {
this.name = name
this.country = country
}
}
class FemaleClub(): Club() { }
fun main() {
var femaleClub = FemaleClub()
println("RESULT: ${femaleClub.country}")
}
// RESULT: Australia
In FemaleClub you are not specifying what should be passed to Club as its 2 String arguments, name and country.
And you are not specifying anywhere that FemaleClub takes 2 strings as constructor arguments, so the last line wont work either.
You need to invoke the constructor (primary/secondary) from the class you want to inherit from.
Club only has one constructor, the primary constructor. It takes two strings as parameters. Club does not have an empty constructor.
So, it cannot be invoked as Club().
I would propose two changes.
1) Make name and country of Club properties
open class Club(name: String, country: String)
2) Declare parameters in FemaleClub's primary constructor
Since you want to be able to specify values for name and country when you instantiate FemaleClub it would be a good idea to give the primary constructor of FemaleClub name and country as parameters.
Additionally I would recommend using named parameters when passing on the values, since you can easily mix up the two strings (resulting in passing name as country and country as name.
class FemaleClub(name: String, country: String): Club(name = name, country = country)
This question already has an answer here:
Is there a way to get didSet to work when changing a property in a class?
(1 answer)
Closed 4 years ago.
Using Playground.
I have the following structure:
struct Foo {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
I declared an instance from it as property observer with default values as "James" for the name and 33 for the age:
var myFoo = Foo(name: "James", age: 33) {
willSet {
print("my foo will be: \(newValue)")
}
didSet {
print("my foo was: \(oldValue)")
}
}
When trying to edit the name of the instance:
myFoo.name = "John"
I could see on the log:
my foo will be: Foo(name: "John", age: 33)
my foo was: Foo(name: "James", age: 33)
which is my expectation. However, when declaring Foo as a class instead of structure: class Foo and running the exact same code, nothing will appear on the log.
What is the reason behind it?
Because a class is a reference type, whereas a struct is a value type.
Suppose Foo is a class. myFoo is merely a reference. Saying myFoo.name = "John" mutates the object in place; no new value is assigned to myFoo, so the setter observer has nothing to observe.
Suppose Foo is a struct. That's a value type. It cannot be mutated in place. Thus, saying myFoo.name = "John" actually replaces the struct in the variable myFoo with a new struct; that sets the variable and the setter observer fires.
That is also why you can say myFoo.name = "John" for a class even if myFoo was declared with let (i.e. the object is merely mutated in place), but you can't do that with a struct — you have to have declared myFoo with var so that the variable value can be replaced.
In Swift, it is possible to convert certain literals to other types automatically:
let _: Double = 1 // Int literal to Double variable
However, when it comes to structs, compiler refuses to perform a similar conversion:
struct User {
var id: Int
var name: String
}
let _: User = (id: 778, name: "Pete") // error: cannot convert value of type '(id: Int, name: String)' to specified type 'User'
Defining an initialiser with respective fields does not help either.
Is there a way to omit the explicit initialiser of a structure, when the type is clear from the context?
In Swift, the only literals that can be used to instantiate a type are Int, Double, String, Bool, and Array's and Dictionary's. Tuples are not allowed.
Swift aims to be a strongly typed langage, so you should use:
let user = User(id: 778, name: "Pete")
Which is both explicit and short, and not more verbose than what you are looking for.
However, you can conform any of your type to any ExpressibleBy<LiteralType>Literal. Here is a fun way to achieve a similar behavior to tuple initialization:
struct User {
var id: Int
var name: String
}
extension Person: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (String, Any)...) {
let elements = Dictionary(uniqueKeysWithValues: elements)
self.id = elements["id"]! as! Int
self.name = elements["name"]! as! String
}
}
let user: User = ["id": 778, "name": "Pete"]
Which is both unsafe and inefficient.
A safer but less convenient way is using a static factory method:
extension User {
static func t(_ tuple: (Int, String)) -> User {
User(id: tuple.0, name: tuple.1)
}
}
let user: User = .t((id: 778, name: "Pete"))
Another interesting thing is that in certain contexts, the compiler will allow the use of a tuple which exactly matches an initializer signature to be used, for instance when using map:
let persons = [(id: 778, name: "Pete")]
let users = persons.map(User.init)