Enum associated values to init differents types of an object

Hi,


I am currently implementing a class Device. That will represent different devices in the real world such as Mobile phone or iBeacon for my application.

I ‘d like to implement these different types as an enum with associated values in it, for the different parameters related to the type (see code example).

I know I could do sub-class, which might be better but I am facing another issue which is.

I am generating a REST API with Swagger, and if I do it with sub-class, I will have to make many many changes. That is why I would prefer to do it with enum. But I am wondering if I am doing it right ? Is it the best way, what would you suggest to me ?


I am pretty new to Swift and didn't get the chance to use enum often.

here is the code

struct Device {
    var name : String
    var deviceId : String?
    let type : Type
  
    init(mobileName: String, platform : String, deviceToken: String, location : CLLocation?){
        self.name = mobileName
        self.type = .Mobile(platform: platform, deviceToken: deviceToken, location: location)
    }
  
    init(pinName: String, location : CLLocation){
        self.name = pinName
        self.type = .Pin(location: location)
    }
  
    init(beaconName: String, uuid: UUID, major: NSNumber, minor: NSNumber){
        self.name = beaconName
        self.type = .Beacon(uuid: uuid, major: major, minor: minor)
    }
}


enum Type {
    case Mobile(platform:String, deviceToken: String, location : CLLocation?)
    case Pin(location:CLLocation?)
    case Beacon(uuid:UUID, major:NSNumber, minor:NSNumber)
}


Depending on the type of the Device, I 'd like to initialize it with the proper parameters.



I appreciate your concern and the time you accord to read my post !


Cheers,

Whenwens

Why do you have some location as optional (location : CLLocation?) and not others in the init ?


I would suggest you change the name Type to something more meaningfull as DeviceType

Two points:


1. If your "Device" type represents an actual device, as opposed to a kind of device, you should be careful about using a struct (value type) rather than a class (reference type). An instance of a value type is passed around by value (of course), which means that there can be multiple "copies" of the value at any one time, in different places within your executing code. Unless you are very, very careful, it becomes unclear which of these values represents the true state of the device.


With a class, you pass around a reference instead, and that means all references access the same, true state information.


2. I think it's fine to use an enum like this, but it's going to get cumbersome if your associated values get more numerous or more complex. They are basically additional properties, but you don't get the flexibility that real properties have.


The alternative approach is to use a protocol Device instead of your type Device. It would define the behaviors common to all conforming instances. Then define one class (or struct, if that's what you really want) for each kind of device, conforming to the Device protocol. Note that protocols can provide default implementations for some of their method (write the default implementations in an extension of the protocol), and the conforming classes can define their own properties and behavior on top of that.


When you come to use a variable of type Device, instead of switching on the enum cases (case .Mobile, case .Pin, case .Beacon), you can switch on the actual type (case is MobileDevice, case is PinDevice, case is BeconDevice, for example).


Whether this improves your code or not depends on what you're doing and how, but it's an alternative to consider.


And please follow Claude's advice about naming things. There is a document discussing the recommended approach in detail at this web site:


swift.org/documentation/api-design-guidelines

2. I think it's fine to use an enum like this, but it's going to get cumbersome if your associated values get more numerous or more complex.

There’s definitely a balance to be struck here but in some cases I think the right answer is to have an enum where each case has a bunch of associated values. If those associated values get out of hand, you can bundle them up into a struct.

For example, here’s a type from a test project I’m working on:

enum Authentication {
    case noMachineNoUser
    case certificateMachineNoUser(PKCS12Identity, CertificateDetails)
    case secretMachineNoUser(SharedSecret)
    case noMachineNameAndPasswordUser(NameAndPassword)
    case certificateMachineNameAndPasswordUser(CertificateDetails, NameAndPassword)
    case secretMachineNameAndPasswordUser(SharedSecret, NameAndPassword)
    case noMachineCertificateUser(CertificateDetails, PKCS12Identity)
    case certificateMachineCertificateUser(CertificateDetails, PKCS12Identity)
    case secretMachineCertificateUser(SharedSecret, CertificateDetails, PKCS12Identity)
}

where each of the associated values is a struct that holds one or more other values. I like the way this falls out in my code because:

  • I don’t need extensibility, which makes me reticent to go down the protocols route

  • It’s impossible to construct one of these enums without providing all the right values

  • It’s hard to use of these enums without consuming all the right values

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Eskimo, I jump in the discussion to ask a question.


I appreciate also to have the associated value being a struct. Very convenient.

But I use to set a label: don't you find it useful in this case ?

Hi,


Thanks for your reply.

Because depending on the Type of the device they are suppose to have differents attributes. A .Mobile would be a mobile phone, which has a platform like : "ios 9.0" or whatever. And a .Beacon doesn't have these attributes instead they have something like their UUID, Major and minor, and they can't locate themselves, that is why I wouldn't put a CLLocation attribute for Beacons.


Ok I am taking your advice and will change it to DeviceType.

I understand you don't need CLLocation for Beacon.

My point was about the int :

init(pinName: String, location : CLLocation): why not optional here ?

Hello QuinceyMorris !



1. Yes, I see your point. In my application, there will be many Device with different DeviceType.
Let's say device1 would be a Device of DeviceType .Mobile with a name, a type(=.Mobile) and since he is of .Mobile DeviceType he has access to more attributes related to his type. In this case, he has a platform : "ios 9", a deviceToken : "x", and a location or not.

Device2 would be a .Pin DeviceType, he has access to Device attributes, name and type(=.Pin) and since he is a Pin he can provide a location or not.

At the end let's say device3 is also a .Mobile, so he has name, type(=.Mobile) and a platform : "ios 10", a deviceToken : "y" and a location or not.

And let's say that we want a device4. var device4 = device3. But we just want to change his deviceToken because everything else is the same.


Each of them will be different that is why I went for Struct.


2. Yes, I am struggling with the associated values now. I don't find a way to access them individually(like if I just want the platform properties of a Device) and they are definately not flexible as a real properties.

The main point of using enum was to avoid to handle the impact of new classes in my API generated with an already defined YAML file via Swagger.


Thanks for your alternative approach, I will probably go for sub-classes or this protocol approach.


I will definately follow Claude's advices and yours. Many thanks for your reply.

Hi Eskimo !


How do you access the property of your enum Authentification, let's say a case certificateMachineNoUser and you want the CertificateDetails ?


In my code, I can only access the Type as a whole. But I want more control on it, like for .Mobile. I will probably just get and use the "location" property.


(this is not a good example because here location is nil)

But let's say I want to get only the platform property and do something with it.

var d = Device.init(mobileName: "my mob", platform: "ios", deviceToken: "jkol", location: nil)
d.Type. ???

When I write d.Type. xCode doesn't suggest me anything. So how do I access the platform and make use of it ? Is it possible ?


I like those two points you said.

  • I don’t need extensibility, which makes me reticent to go down the protocols route
  • It’s impossible to construct one of these enums without providing all the right values

That is exactly why I went for enum at first. But I really need to have access to the associated values inside the enum, I wonder if it is possible.

It is because a Pin Device needs a location. A Mobile Device can provide it later but not a Pin.

I want to implement a geolocalized app, where we can create Device of differents types, like mobile, pin, and beacon. Since a pin doesn't have the capacity to provide his location by his own. It needs to be settle at his initalization.

But I use to set a label: don't you find it useful in this case ?

I toggle back and forth with regards

enum
labels. In the specific example I posted earlier I don’t have labels for two reasons:
  • I want to make construction as compact as possible

  • The unique types means that there’s no risk of getting the associated values in the wrong order.

In other circumstances I might well use labels.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

In my case I want to apply the authentication value to an NEVPNProtocol subclass instance. There’s two ways I could have approached this:

  • Write a computed property to, for example, extract the

    NameAndPassword
    or return nil if there isn’t one of this case.
  • Extract the

    NameAndPassword
    value via a
    switch
    statement.

I chose to do the latter because it’s safer (due to the

switch
statement’s exhaustiveness check). So, for example:
switch self {
    case .noMachineNoUser,
         .certificateMachineNoUser,
         .secretMachineNoUser,
         .noMachineCertificateUser,
         .certificateMachineCertificateUser,
         .secretMachineCertificateUser:
        break
    case .noMachineNameAndPasswordUser(let nameAndPassword),
         .certificateMachineNameAndPasswordUser(_, let nameAndPassword),
         .secretMachineNameAndPasswordUser(_, let nameAndPassword):
        try nameAndPassword.save(to: proto)
}

There’s two things I like about this:

  • If I add another

    enum
    case, I have to handle it here or I get an error.
  • Modern versions of Swift let me have multiple cases that all bind the same value (

    nameAndPassword
    in this example).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn,

Modern versions of Swift let me have multiple cases that all bind the same value (nameAndPassword in this example).


Very interesting,; I didn't notice.

I've tested and found a limitation : if a value is bound in a pattern, it must be present in all.

But that remains an interesting Swift3 evolution.

Enum associated values to init differents types of an object
 
 
Q