Swift initialise property at struct initialisation without initialiser - swift

In swift, structs have an automatically generated memberwise initializer.
This means the following struct can be initialised without me having to write an init.
struct Activity {
let name: String
let desc: String
let category: Category
let subcategory: Subcategory
let emoji: Character
let coordinate: CLLocationCoordinate2D
let creationTime: Date = Date()
let activityTime: Date
let id: UUID = UUID()
var comments: [Comment] = []
}
I have one single property called emojiwhich is computed by the subcategory. In other words, the value for emoji depends on the value of subcategory.
However this means that the value of emoji can only be assigned after the initialisation of subcategory.
How should I do this in code?
Approach 1:
Provide my own initialiser
init(name: String, desc: String, category: Category, subcategory: Subcategory,
coordinate: CLLocationCoordinate2D, activityTime: Date) {
self.name = name
self.desc = desc
self.category = category
self.subcategory = subcategory
self.coordinate = coordinate
self.activityTime = activityTime
self.emoji = AllCategories.categories[category]?[subcategory] ?? "❌"
}
I don't like this approach as it adds a lot of unecessary code that will only grow if I add more properties... I would like to use the generated initialiser of the struct. On the other hand, the code is still very simple.
Approach 2:
Use a lazy varthat is only computed when called.
lazy var emoji: Character = {
AllCategories.categories[category]?[subcategory] ?? "❌"
}()
I also don't really like this approach, I find it overly complex for what I am trying to do. Also this makes emojia varinstead of letwhich it is not, I want it to remain a constant. On the other hand, I can continue using the automatically generated initialiser.
Questions:
What other possibilities do I have?
If there are none, which of the 2 approches is the best?

This sounds like a great chance to use computed properties:
var emoji: Character {
AllCategories.categories[category]?[subcategory] ?? "❌"
}
Although it is declared a var, you can't actually set it. It's just how computed properties must be declared.
The expression AllCategories.categories[category]?[subcategory] ?? "❌" will be evaluated every time you use the property. It's not a too time-consuming expression, so IMO it's fine.

Related

Initialize a variable with nil vs implicitly unwrapped optional

In Swift Guide https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
there is an example of using Implicitly Unwrapped Optionals
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
And I am wondering if it is possible and what difference it would make if I made a small change:
var capitalCity: City = nil
Your code will simply not compile. Here's why.
By specifying var capitalCity: City, you're explicitly saying that your variable is non-optional. It can only ever hold a valid City. It can never be optional, so it can never hold nil.
Meanwhile, anytime you use ? or !, you are defining an optional type. These can be nil, and in fact are nil by default.
So in your example from the guide, var capitalCity: City! is saying the following:
capitalCity is declared as an implicitly unwrapped optional type (the !), meaning that you, as the developer, are declaring it to always have a valid City type whenever it is called.
That said, until you set a City to it, it defaults to being nil.
When you should use optionals comes down to how you want to use your properties. In the original example, from what little we see, capitalCity is only set in the class init. So there wouldn't be a reason for it to be optional. So for that example, it could exist like the other property: let capitalCity: City.
A lot depends on how you want the properties to exist and work when you're using your object. For instance, making something optional signals at least two things:
The property may not have a valid value in the class' lifecycle.
You may not be setting it when you initialize your class. (Remember, an optional initializes to nil)
I hope this helps you understand optionals some more.

What's the point of READ-only variables when you have LET?

For example:
var dogName : String {
return "Buster"
}
VS..
let dogName = "Buster"
Let's say we're declaring each of these at the top level of a class as instance properties. Are these just two ways of doing the same thing? If not, what's the point of having a read-only variable?
Thanks
Let me try to sum up what the other answers are saying while also adding missing information that I think is critical in order to understand this.
Properties
Properties are simply values that are associated with an object and may be queried in a trivial amount of time without the need (or ability) for parameters like methods have.
Stored Properties
When you create a stored property, whether with let or var, the value assigned at any given point in time will be stored in memory, which is why it is called a stored property.
var name = "Matt"
For variables using var, the value is stored in memory in a way that makes it mutable (editable). You can reassign the value at will and it will replace the previous value stored in memory.
let name = "Matt"
For constants using let, the value is also stored in memory, but in such a way that it may not be changed after the first time assigning to it.
Computed Properties
Computed properties are not stored in memory. As ganzogo says in the comments, computed properties act similarly to methods, but do not take parameters. When deciding when to use a computed property or a function with no parameters, the Swift API Design Guidelines recommend using a computed property when it will simply create or fetch, and then return the value, provided that this takes a trivial amount of time.
var fullName: String {
return firstName + lastName
}
Here, we assume that firstName and lastName are already properties on the object. There is no sense of initialization with this property because it is not stored anywhere. It is fetched on demand every time. That is why there is no sense to doing anything like the following:
var dogName : String {
return "Buster"
}
This has no benefit over a stored property except that no memory will be used in storing the String "Buster".
In fact, this is a simplified version of computed properties. You will notice that the Swift Language Guide describes the use of both get and set in a computed property. set allows you to update the state of other variables when one sets a computed variable. For example:
var stored: Int
var computed: Int {
get {
return stored + 5
}
set {
stored = newValue - 5
}
}
Some useful applications of this were pointed out by Rajan's answer, for example getting and setting volume from width, height, and depth.
A read-only computed var is just a computed var which specifies only a getter, in which case the get keyword and brackets are not required.
Read-Only for Access Control
When developing modules such as frameworks, it is often useful to have a variable only be modifiable from within that object or framework and have it be read-only to the public.
private var modifiableItem: String
public var item: String {
return modifiableItem
}
The idea here is that modifiableItem should only be mutable from within the object that defined it. The private keyword ensures that it is only accessible within the scope of the object that created it and making it a var ensures that it may be modified. The public var item, then, is a computed variable that is exposed to the public that enables anyone to read, but not mutate the variable.
As Hamish notes in the comments, this is more concisely expressible by using private(set):
public private(set) var item: String
This is probably the best way to go about it, but the previous code (using a private stored property and public computed one) demonstrates the effect.
let dogName = "Buster"
means that the dogName variable can't be changed later on once assigned "Buster" and it becomes constant
var dogName : String {
return "Buster"
}
It is a computed read only property where you can have some calculation which can be changed as it is a var but in a way defined below:
The computed property can be changed like
var dogName : String {
return "Stress"+"Buster"
}
Consider this example from Apple Docs
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
It will print
// Prints "the volume of fourByFiveByTwo is 40.0"
Here the volume is calculated when you initialize the object of struct Cuboid and is computed at run time. If it was let, then you have to initialize it before using by some constant.
If you want to read more about it, read the Computed Properties section here
In your example, they are 2 ways of doing the same thing. However, you can do a lot more with a computed property. For example:
var dogName: String {
return firstName + " " + lastName
}
Here, firstName and lastName might not be known at initialization time. This is not possible to do with a simple let property.
It might help you to think of a computed property as a method with no parameters.
A read-only property in a class/struct means that you can't change the value of the property for that instance of the class/struct. It prevents me from doing:
someObject.dogName = "Buddy" // This fails - read-only property
However, I can still do this:
var someVariable = someObject.dogName // someVariable now is "Buster"
someVariable = "Buddy" // This is OK, it's now "Buddy"
A let constant means you won't be changing the value of that specific constant in that block of code.
let someName = "Buster"
someName = "Buddy" // This fails - someName is a constant
There are two different cases:
1) Value type:
struct DogValueType {
var name: String
}
let dog1 = DogValueType(name: "Buster")
var dog2: DogValueType {
return DogValueType(name: "Buster")
}
let dog3: DogValueType = {
return DogValueType(name: "Buster")
}()
dog1 - dog3 can't be changed or mutated
dog1 & dog3 stores value
dog3 computes value each time you accessing it
2) Reference type:
class DogReferenceType {
var name: String
init(name: String) {
self.name = name
}
}
let dog4 = DogReferenceType(name: "Buster")
var dog5: DogReferenceType {
return DogReferenceType(name: "Buster")
}
let dog6: DogReferenceType = {
return DogReferenceType(name: "Buster")
}()
dog4 - dog6 can't be changed, but can be mutated
dog4 & dog6 stores reference to an object.
dog5 creates object each time you accessing it

Compiler errors for struct init in Swift

I have the following struct:
struct Person {
let name: String
let age: Int
let assets: Double
}
To initialize a person, I would like to pass it a dictionary that contains the name and age, as well as info to calculate the assets:
public init(info: [String : AnyObject]) {
if let name = info["name"] as? String {
self.name = name
}
if let age = info["age"] as? Int {
self.age = age
}
if let assets = info["assets"] as? [String : AnyObject] {
calculateAssets(assets: assets)
}
}
mutating func calculateAssets(assets: [String : AnyObject]) {
self.assets = 1+2+3.4 // really do this with info from the dictionary
}
With this setup, I get two compiler errors:
'self' used before all stored properties are initialized
Return from initializer without initializing all stored properties
Following the compiler suggestions, I added a default value for each property, and changed them to be a var:
struct Person {
var name: String = ""
var age: Int = 0
var assets: Double = 0.0
// init ...
}
And indeed, the compiler errors are gone.
But am I on the right track by making these corrections?
The problem is that in your init function you might initialize the variables but you can't be sure, since the conditions of the if statements can be false. This is why the compiler is giving you the errors (also because you are trying to call self.assets in another function when the variable might still not be initialized - so you at least need to change this one to a var).
If you can't be sure that the values in the info dictionary are valid you should change your variables to var.
Now you have two choices:
Give your variables a default value (like you did in your example)
Declare your variables as Optionals (like suggested by #Özgür).
Your choice depends on what makes more sense. If your default values make sense for your situation and you can work with the variables having those values then I'd go with that.
Otherwise, you should go with the Optionals. Optionals obviously have the plus that you don't need to initialize them but then the con is that you'll need to implicitly or explicitly unwrap them later when you want to use them (using ? or !).
If - for whatever reason - you can be sure that the values in the info dict are valid - you could also leave your name and age as constants (let) and just explicitly unwrap the dictionary values when you assign them:
self.name = info["name"] as! String
self.age = info["age"] as! Int
But as I said this would only work if info["name"] and info["age"] contain valid values, otherwise you will get a runtime exception. So the "default value" or "Optional" choices are your safe and clean options, whereas this is the quick and dirty way.
You got compiler error because your variables are not inited when you set them.(you define them as inited). That's why for second case when you init them to nil values, error is gone. For this conditions, Swift has optionals.Can be nil and a value at the same time. Check here. You can add ? and that makes the variable optional.
struct Person {
var name: String?
var age: Int?
var assets: Double?
// init ...
}

Why use an initializer? [duplicate]

This question already has answers here:
Why do we need to specify init method?
(3 answers)
Closed 6 years ago.
Newbie, coming from PHP. Be gentle.
Here's a swift struct with an initializer
struct Book {
let title: String
let author: String
let price: String
let pubDate: String
init(title: String, author: String, price: String, pubDate: String){
self.title = title
self.author = author
self.price = price
self.pubDate = pubDate
}
}
let HarryPotter = Book(title: "Harry Potter", author: "JK Rowling",
price: "30$", pubDate: "January 10th, 1998")
And here's a swift struct without an intializer
struct Book {
let title: String
let author: String
let price: String
let pubDate: String
}
let HarryPotter = Book(title: "Harry Potter", author: "JK Rowling",
price: "30$", pubDate: "January 10th, 1998")
If these both do the same thing, then what is the advantage of using an initializer?
In the second case, you're still using an initializer. You're using the default initializer, which was generated for you because you haven't specified any of your own.
Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values.
To answer the more general question of what initializes for: they encapsulate the initialization of an instance, and guarantee that you can never obtain an instance in a "half-baked" state. They're Swift's equivalent of PHP Constructors.
In your specific case, nothing, because all your initialiser is doing is setting the values. But, it's possible that you could have other code in the initialiser which actually does something.
If you want to include some logic beforehand you could do that in the constructor/initializer.
This is a bit of a guess since I'm a Ruby developer rather than Swift, but there might be times when you would want to run more (arbitrary) code inside of your initializer besides just assigning the argument values to instance variables. For example:
def initialize(attr)
#attr = attr
puts "I'm initializing!"
end
In the above case I've assigned the attribute and printed to stdout. I'm guessing in Swift all of that attribute assignment is being done automatically so if all you want to do is assign attributes you can avoid writing the initializer yourself and let Swift handle it.

Binding Swift properties to NSTableView?

I think I have programmed myself into a corner, but I'm hoping you all know a way out. I have a class...
class Card {
var order: Int? = -1
var tag: String = "0"
var comment: String?
var data : [String: NSNumber]
}
Ideally everything would be in data, which is a few strings and lots of numbers. I started with [String, String] but I found I was writing lots of code to cast and convert when I wanted to (say) compare one of those numbers to zero. Changing it to [String, NSNumber] simplified all that code, but now my tableViewDataSource becomes very complex because some of the data is in data and some is in a separate property like comment. I even tried [String, Any], but then everything had to be cast all the time to do anything.
I have a feeling I am missing something fundamental here. When working with NSTableViews, is there a simple way to use Swift properties that I'm missing? valueForKey: does not work, there's no easy way to do a reflection-like solution I know of, etc. Any suggestions?
You can only bind dynamic properties, and your class needs to inherit from NSObject or implement NSObjectProtocol. Additionally, nilable value-types aren't allowed, so you cannot bind Int?
ie.:
class Card: NSObject {
dynamic var order: Int = -1
dynamic var tag: String = "0"
dynamic var comment: String?
dynamic var data: [String: NSNumber]
}