Compiler errors for struct init in Swift - 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 ...
}

Related

String as Member Name in Swift

I have an array of strings and a CoreData object with a bunch of variables stored in it; the strings represent each stored variable. I want to show the value of each of the variables in a list. However, I cannot find a way to fetch all variables from a coredata object, and so instead I'm trying to use the following code.
ListView: View{
//I call this view from another one and pass in the object.
let object: Object
//I have a bunch of strings for each variable, this is just a few of them
let strings = ["first_name", "_last_name", "middle_initial" ...]
var body: some View{
List{
ForEach(strings){ str in
//Want to pass in string here as property name
object.str
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
}
}
}
So as you can see, I just want to pass in the string name as a member name for the CoreData object. When I try the code above, I get the following errors: Value of type 'Object' has no member 'name' and Expected member name following '.'. Please tell me how to pass in the string as a property name.
CoreData is heavily based on KVC (Key-Value Coding) so you can use key paths which is much more reliable than string literals.
let paths : [KeyPath<Object,String>] = [\.first_name, \.last_name, \.middle_initial]
...
ForEach(paths, id: \.self){ path in
Text(object[keyPath: path]))
}
Swift is a strongly typed language, and iterating in a python/javascript like approach is less common and less recommended.
Having said that, to my best knowledge you have three ways to tackle this issue.
First, I'd suggest encoding the CoreData model into a dictionary [String: Any] or [String: String] - then you can keep the same approach you wanted - iterate over the property names array and get them as follow:
let dic = object.asDictionary()
ForEach(strings){ str in
//Want to pass in string here as property name
let propertyValue = dic[str]
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
Make sure to comply with Encodable and to have this extension
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
Second, you can hard coded the properties and if/else/switch over them in the loop
ForEach(strings){ str in
//Want to pass in string here as property name
switch str {
case "first_name":
// Do what is needed
}
}
Third, and last, You can read and use a technique called reflection, which is the closest thing to what you want to achieve
link1
link2

How to initialize a struct with dictionaries in Swift

I want to initialize every time a struct with dictionaries. Later, I'm going to use its properties instead a dictionary's keys and values - it seems rather easier. However, when I try the code below, it tells me that "Return from initializer without initializing all stored properties" and "1. 'self.one' not initialized" and "2. 'self.two' not initialized". My question is how to initialize a struct from a dictionary, so that I have basically a struct with the contents of the dictionary? Or how to transform it into struct?
struct Blabla {
var one: String
var two: [Int]
init(three: [String: [Int]]) {
for i in three {
self.one = i.key
self.two = i.value
}
} ERROR! - Return from initializer without initializing all stored properties
}
struct Blabla {
var one: String
var two: [Int]
init(three: [String: [Int]]) {
one = ""
two = []
for i in three {
self.one = i.key
self.two = i.value
}
} ERROR! - Return from initializer without initializing all stored properties
}
for in clause may have zero runs, in which case struct properties will not be initialized. You have to provide default values (or emit fatalError if you really need to).
While I think your example is pure synthetical, there is no need to loop through array, you can set properties to its last entry.
The issues is that if three is an empty Dictionary, the instance properties one and two don't get initialised. Also, you are overwriting the properties in each iteration of the for loop and the compiler cannot guarantee that there will be any iterations of the loop in compile-time, hence the compiler error.
You could make the initialiser failable to account for this by checking that the dictionary actually contains at least one key-value pair and assigning that first key-value pair to your properties.
struct Blabla {
var one: String
var two: [Int]
init?(three: [String: [Int]]) {
guard let key = three.keys.first, let value = three[key] else { return nil }
one = key
two = value
}
}
However, you should rethink what it is that you are actually trying to achieve, since with your current setup you have a mismatch between your init input values and the properties of your struct.
This code should compile, but it feels unsafe to me to initialize a Struct in this way because:
It assume your dictionary has values in it.
Your stored properties will always have the last value you looped through.
In order to pull values out to satisfy the compiler you need to force unwrap them. (With Dávid Pásztor's guard-letting approach, this can be avoided)
struct Blabla {
var one: String
var two: [Int]
init(three: [String: [Int]]) {
self.one = three.keys.first!
self.two = three[three.keys.first!]!
}
}
let input = ["pizza": [1,2]]
let l = Blabla(three: input)
If I were you I would let the memberwise initializer that you get for free do its thing and provide either a specialized initializer to handle your case of taking a Dictionary as input or move that parsing to another function/class/etc....
The compiler error is clear: If the dictionary is empty the struct members are never initialized. But the code makes no sense anyway as each iteration of the dictionary overwrites the values.
Maybe you mean to map the dictionary to an array of the struct
struct Blabla {
let one: String
let two: [Int]
}
let three = ["A":[1,2], "B":[3,4]]
let blabla = three.map{Blabla(one: $0.key, two: $0.value)}
print(blabla) // [Blabla(one: "A", two: [1, 2]), Blabla(one: "B", two: [3, 4])]
struct blabla{
var a : string
var b : [int] = []
init(_ data: [string:[int]]){
// whatever you want to do
}
}

initialise Codable result with var

I need to initialise the result of JSONDecoder in a var object defined outside the API Call.
apiService.GETAPI(url: apiStr, completion: {(success, result) in
if(success) {
let apiResponse = result.value as! NSDictionary
let data = apiResponse.value(forKey: "data") as! NSDictionary
do {
let profileData = try JSONSerialization.data(withJSONObject: data.value(forKey: "profile"), options: .prettyPrinted)
print(profileData)
let profile = try JSONDecoder().decode(Profile.self, from: profileData)
print(profile.name)
}
catch {
print("json error: \(error.localizedDescription)")
}
}
completion(success)
})
But I am unable to do so. This is my Profile Codable struct
struct Profile : Codable {
var id : Int
var name : String
var member_id : Int
var category_id : Int
var membership_id : Int
var information : String
var city : String
var love_count : Int
var vendor_price : String
var locality_name : String
var phone : [String]
var address : [Address]?
var status : Int?
var managed_by_wmg : Int?
}
How to do it. I need it to be var since I need to perform operation and access it later in the other code.
As we have already discussed in the comments of your question, you need to declare variable of Profile type outside the closure.
So now the problem become "How to change Profile struct, so I can declare variable of it?"
For this you have several options. I will list them and comment on which to choose and why.
Option 1
Add default values to all the variables of Profile struct as so: var id : Int = 0, so you can declare it later with var profile: Profile?. With this solutions you need to have some knowledge about the objects you are expecting and their default values in a way they make sense to your business logic. Example for such default values are "" for string or 0 integer. This might seem good but yet their is better solution.
Option 2
Make ALL the variables of Profile optional as so: var id : Int?. This might sounds strange at first, but is the optimal solution for working with server data. So this method has several benefits:
It will never crash, no matter what the server sends you as a data
All the variables have default values equaling nil
You do not need to think about your business logic and what default value suits your needs
However, with this method there is one drawback: some properties that with your logic could never be nil will have nil initial value. So you need to add validations for unwrapping the nullable properties.
Option 3 Add explicit unwrapping to the variable type as so var id : Int!. Note this also adds nil as initial value to your properties, but tells the compiler that in every time you are gonna use them, they will not be nil. I would personally not recommend this method, because if the variable is not received from the server, or the parsing fails, or something else unexpected happens, your app will crash with found nil while unwrapping optional value. For reference you might have noticed all the IBOutlets are defined this way.
Having said all that, I would recommend you going with Option 2, which best suits server communication, but the choice is yours!
Happy coding.
I have found two solutions to my question. Thanks to #dvp.petrov.
Solution 1 var profile : Profile! comes handy but it becomes difficult if this is not an optional variable and have to be used at many places. One will have to put lot of checks and unwrap it every time. This is easy to use when one has to use it at very less places
Solution 2 : Create an init function giving default values to all the variable and then you can create var profile : Profile(). This way your object will not be nil and will be able to use it at places.
struct Profile : Codable {
var id : Int
var name : String
var member_id : Int
init() {
self.id = 0
self.name = ""
self.member_id = 0
}

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

Swift optional values during initialization preventing default initializer inheritance

In Swift:
1) If you provide a default value for all of the stored properties in a class, then you inherit the default initializer, ie - init().
-- AND --
2) A property of any optional type defaults to the value of nil, ie - var shouldBeNill: String? //should initially be nill
-- THEREFORE --
I would expect this code to work:
class Product {
let name: String?
}
let product = Product()
But when I type it in as a playground, I get the error: "class Product has no initializers".
Why isn't Product inheriting the default initializer init()? I know I can make this work by explicitly setting let name: String? = nil, but I'm unsure why I have to do this. Is this an error on Swift's side, or is there something I am not quite grasping?
You are on the right track. The issue here is actually the let vs var.
let declares the property constant. In this case Product would have an optional constant name of type String with no initial value, and this of course makes no sense.
The compiler complains about a lacking init() function because let properties are allowed to be set once during init(), as part of object construction, if not defined already in declaration eg.
let name: String = "Im set!" // OK
let name: String? = nil // OK, but very weird :)
let name = "Im set!" // OK, type not needed, implicit.
let name: String // OK, but needs to be set to a string during init()
let name: String? // OK, but needs to be set to string or nil during init()
let name // Not OK
The Swift Programming Language - Constants and Variables