Why Do I Need to Declare an Optional Value as Nil Explicitly in Struct - Swift

Why do I need to declare an optional value as nil explicitly in Struct - Swift

Both Classes and Structs need to have all property values set when they are initialized. This can be done either through explicit default values or by setting a value in the designated initializer.

However, Structs differ in the fact that they have an automatically generated memberwise initializer.

When you don't define a value for someProperty explicitly, your struct has one initializer only: the automatically generated memberwise one.

If you do provide a default value, you get two: one that takes no arguments, and one that takes a value for someProperty as an argument

From the docs:

All structures have an automatically-generated memberwise initializer,
which you can use to initialize the member properties of new structure
instances. Initial values for the properties of the new instance can
be passed to the memberwise initializer by name:

let vga = Resolution(width: 640, height: 480)

Unlike structures, class instances do not receive a default memberwise
initializer. Initializers are described in more detail in
Initialization.

Why this struct with optional values returns nothing?

This statement is not succeeding:

guard let whomenc:String = $0["whomenc"] as? String else {return}

either because the key "whomenc" doesn't exist or the type isn't a String. The guard statement then returns from the closure and does nothing more for that dictionary.

Since you can create a Notification even if some values are nil, you could remove the guard statements and pass the (possibly nil) values to the Notification initializer:

obj.forEach {
let type = $0["type"] as? String
print("type = \(type ?? "nil")")
let dp = $0["dp"] as? String
print("dp = \(dp ?? "nil")")
let name = $0["name"] as? String
print("name = \(name ?? "nil")")
let postimg = $0["postimg"] as? String
print("postimg = \(postimg ?? "nil")")
let whomenc = $0["whomenc"] as? String
print("whomenc = \(whomenc ?? "nil")")
let notification = Notification(type: type, dp: dp, name: name, postImage: postimg, whomenc: whomenc)
self.notifiArray.append(notification)
print("notifiArray.count = \(self.notifiArray.count)") // this satement doesn't gets executed.
}

Why do you have to initialize a constant that is declared an optional?


If I don't assign a value to an optional why isn't its value nil and the if let catch it?

It would be, if you initialized it as nil, either like this:

let favoriteSong: String? = nil

Or like this:

let favoriteSong: String?
favoriteSong = nil

But you didn't do either. Thus, because you might still have done the second one, the compiler gives an error when you try to use the uninitialized variable.

Think about it this way: if

 let favoriteSong: String?

...automatically meant

 let favoriteSong: String? = nil

...then it would be impossible to say this:

let favoriteSong: String?
favoriteSong = "Rock Around the Clock"

...because this is a constant — it cannot be changed. But we need to be able to say that! It's legal syntax. Therefore that is not what

 let favoriteSong: String?

...means. Do you see?

The rule for var is different, because it's changeable. Because of that, you get automatic default initialization to nil and can change it later.

But for let, you only get one shot at initialization, so you don't get the automatic default which would prevent you from doing your own initialization in the next line; you have to initialize explicitly.

Constant unassigned optional will not be nil by default


Yes it's correct

An optional variable doesn't need to be manually initialized. If you read it before having populated it does contain nil.

From Apple docs

If you define an optional variable without providing a default value, the variable is automatically set to nil for you [...]

On the other hand the compiler does force you to manually initialize an Optional constant (let) before you can read it.

Unlike a variable, the value of a constant cannot be changed once it is set. Attempting to do so is reported as an error when your code is compiled [...]

Why?

A constant can be written only once. It doesn't need to happened on the same line it is initialized but it must happened before your read it.

E.g. this code works fine

let num: Int?
num = 1
print(num)

However if the compiler had put a temporary nil value inside num then the constant would have been wrote twice. Which is against the concept of constant.

let num: Int?
print(num) // nil ??? <- this can't work!
num = 1
print(num) // 1

Another example

This code snippet works fine

func printArea(width: Int?, height:Int?) {
let area: Int?
if let width = width, height = height {
area = width * height
} else {
area = nil
}
print(area)
}

Again, if the compiler had put a temporary nil value inside area then...

func printArea(width: Int?, height:Int?) {
let area: Int?
print(area) // not possible! area is going to change in a moment
if let width = width, height = height {
area = width * height
} else {
area = nil
}
print(area)
}

Swift nested optional value types (structs) and modification of properties

First, you should reduce the number of Optionals in the system. There are various ways to deal with Optional-collections (mutating helper methods like you suggested for instance), but Optional-overuse creates a lot of unneeded complexity. It is very rare that a Collection of any kind should be an Optional. That only makes sense if nil and "empty" mean different things (and that is very rare).

Rather that wrapping the entire data model around a specific JSON API, convert the JSON into the data model you want. For example, here is a JSON model that includes a required Int and may or may not include an Array, but internally we want to treat "missing array" as "empty." We also want to strip empty arrays before sending them.

import Foundation

let json = Data("""
{
"y": 1
}
""".utf8)

struct X {
var y: Int
var z: [String]
}

extension X: Codable {
enum CodingKeys: String, CodingKey {
case y, z
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
y = try container.decode(Int.self, forKey: .y)
z = try container.decodeIfPresent([String].self, forKey: .z) ?? []
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(y, forKey: .y)
if !z.isEmpty {
try container.encode(z, forKey: .z)
}
}
}

let decoder = JSONDecoder()
print(try decoder.decode(X.self, from: json))

let encoder = JSONEncoder()
print(String(data: try encoder.encode(X(y: 1, z: [])), encoding: .utf8)!)

This moves all the work into two methods rather than spreading it out all over the program every time you access the data model. Custom codables are still a bit tedious to write (and can introduce subtle bugs in the encoder step), so if you have a lot of them you should look at SwiftGen which can write them for you.

If you really wanted to keep track of whether a key were missing versus empty (so you might re-encode the same way it was sent to you), then I would probably shadow the optional properties this way:

struct X: Codable{
enum CodingKeys: String, CodingKey {
case y
case _z = "z"
}

var y: Int
private var _z: [String]? // The actual `z` we got from the JSON
var z: [String] { get { return _z ?? [] } set { _z = newValue } }

init(y: Int, z: [String]?) {
self.y = y
self._z = z
}
}

The "real" z is stored in _z and is available to be reserialized, but the rest of the program never sees an Optional.

Another somewhat common technique is to create an Adapter layer that converts a "JSON-compatible" struct to an internal data model and back. This allows your internal data model to be slightly different than the JSON if that's convenient.

You also can of course create helper methods, but the real key to all of this is to not allow Optionals to leak into the rest of your program that aren't really optional. If there must be complexity somewhere in the system, put it at the point of parsing/encoding, not at the point of use.

Swift Initialize Struct with optional stored properties

Use default values:

init(busName: String, website: String? = nil, address: String? = nil) {
self.name = busName
self.web = website
self.address = address
}

Then you can call the init like this:

_ = Business(busName: "Foo")
_ = Business(busName: "Foo", website: "www.foo.bar")
_ = Business(busName: "Foo", address: "bar")
_ = Business(busName: "Foo", website: "www.foo.bar", address: "bar")

When should I use optionals and when should I use non-optionals with default values?

The choice depends on what you model.

If a property of the object that you model may be absent completely, e.g. a middle name, a name suffix, an alternative phone number, etc., it should be modeled with an optional. A nil optional tells you that the property is not there - i.e. a person does not have a middle name or an alternative phone number. You should also use optional when you must distinguish between an empty object and a missing object.

If a property of the object must be set, and has a meaningful default, use an non-optional with a default:

class AddressList {
var addresses : [Address]
var separator : String = ";"
...
}

If users of your class need to change the separator, they have a way to do that. However, if they do not care about the separator, they can continue using the default without mentioning it in their own code.

Why create Implicitly Unwrapped Optionals, since that implies you know there's a value?

Consider the case of an object that may have nil properties while it's being constructed and configured, but is immutable and non-nil afterwards (NSImage is often treated this way, though in its case it's still useful to mutate sometimes). Implicitly unwrapped optionals would clean up its code a good deal, with relatively low loss of safety (as long as the one guarantee held, it would be safe).

(Edit) To be clear though: regular optionals are nearly always preferable.

Non-optional values in Swift-structure returns optional values


Did I get it wrong?

Yes. Your variables are optionals - the ! operator is a force unwrap operator, so your variables are implicitly unwrapped. If you don't want your variables to be optionals, just drop !.

The ! operator exists in Swift to facilitate bridging from Objective-C, for instance for IBOutlet variables. Using ! operator aside from that is a bad practice.



Related Topics



Leave a reply



Submit