having an enum issue.

in the tutorial I am following, which is for Swift 4.2 there is code, that when converted to my app's context looks like this:


public enum PropertyFamily: String, ClassFamily {
    case CNStringProp = "CNStringProp"
    case CNIntProp = "CNIntProp"
    case CNBoolProp = "CNBoolProp"
    case CNFloatProp = "CNFloatProp"
    case CNClampedFloatProp = "CNClampedFloatProp"
    case CNColor_rgbaProp = "CNColor_rgbaProp"
    case CNPoint_xyProp = "CNPoint_xyProp"
    case CNSize_whProp = "CNSize_whProp"
    case CNSize_whdProp = "CNSize_whdProp"
    case CNVector_xyzProp = "CNVector_xyzProp"
    
    static var discriminator: Discriminator = .type
    
    func getType() -> AnyObject.Type {
        switch self {
        case .CNStringProp:
            return CNStringProp.self
        case .CNIntProp:
            return CNIntProp.self
        case .CNBoolProp:
            return CNBoolProp.self
        case .CNFloatProp:
            return CNFloatProp.self
        case .CNClampedFloatProp:
            return CNClampedFloatProp.self
        case .CNColor_rgbaProp:
            return CNColor_rgbaProp.self
        case .CNPoint_xyProp:
            return CNPoint_xyProp.self
        case .CNSize_whProp:
            return CNSize_whProp.self
        case .CNSize_whdProp:
            return CNSize_whdProp.self
        case .CNVector_xyzProp:
            return CNVector_xyzProp.self
        }
    }
}


this is an attempt to support saving Arrays with subclasses of a single class, without steamrolling the class type on load.

everything that starts with 'CN' is an NSObject subclass. the goal of the enum is to load a String property and turn it into a class type that can be used per record while loading an array.


there is a protocol that defines ClassFamily (supplied by the tutorial):


/// To support a new class family, create an enum that conforms to this protocol and contains the different types.
protocol ClassFamily: Decodable {
    /// The discriminator key.
    static var discriminator: Discriminator { get }
    
    /// Returns the class type of the object coresponding to the value.
    func getType() -> AnyObject.Type
}

/// Discriminator key enum used to retrieve discriminator fields in JSON payloads.
enum Discriminator: String, CodingKey {
    case type = "type"
}


I'm learning as I go, but I've hit a snag. I suspect that it's an issue that comes from Swift 4.2 moving to Swift 5

on return in the 'getType' function I have an error that states: 'Enum case 'ClassName' cannot be used as an instance member'

there is a 'fix it' button. and when that happens,

this: return CNSize_whProp.self

turns into this : return PropertyFamily.CNSize_whProp.self


which sets off even worse errors:

'Cannot convert return expression of type 'PropertyFamily' to return type 'AnyObject.Type''


I know the compiler is barking up the wrong tree. What it transforms the code into is gibberish.

What I do not know, is what the real problem is. The example is explicit, but it does not go into any detail about how to handle the AnyObject.type type when things go wrong.

What is the Type you expect from CNStringProp.self ?


The following is accepted, but with a warning of probable crash Cast from 'PropertyFamily' to unrelated type 'AnyObject.Type' always fails

case .CNStringProp:

return PropertyFamily.CNStringProp.self as! AnyObject.Type

Seemd Swift 5 (I do not know since which version of Swift), shadows the same identifiers with enum cases with the instance methods of the enum. (Though accessing the enum case without prefix is not accepted as suggested by the error message.)


You can change your enum case identifiers to distinguish them from type names:

public enum PropertyFamily: String, ClassFamily {
    case cnStringProp = "CNStringProp"
    case cnIntProp = "CNIntProp"
    case cnBoolProp = "CNBoolProp"
    case cnFloatProp = "CNFloatProp"
    case cnClampedFloatProp = "CNClampedFloatProp"
    case cnColor_rgbaProp = "CNColor_rgbaProp"
    case cnPoint_xyProp = "CNPoint_xyProp"
    case cnSize_whProp = "CNSize_whProp"
    case cnSize_whdProp = "CNSize_whdProp"
    case cnVector_xyzProp = "CNVector_xyzProp"
    
    static var discriminator: Discriminator = .type
    
    func getType() -> AnyObject.Type {
        switch self {
        case .cnStringProp:
            return CNStringProp.self
        case .cnIntProp:
            return CNIntProp.self
        case .cnBoolProp:
            return CNBoolProp.self
        case .cnFloatProp:
            return CNFloatProp.self
        case .cnClampedFloatProp:
            return CNClampedFloatProp.self
        case .cnColor_rgbaProp:
            return CNColor_rgbaProp.self
        case .cnPoint_xyProp:
            return CNPoint_xyProp.self
        case .cnSize_whProp:
            return CNSize_whProp.self
        case .cnSize_whdProp:
            return CNSize_whdProp.self
        case .cnVector_xyzProp:
            return CNVector_xyzProp.self
        }
    }
}

(In a popular Swift convention, you use lower case identifiers for case names.)


Or you can prefix your type names with the packege name they are defined:

public enum PropertyFamily: String, ClassFamily {
    case CNStringProp = "CNStringProp"
    case CNIntProp = "CNIntProp"
    case CNBoolProp = "CNBoolProp"
    case CNFloatProp = "CNFloatProp"
    case CNClampedFloatProp = "CNClampedFloatProp"
    case CNColor_rgbaProp = "CNColor_rgbaProp"
    case CNPoint_xyProp = "CNPoint_xyProp"
    case CNSize_whProp = "CNSize_whProp"
    case CNSize_whdProp = "CNSize_whdProp"
    case CNVector_xyzProp = "CNVector_xyzProp"
    
    static var discriminator: Discriminator = .type
    
    func getType() -> AnyObject.Type {
        switch self {
        case .CNStringProp:
            return TheModuleName.CNStringProp.self
        case .CNIntProp:
            return TheModuleName.CNIntProp.self
        case .CNBoolProp:
            return TheModuleName.CNBoolProp.self
        case .CNFloatProp:
            return TheModuleName.CNFloatProp.self
        case .CNClampedFloatProp:
            return TheModuleName.CNClampedFloatProp.self
        case .CNColor_rgbaProp:
            return TheModuleName.CNColor_rgbaProp.self
        case .CNPoint_xyProp:
            return TheModuleName.CNPoint_xyProp.self
        case .CNSize_whProp:
            return TheModuleName.CNSize_whProp.self
        case .CNSize_whdProp:
            return TheModuleName.CNSize_whdProp.self
        case .CNVector_xyzProp:
            return TheModuleName.CNVector_xyzProp.self
        }
    }
}

In the example above `TheModuleName` needs to be the module name (usually the name of the project or framework) of the following types defined.


I prefer using different case names than type names.

ultimately, I was not able to make other parts of the solution work. It was something I found online to save a Homogenous Array, without stripping object type from the various object instances. Basically, even the donloadable 'finished' code... didn't compile, and it's way beyond me to try to figure it out. Although I did make an attempt and I feel like I gained some mildly deeper understanding of encoders/decoders... I don't feel that I have come away with a comprehensive understanding. I looked for a Codable 201 type coursework, would have helped.


while lamenting this failed solution, which had me doing things that I was very uncomfortable doing, I hit on the main problem: there's no good way to interperet the incoming elements of an array in order to determine what class they are (indeed, that goes against the security needs answered by Codable and NSSecureCoding.)


and it occurred to me that a wrapper class would work better, require less "acrobatic plumbing" in features of the language that are alien to me, and be fairly straight forward to setup. And the main thing about the wrapper class... it would allow me an opportunity to examine each object and figure out which class it is.


the wrapper class has 2 properties :

1. a 'type' property. an enum of Strings... just a set of keys really, that indicate class.

2. a property of the same type as the array in question (in this case CNProperty)


when it comes time to encode the array, you simply map the array to an array of wrapper objects. the wrapper, when it's object is set, automatically sets the type. and you encode that.


when you decode the Wrapper array, the wrapper loads the type first. then it does a switch on the type, and loads the wrapped object as the appropriate type.


it's another simple map to set the array from the wrapped objects.


it's not trivial work, and I am not exactly happy that developers are sandbagged by security features in this way, but the new solution is fairly portable, easy to understand, and works without monkeying around in the guts of the language. It's a far cry from the solution I found online, and I feel like again... the next level of swift understanding has eluded me. But I'm the only person I know doing this, I'm self trained, and the Swift.org website is a place where people use opaque language, and throw around indescipherable buzzwords. One day, swift will stop being the wild west, and there will actually be human readable documents explaining all of this. meanwhile : my Object based solution:



this is the object that I wrote that saves the array:

the array is called 'props' take a look at the lines that encode and decode it. nothing fancy. but it works.

public class CNProps : CNDagObj {
    public var props: [CNProperty] = []
    
    private enum CodingKeys: String, CodingKey {
        case props
    }
    public override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(props.map({CNPropertyWrapper(prop: $0)}) , forKey: .props)
    }
    
    required public init(from decoder: Decoder) throws {
        super.init()
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        let wrappers = try container.decode([CNPropertyWrapper].self, forKey: .props)
        props = wrappers.map({ $0.prop })
    }
    
    public init(props: [CNProperty]) {
        self.props = props
        super.init()
    }
    
    public init(position: CGPoint, props: [CNProperty]) {
        self.props = props
        super.init(position: position)
    }
}


here's my wrapper class (i'm leaving out the various classes we are wrapping... it'l will just bog down the discussion CNProperty is the superClass, everything else is a subclass):


the Choice to mirror the exact names of the classes in my enum, against the norms of lowercaing the first letter... That was made to help fight my dyslexia a bit. I might go back and change it, now that I know I've got the pattern worked out.


public enum propType : String, Codable{
    case CNProperty = "CNProperty"
    case CNFloatProp = "CNFloatProp"
    case CNStringProp = "CNStringProp"
    case CNBoolProp = "CNBoolProp"
    case CNIntProp = "CNIntProp"
    case CNClampedFloatProp = "CNClampedFloatProp"
    case CNColor_rgbaProp = "CNColor_rgbaProp"
    case CNPoint_xyProp = "CNPoint_xyProp"
    case CNSize_whProp = "CNSize_whProp"
    case CNSize_whdProp = "CNSize_whdProp"
    case CNVector_xyzProp = "CNVector_xyzProp"
}


 
class CNPropertyWrapper : Codable {
    var type : propType = .CNProperty
    var prop : CNProperty! = nil
    
    private enum CodingKeys: String, CodingKey {
        case prop
        case type
    }
    
    init(prop: CNProperty) {
        self.prop = prop
        self.setType()
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(type, forKey: .type)
        try container.encode(prop, forKey: .prop)
    }
    
    required public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        type = try container.decode(propType.self ,forKey: .type )
        
        switch type{
        case .CNProperty:
            prop = try container.decode(CNProperty.self ,forKey: .prop )
        case .CNFloatProp:
            prop = try container.decode(CNFloatProp.self ,forKey: .prop )
        case .CNStringProp:
            prop = try container.decode(CNStringProp.self ,forKey: .prop )
        case .CNBoolProp:
            prop = try container.decode(CNBoolProp.self ,forKey: .prop )
        case .CNIntProp:
            prop = try container.decode(CNIntProp.self ,forKey: .prop )
        case .CNClampedFloatProp:
            prop = try container.decode(CNClampedFloatProp.self ,forKey: .prop )
        case .CNColor_rgbaProp:
            prop = try container.decode(CNColor_rgbaProp.self ,forKey: .prop )
        case .CNPoint_xyProp:
            prop = try container.decode(CNPoint_xyProp.self ,forKey: .prop )
        case .CNSize_whProp:
            prop = try container.decode(CNSize_whProp.self ,forKey: .prop )
        case .CNSize_whdProp:
            prop = try container.decode(CNSize_whdProp.self ,forKey: .prop )
        case .CNVector_xyzProp:
            prop = try container.decode(CNVector_xyzProp.self ,forKey: .prop )
            
        }
    }
    
    func setType(){
        switch self.prop{
        case is CNFloatProp:
            self.type = .CNFloatProp
        case is CNStringProp:
            self.type = .CNStringProp
        case is CNBoolProp:
            self.type = .CNBoolProp
        case is CNIntProp:
            self.type = .CNIntProp
        case is CNClampedFloatProp:
            self.type = .CNClampedFloatProp
        case is CNColor_rgbaProp:
            self.type = .CNColor_rgbaProp
        case is CNPoint_xyProp:
            if self.prop is CNVector_xyzProp{
                self.type = .CNVector_xyzProp
            }else{
                self.type = .CNPoint_xyProp
            }
        case is CNSize_whProp:
            if self.prop is CNSize_whdProp{
                self.type = .CNSize_whdProp
            }else{
                self.type = .CNSize_whProp
            }
        default:
            break
        }
    }
}
having an enum issue.
 
 
Q