3

Im using Swift 3

Wondering whether any method is available to check if all the properties in an object has value / nil

Eg:

class Vehicle {
var name : String?
var model: String?
var VIN: String?
}

let objCar = Vehicle()
objCar.name = "Volvo"

if objCar.{anyProperty} ! = nil {
//Go to other screen
}

Im in search of the {anyProperty} method where it returns true only if I have values for all properties of objCar. In our case, objCar has no model and VIN and so {anyProperty} is false and will come out of if loop

Pls advice

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Jack
  • 193
  • 3
  • 10
  • 1
    Does it even make sense to have them be optional in the first place? A `Car` ***needs*** to have a `name`, `model,` and `VIN`. Also `model` and `VIN` can't change, so it doesn't make sense to have them be `var` variables – Alexander Jun 21 '17 at 23:26
  • Hi Alexander, thanks for pointing out - but that was just an example I was referring to understand what my requirement was. Sorry couldn't find better example at that moment – Jack Jun 22 '17 at 16:20

5 Answers5

10

I'm not sure if this should be used for production apps, as I'm not very familiar with reflection in Swift (using Mirror) and if it has any negative aspects (performance, etc).

Here goes:

class Vehicle {
    var name : String?
    var model: String?
    var VIN: String?
}

let objCar = Vehicle()
objCar.name = "Volvo"
objCar.model = "242DL"
objCar.VIN = "123456"

let hasMissingValues = Mirror(reflecting: objCar).children.contains(where: {
    if case Optional<Any>.some(_) = $0.value {
        return false
    } else {
        return true
    }
})

print(hasMissingValues) 

hasMissingValues will be false in the above example (all properties of Vehicle are set). Comment the line where model is set, for example, and the value of hasMissingValues will now be true.


Note: There may be a better way to compare $0.value (of type Any) to nil. Also, this works for properties of any type (not just String) of the class.

paulvs
  • 11,963
  • 3
  • 41
  • 66
  • It works with `$0.value as Any? != nil` or `let _ = $0.value as Any?` instead of `case Optional.some(_) = $0.value`. – Jeffery Thomas Jun 22 '17 at 00:23
  • 1
    @JefferyThomas, those comparisons aren't working for me. `$0.value` is of type `Any`. For the child where value is not set (i.e. is `nil`), printing `$0.value` prints out `nil`, and printing `$0.value as Any?` prints `Optional(nil)`. However, neither of these when compared with `nil` (using ==) gives `true`. It must be because Swift doesn't naturally allow a variable of type `Any` to be set to nil, even though an `Any` can hold any type, including an `Optional`, see [this question](https://stackoverflow.com/questions/34644128/why-non-optional-any-can-hold-nil). – paulvs Jun 22 '17 at 00:35
  • 1
    Sorry my bad, I'll leave the original comment even though it's wrong. What do you think of `Mirror(reflecting: $0.value).descendant("some") != nil`? I think it has a clearer meaning than the case comparison. – Jeffery Thomas Jun 22 '17 at 01:20
  • Good One. Mirror works for all types except closures I beleive. In my case I have few closures to check the nil values – Jack Jun 22 '17 at 16:41
9

I would strongly recommend against this. State validation is something which should happen from inside a class. From inside the class, you should know better how to check validity.

class Vehicle {
    var name: String?
    var model: String?
    var VIN: String?

    func isReadyToAdvance() -> Bool {
        return name != nil && model != nil && VIN != nil
    }
}

let objCar = Vehicle()
objCar.name = "Volvo"

if objCar.isReadyToAdvance() {
    // Go to other screen
}

If there are subclasses with different rules for isReadyToAdvance() they can override that method.


If isReadyToAdvance() doesn't make sense for the base class, then add it as an extension.

extension Vehicle {
    func isReadyToAdvance() -> Bool {
        return name != nil && model != nil && VIN != nil
    }
}

@iPeter asked for something a bit more compact when there are lots of properties.

extension Vehicle {
    func isReadyToAdvance() -> Bool {
        // Add all the optional properties to optionals
        let optionals: [Any?] = [name, model, VIN]
        if (optionals.contains{ $0 == nil }) { return false }

        // Any other checks

        return true
    }
}
Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117
6

If you have a lot of fields, you can use this approach:

struct S {
    let x: String?
    let y: Int
    let z: Bool

    func hasNilField() -> Bool {
        return ([x, y, z] as [Any?]).contains(where: { $0 == nil})
    }
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
1

The better solution is to make struct Equatable and compare with empty etalon

struct Vehicle: Equatable {
    var name : String?
    var model: String?
    var VIN: String?
}

var objCar = Vehicle()
objCar.name = "Volvo"

if objCar != Vehicle() {
    //Go to other screen
}

For cases if you do not want to compare all parameters by yourself or try to not use features that degrade performance (as Mirror)

Roman Filippov
  • 2,289
  • 2
  • 11
  • 17
0

The best solution for this problem is

let mirror = Mirror(reflecting: yourStruct)
let hasNilValues = mirror.children.contains(where: { "\($0.value)" == "nil" })