Swift checking nil using if-let from a class level - swift

I've been using nested if let(s) to check nils. For example, a class that contains name and age
class Person {
var name: String?
var age: Int?
}
if let p = Person() as? Person {
if let name = p.name {
}
if let age = p.age {
}
}
When I'm unsure if a variable is empty/null, is it necessary to use if let on everything and everytime? just trying to make sure if im doing it right.

You need if-let only when you need to check for nil and use the unwrapped value. Otherwise you can simply check for nil using != or == operator.
Also there is no need to explicitly typecast p to Person using as? Person, since the type can be inferred already.
let p = Person()
if p.name != nil {
}
if p.age != nil {
}

You do not need to check anything
p = Person()
creating a new Person object with no initial values means that all properties are nil so the following checks are unnecessary, we already know they are nil
if let name = p.name {
if let age = p.age {
I would suggest you add a constructor with parameters so that you can set the properties directly
Either with mandatory values so you don't have to check for nil afterwards
init(name: String, age: Int) {
self.name = name
self.age = age
}
or with optional values
init(name: String?, age: Int?) {
self.name = name
self.age = age
}
but then you need to check
var name: String?
var age: Int?
//...other code
let p = Person(name: name, age: age)
if let personName = p.name {
}
if let personAge = p.age {
}

Related

Error when delegating initialization in swift struct

I am trying to use another initalizer when some edge case happens during initialization, but I realized that once I delegate initialization to another initalizer, I can't treat that initializer like I would a normal one, instead I have to delegate initialization in all code branches.
(real use case is to initialize a struct containing email and name from a ASAuthorizationAppleIDCredential But because this object only returns the email and name the first time the user signs up with Apple, I have to check if it contains it, if no then an initalizer is called, that can create one by loading it from NSUbiquitousKeyValueStore. I ended up solving this by creating a function returning this struct instead of an init function)
The errors I get are
'self' used before 'self.init' call or assignment to 'self'
'self.init' isn't called on all paths before returning from initializer
These come up when I try to use a different initializer in a guard statement's else closure. Here is a simplified example:
struct Test {
var name : String
var age : Int
init(name : String) {
self.name = name
self.age = -1
}
init(name : String, age : Int?) {
guard let age = age else {
self.init(name: name)
return
}
self.name = name
self.age = age
}
}
Another solution I found is to create an init that can be called after the guard statement, in this case:
init(name: String, age : Int) {
self.name = name
self.age = age
}
and then use this init instead of assigning values directly.
My question is: why is this the case, and where is this behaviour mentioned or explained? The only documentation I found about this was the swift docs here:
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID215
Not sure about other languages but in Swift you have to first initialise existing initialiser before initialising extra property/properties.
import UIKit
struct Test {
var name : String
var age : Int
init(name : String) {
self.name = name
self.age = -1
}
init(name : String, age : Int?) {
self.init(name: name)
if let age = age {
self.name = name
self.age = age
}
}
}
let x = Test(name: "x", age: nil)
let y = Test(name: "y", age: 18)
let z = Test(name: "z")

'super.init' isn't called on all paths before returning from initializer. But it's useless

I have base class
class Company {
var list: [Employee] = []
var name: String = ""
func getProduct() -> Product {
return Product(name: "Phone")
}
required init(_ list: [Employee], _ name: String) {
self.list = list
self.name = name
}
convenience init?(_ employee: Employee?, _ name: String) {
if ( employee == nil || name.isEmpty) { return nil }
if let emp = employee as? Employee {
self.init([emp], name)
}
self.init(employee, name)
}
}
And inherited
class FoodCompany: Company{
var qualityCertificate: String
required init(_ list: [Employee], _ name: String) {
self.list = list
self.name = name
}
init? (_ employee : (String?, String?), _ name: String, _ qualityCertificate: String ) {
if ( employee.0 == nil || employee.1 == nil ) {
return nil
}
if ( name.isEmpty || qualityCertificate.isEmpty) {
return nil
}
self.qualityCertificate = qualityCertificate
let fName = employee.0 as? String
let lName = employee.1 as? String
}
}
In failable init i have an error
'super.init' isn't called on all paths before returning from initializer.
But how i can add super.init calling, if i have no data for it? Maybe i don't understand something?
Maybe i need to add init without parameters?
At the moment you are going to create a subclass of an object you have to call super somewhere.
I don't know what Product and Employee is but to fulfill the inheritage initialization rules you have to write something like this
class Company {
var list: [Employee] = []
var name: String = ""
func getProduct() -> Product {
return Product(name: "Phone")
}
required init(_ list: [Employee], _ name: String) {
self.list = list
self.name = name
}
convenience init?(_ employee: Employee?, _ name: String) {
guard let emp = employee, !name.isEmpty else { return nil }
self.init([emp], name)
}
}
class FoodCompany: Company {
var qualityCertificate: String
required init(_ list: [Employee], _ name: String) {
self.qualityCertificate = ""
super.init(list, name)
}
init?(_ employee : (String?, String?), _ name: String, _ qualityCertificate: String ) {
if name.isEmpty || qualityCertificate.isEmpty { return nil }
guard let fName = employee.0, let nName = employee.1 else { return nil }
self.qualityCertificate = qualityCertificate
super.init([Employee(fName: fName, nName: nName)], name)
}
}
Side note: Omitting the parameter labels in init methods is not a good Swift practice.
You can call super.init once you have set the properties of the sub-class. So first change the required init in the subclass to
required init(_ list: [Employee], _ name: String) {
qualityCertificate = ""
super.init(list, name)
}
Then for the other init I would start by getting the values from the tuple using a guard statement
guard let firstName = employee.0, let lastName = employee.1 else { return nil }
because now we will either have to variables with (non-nil) values that we can use later or the init will return nil
Then we can use those two variables to create an Emplyoee instance and send to super.init
super.init([Employee(firstName, lastName)], name)
If we also add the other validation to the guard statement the full init becomes
init?(_ employee: (String?, String?), _ name: String, _ qualityCertificate: String) {
guard let firstName = employee.0, let lastName = employee.1, !name.isEmpty, !qualityCertificate.isEmpty) {
return nil
}
self.qualityCertificate = qualityCertificate
super.init([Employee()], name)
}

Cast Protocol<> Any to String (or others)

I have a class called User()
class User {
var name: String?
var email: String?
var id: String?
var identification_number: String?
var phone_number: NSMutableArray?
var user_group: String?
var date: NSDate?
}
I want to get all of the variables in the class and their respective values. I am trying to use Mirror in this case.
func updateProfile(user: User) {
let mirror = Mirror(reflecting: user)
for child in mirror.children {
print("\(child.label!), \(child.value)")
}
}
My question is, how can I convert child.value to any other datatype, say String ?
I only got to find out that child.value belongs to the Protocol 'Any'
child.value has the Any type. Casting from Any to an optional poses some problems, fortunately Sandy Chapman gave a very nice solution in this post.
With his function, the code would look like this:
func castToOptional<T>(x: Any) -> T? {
return Mirror(reflecting: x).descendant("Some") as? T
}
func updateProfile(user: User) {
let mirror = Mirror(reflecting: user)
for child in mirror.children {
print("\(child.label!), \(child.value)")
if let stringVal = castToOptional(child.value) as String? {
print("Unwrapped a string: \(stringVal)")
} else if let stringVal = child.value as? String {
print("Found a non-optional string: \(stringVal)")
}
}
}
So if you're looking for strings, you need to look for both optional and non-optional ones. This applies to all types you need to check.
Create a protocol for extending Optional<Any> type to return it's non-optional-value:
private protocol AnyOptional {
var objectValue: Any? { get }
}
extension Optional: AnyOptional {
var objectValue: Any? {
switch self {
case .None:
return nil
case .Some(_):
return self! as Any
}
}
}
Thereafter you can use AnyOptional protocol as a type, and cast Any? objects to AnyOptional, thereafter allowing us to make use of the .objectValue property of AnyOptional
class User {
var name: String?
var email: String?
var id: String = "Default ID" // Lets try also with one non-optional
var identification_number: String?
var phone_number: NSMutableArray?
var user_group: String?
var date: NSDate?
}
var myUser = User()
myUser.name = "John"
myUser.phone_number = ["+44", "701 23 45 67"]
func updateProfile(user: User) {
let mirror = Mirror(reflecting: user)
for child in mirror.children {
let value : Any = (child.value as? AnyOptional)?.objectValue ?? child.value
switch(value) {
case let obj as String: print("String item: User.\(child.label!) = " + obj)
case let obj as NSMutableArray: print("NSMutableArray item: User.\(child.label!) = \(obj)")
case let obj as NSDate: print("NSDate item: User.\(child.label!) = \(obj)")
case _ : print("Non-initialized optional item: User.\(child.label!) = \(value)")
}
}
}
Which yields the following output
updateProfile(myUser)
/*
String item: User.name = John
Non-initialized optional item: User.email = nil
String item: User.id = Default ID
Non-initialized optional item: User.identification_number = nil
NSMutableArray item: User.phone_number = (
"+44",
"701 23 45 67"
)
Non-initialized optional item: User.user_group = nil
Non-initialized optional item: User.date = nil */
The benefit of using this solution is that it will "unwrap" optional non-nil values of child.value (without the "Optional(...)" padding) as well as values of child.value that are not optional, without the need of separate "unwrapping" for the two cases. In the switch case above, you can handle whatever non-nil property of the User object that you need to work with, not just as String but any of the types in your User class. The obj property in the switch case will be of the non-optional type of each of the non-nil properties of your class. The default case corresponds to optionals with value nil (not assigned).

Guard when setting multiple class properties in Swift 2

It's trivial enough to do something like this:
class Collection {
init(json: [String: AnyObject]){
guard let id = json["id"] as? Int, name = json["name"] as? String else {
print("Oh noes, bad JSON!")
return
}
}
}
In that case we were using let to initialize local variables. However, modifying it to use class properties causes it to fail:
class Collection {
let id: Int
let name: String
init(json: [String: AnyObject]){
guard id = json["id"] as? Int, name = json["name"] as? String else {
print("Oh noes, bad JSON!")
return
}
}
}
It complains that let or var needs to be used but obviously that isn't the case. What's the proper way to do this in Swift 2?
In the if let, you are unwrapping values from the optional as new local variables. You can’t unwrap into existing variables. Instead, you have to unwrap, then assign i.e.
class Collection {
let id: Int
let name: String
init?(json: [String: AnyObject]){
// alternate type pattern matching syntax you might like to try
guard case let (id as Int, name as String) = (json["id"],json["name"])
else {
print("Oh noes, bad JSON!")
self.id = 0 // must assign to all values
self.name = "" // before returning nil
return nil
}
// now, assign those unwrapped values to self
self.id = id
self.name = name
}
}
This is not specific to class properties - you can’t conditionally bind into any variable, for example this doesn’t work:
var i = 0
let s = "1"
if i = Int(s) { // nope
}
Instead you need to do:
if let j = Int(s) {
i = j
}
(though of course, in this case you’d be better with let i = Int(s) ?? 0)

Dictionary of swift classes with strings

I have this class Identity and a dictionary of instances of them with Strings as keys. I want to access one of the instances by a string and change some of its properties. I'm trying to use a switch statement to access the instances in the dictionary depending on the value of a string.
class Identity {
let provider: String
let uid: String?
let token: String?
let name: String?
init(provider: String){
self.provider = provider
self.uid = nil
self.token = nil
self.name = nil
}
}
var identities = [String:Identity]()
identities["twitter"] = Identity(provider: "twitter")
identities["twitter"].uid = "131241241241"
identities["twitter"].name = "#freedrull"
let provider: String = "twitter"
var i: Identity? {
switch provider {
case "twitter":
return identities["twitter"] as Identity?
case "facebook":
return identities["facebook"] as Identity?
case "soundcloud":
return identities["soundcloud"] as Identity?
default:
return nil
}
}
if i != nil {
i.name = "tony"
}
I get an error about assigning i.name to "tony". Do I need to cast i to an Identity somehow? I thought it already was.
You have declared i as an Optional:
var i: Identity? // ...
So it's still an Optional. It's not an Identity. It's an Optional wrapping an Identity. But you can't do anything to an Optional - until you unwrap it. Unwrap it, to get at the Identity. You have:
if i != nil {
i.name = "tony"
}
Instead:
if let i = i {
i.name = "tony"
}
Or:
if i != nil {
i!.name = "tony"
}
Both are ways of unwrapping the Optional.
Or, test and unwrap all in one move:
i?.name = "tony"
Then you'll have a new problem; you have declared name as a constant. You can't change a constant! You have:
let name: String?
Instead:
var name: String?
[By the way, much of this code is redundant:
init(provider: String){
self.provider = provider
self.uid = nil
self.token = nil
self.name = nil
}
uid, token, and name are all Optionals, so they are already nil. You can cut those three lines.]