How can I check if a property has been set using S

2020-02-14 10:48发布

问题:

Some of my models have optional properties. I'm trying to write a method that can evaluate if they've been set.

Below is an attempt, but I can't figure out how to determine a nil value from an Any object [edit: (the child variable is of type Any)]. It doesn't compile.

func allPropertiesHaveValues(obj: AnyObject) -> Bool {
    let mirror = Mirror(reflecting: obj)
    for child in mirror.children {
       let value = child.value
       if let optionalValue = value as? AnyObject? { //Does not compile
          if optionalValue == nil {
             return false 
          }
       }
    }
    return true
}

Edit:

I forgot to clarify that the child value in the above example is always of type Any. The Any type is difficult in that it cannot be compared to nil and a cast to AnyObject always fails. I've tried to illustrate it in the playground below.

var anyArray = [Any]();

var optionalStringWithValue: String? = "foo";
anyArray.append(optionalStringWithValue);

var nilOptional: String?
anyArray.append(nilOptional)

print(anyArray[0]);   // "Optional("foo")\n"
print(anyArray[1]);   // "nil\n"

if let optionalString = anyArray[0] as? AnyObject {
    //will always fail
    print("success")
}

//if anyArray[1] == nil {  //  will not compile

//}

回答1:

Obsolete:

You can simply check if the optional value is nil or not :

func allPropertiesHaveValues(obj: AnyObject) -> Bool {
   let mirror = Mirror(reflecting: obj)
   for child in mirror.children {
     //child.value being an optional
     if child.value == nil {
       return false
     }
    }
   return true
}

Edit:

To check if an Any object is optional and contains a value or not using reflection :

let optionalString : String? = "optional string"
let any : Any = optionalString

//First you will need to create a mirror of the any object
let mirror = Mirror(reflecting : any)

//Then you can check the display style to see if it's an optional
if mirror.displayStyle == .Optional {
    //If it is, check the count of its children to see if there is a value or not
    if mirror.children.count == 0 {
        print("I don't have a value")
    }
    else {
        print("I have a value")
    }
}

Here is a playground example (based on yours):

var anyArray = [Any]()

var optionalStringWithValue: String? = "foo"
anyArray.append(optionalStringWithValue)

var nilOptional: String?
anyArray.append(nilOptional)

let string = "string not optional"
anyArray.append(string)

print(anyArray[0])   // "Optional("foo")\n"
print(anyArray[1])   // "nil\n"
print(anyArray[2])   // "string not optional\n"

let mirrorOptionalWithValue = Mirror(reflecting: anyArray[0])

if mirrorOptionalWithValue.displayStyle == .Optional
    && mirrorOptionalWithValue.children.count == 1 {
    print("Is an optional and contains a value")
}

let mirrorOptionalWithoutValue = Mirror(reflecting: anyArray[1])

if mirrorOptionalWithoutValue.displayStyle == .Optional &&
    mirrorOptionalWithoutValue.children.count == 0 {
    print("Is an optional but is nil")
}

let mirrorNotAnOptional = Mirror(reflecting: anyArray[2])

if mirrorNotAnOptional.displayStyle != .Optional {
    print("Is not an optional")
}


回答2:

I used @ebluehands technique of reflecting the Any value to modify the original function. It cycles through the properties with an initial mirror, then reflects each one individually using displayStyle to determine if the property is optional.

func allPropertiesHaveValues(obj: AnyObject) -> Bool {
    let mirror = Mirror(reflecting: obj)
    for child in mirror.children {
        let value: Any = child.value
        let subMirror = Mirror(reflecting: value)
        if subMirror.displayStyle == .Optional {
            if subMirror.children.count == 0 {
                return false
            }
        }
    }
    return true
}


回答3:

Another option is create a extension.

extension NSManagedObject {

    func checkIfAllRequiredMembersAreSet() -> Bool {
        let attributes = self.entity.attributesByName

        for (attribute, value) in attributes {

            if value.attributeValueClassName != nil {
                let v: AnyObject? = self.valueForKey(attribute)
                if !value.optional && v != nil {
                    return false
                }
            }
        }

        return true
    }
}


回答4:

Based on this answer, I recommend using if case Optional<Any>.some(_).

I did something recently to make sure I have at least one optional set. Here's an example to make sure all are set. You can paste into playgrounds:

import Foundation

struct SomeError: Error {
    let code: Int?
    let message: String?
    let errorDescription: String?

    var allValuesSet: Bool {
        for aChild in Mirror(reflecting: self).children {
            if case Optional<Any>.some(_) = aChild.value {
                continue
            } else {
                return false
            }
        }
        return true
    }
}

let errorTest = SomeError(code: nil, message: "failed", errorDescription: nil)
let errorTest2 = SomeError(code: -1, message: "failed", errorDescription: "missing information")

print("is valid: \(errorTest.allValuesSet)") //is valid: false
print("is valid: \(errorTest2.allValuesSet)") //is valid: true