Search code examples
swiftgenericsmeasurement

Generic measurement variable in Swift


I have this code

public class DoubleSensor: ObservableObject {
    private var cancellables: Set<AnyCancellable> = Set<AnyCancellable>()
    private(set) var sensor: any VEntity
    
    @Published public var state: doubleState = DefaultState.forDouble()
    
    public init(withSensor sensor: any VEntity) {
        self.sensor = sensor

    }
    
    private func updateData(fromState newState: doubleState) async {
        self.state = newState
    }
}

public final class TemperatureSensor: DoubleSensor {
    
    public var measurement: Measurement<UnitTemperature> {
        return Measurement(value: state.value, unit: .celsius)
    }
    
    public override init(withSensor sensor: any VEntity) {
        super.init(withSensor: sensor)
    }
}

public final class PercentageSensor: DoubleSensor {
    
    public var measurement: Measurement<UnitPercentage> {
        return Measurement(value: state.value, unit: UnitPercentage.percentage)
    }
    
    public override init(withSensor sensor: any VEntity) {
        super.init(withSensor: sensor)
    }
}

public final class EnergySensor: DoubleSensor {
    
    public var measurement: Measurement<UnitEnergy> {
        return Measurement(value: state.value, unit: .wattHours)
    }
    
    public override init(withSensor sensor: any VEntity) {
        super.init(withSensor: sensor)
    }
}

As you can see I have 3 class inheriting from DoubleSensor class (a small version of it). All of inheriting classes have a measurement variable specific to a measurement unit. How can I create a generic class instead specific ones?

I can create something like

public final class MeasurementSensor<T: Dimension>: DoubleSensor {

    public var measurement: Measurement<T> {
        return Measurement(value: state.value, unit: ???)
    }
    
    public override init(withSensor sensor: any VEntity) {
        super.init(withSensor: sensor)
    }

}

but I don't know how to set the unit value

Edit:

Thanks to Dávid Pásztor to suggesting me edits to improve the question:

VEntity is a protocol defined as

public protocol VEntity: Hashable, Equatable {
    var domain: String { get }
    var entityId: String { get }
    var entityName: String { get }
    var notificationName: NSNotification.Name { get }
}

doubleState is a typealias defined as

public typealias doubleState   = EntityState<Double, GenericAttributes>

where EntityState is an object representing an entity state with two generics properties: S (value) and A (attributes), but I don't think its definition (not small) is useful in this context

and GeneriAttribute is

public struct GenericAttributes: VAttribute {
    public let commonName: String
}

are your DoubleSensor subclasses only supposed to work with a hardcoded unit value? or should they work with any value of the same type?

My goal is to get rid of multiple inheriting classes and create just one that will contain a measurement of given unit. Ex:

let temperatureSensor: MeasurementSensor = MeasurementSensor(withSensor: sensor01, andUnit: UnitTemperature, measuringIn: .celsius)
let energySensor: MeasurementSensor = MeasurementSensor(withSensor: sensor02, andUnit: UnitEnergy, measuringIn: .kilowattHours)

This is just pseudocode to give an idea of final result. I hope now question is more clear.

Edit 2:

Suggested by Alexander (thanks) I think we can cut off all irrelevant stuff keeping only what we need, so we can use this class

public final class MeasurementSensor {
    var value: Double
    
    public var measurement: Measurement<UnitEnergy> {
        return Measurement(value: value, unit: .kilowattHours)
    }
    
    init(value: Double) {
        self.value = value
    }
}

as example, but keeping that pseudocode above as a final result


Solution

  • If you want a generic class where the measurement unit is of a certain type, you simply need to make your class generic over the Dimension type and inject the actual unit you want to use when creating a new instance.

    final class MeasurementSensor<UnitType: Dimension> {
      var value: Double
      var unit: UnitType
    
      var measurement: Measurement<UnitType> {
        return Measurement(value: value, unit: unit)
      }
    
      init(value: Double, unit: UnitType) {
        self.value = value
        self.unit = unit
      }
    }
    

    Usage:

    let kwhSensor = MeasurementSensor(value: 0, unit: UnitEnergy.kilowattHours)
    let celsiusSensor = MeasurementSensor(value: 0, unit: UnitTemperature.celsius)