Swiftier? implementation of Measurement and Unit in Foundation


(Joanna Carter) #1

An important requirement of the design of measurements and units was that it had to work in Objective-C as well, where protocols do not have as many capabilities as they do in Swift. The bridging into Swift can only do so much, and frankly it didn’t seem to be the case that traditional inheritance was really much of a problem for this API in the first place.

I appreciate that this is an important design goal and was attempting to follow that ethos but still just hoping to make more use of protocols and structs as recommended.

We did make some changes upon import to Swift. Most importantly, Measurement is indeed a struct in Swift while a class in Objective-C.

I have continued to work with my version and have now got to the stage where all of the types are either protocols and classes in Objective-C and protocols and structs in Swift.

What is more, I have managed to place a lot of bridging logic in protocol extensions, so that it doesn't have to be repeated as boilerplate in each of the implementing structs.

Here is the code that I have created so far; please let me know if I am "flogging a dead horse" here :slight_smile:

///////////////////
public protocol UnitConverter : _ObjectiveCBridgeable
{
func baseUnitValue(fromValue value: Double) -> Double

func value(fromBaseUnitValue baseUnitValue: Double) -> Double

// needed here to declare "abstract" init, which is then implemented to call different "real" inits in implementing structs
init(fromObjectiveC source: _ObjectiveCType)
}

extension UnitConverter where _ObjectiveCType : JCUnitConverter
{
public static func _isBridgedToObjectiveC() -> Bool
{
   return true
}

public static func _getObjectiveCType() -> Any.Type
{
   return _ObjectiveCType.self
}

public static func _forceBridgeFromObjectiveC(_ source: _ObjectiveCType, result: inout Self?)
{
   result = Self.init(fromObjectiveC: source)
}

public static func _conditionallyBridgeFromObjectiveC(_ source: _ObjectiveCType, result: inout Self?) -> Bool
{
   _forceBridgeFromObjectiveC(source, result: &result)

   return true
}

public static func _unconditionallyBridgeFromObjectiveC(_ source: _ObjectiveCType?) -> Self
{
   return Self.init(fromObjectiveC: source!)
}
}

public struct UnitConverterLinear : UnitConverter
{
public let coefficient: Double

public let constant: Double

public init(coefficient: Double, constant: Double)
{
   self.coefficient = coefficient

   self.constant = constant
}

public init(coefficient: Double)
{
   self.init(coefficient: coefficient, constant: 0)
}

public func baseUnitValue(fromValue value: Double) -> Double
{
   return value * coefficient + constant
}

public func value(fromBaseUnitValue baseUnitValue: Double) -> Double
{
   return (baseUnitValue - constant) / coefficient
}
}

extension UnitConverterLinear
{
// "override" of abstract init declared in UnitConverter protocol
public init(fromObjectiveC source: JCUnitConverterLinear)
{
   self.init(coefficient: source.coefficient, constant: source.constant)
}

// likewise, this _ObjectiveCBridgeable method has to be declared here, otherwise it doesn't know about the specific init
public func _bridgeToObjectiveC() -> JCUnitConverterLinear
{
   return JCUnitConverterLinear(coefficient: self.coefficient, constant: self.constant)
}
}

public struct UnitConverterReciprocal : UnitConverter
{
public let reciprocal: Double

public init(reciprocal: Double)
{
   self.reciprocal = reciprocal
}

public func baseUnitValue(fromValue value: Double) -> Double
{
   return reciprocal / value
}

public func value(fromBaseUnitValue baseUnitValue: Double) -> Double
{
   return baseUnitValue * reciprocal
}
}

extension UnitConverterReciprocal : _ObjectiveCBridgeable
{
// "override" of abstract init declared in UnitConverter protocol
public init(fromObjectiveC source: JCUnitConverterReciprocal)
{
   self.init(reciprocal: source.reciprocal)
}

// likewise, this _ObjectiveCBridgeable method has to be declared here, otherwise it doesn't know about the specific init
public func _bridgeToObjectiveC() -> JCUnitConverterReciprocal
{
   return JCUnitConverterReciprocal(reciprocal: self.reciprocal)
}
}

public protocol Unit
{
var symbol: String { get }
}

public protocol ConvertibleUnit : Unit, _ObjectiveCBridgeable
{
associatedtype ConverterType : UnitConverter

var converter: ConverterType { get }

static var baseUnit : Self { get }

init(symbol: String, converter: ConverterType)
}

extension ConvertibleUnit where _ObjectiveCType : JCConvertibleUnit
{
public init(fromObjectiveC source: _ObjectiveCType)
{
   self.init(symbol: source.symbol, converter: source.converter as! ConverterType)
}

public static func _isBridgedToObjectiveC() -> Bool
{
   return true
}

public static func _getObjectiveCType() -> Any.Type
{
   return _ObjectiveCType.self
}

public func _bridgeToObjectiveC() -> _ObjectiveCType
{
   return _ObjectiveCType.init(symbol: self.symbol, converter: self.converter as! JCUnitConverter)
}

public static func _forceBridgeFromObjectiveC(_ source: _ObjectiveCType, result: inout Self?)
{
   result = self.init(fromObjectiveC: source)
}

public static func _conditionallyBridgeFromObjectiveC(_ source: _ObjectiveCType, result: inout Self?) -> Bool
{
   _forceBridgeFromObjectiveC(source, result: &result)

   return true
}

public static func _unconditionallyBridgeFromObjectiveC(_ source: _ObjectiveCType?) -> Self
{
   return Self.init(fromObjectiveC: source!)
}
}

public struct LengthUnit : ConvertibleUnit
{
public let symbol: String

public let converter: UnitConverterLinear

private struct Symbol
{
   static let kilometers = "km"
   static let meters = "m"
   // ...
}

private struct Coefficient
{
   static let kilometers = 1000.0
   static let meters = 1.0
   // ...
}

public static var kilometers: LengthUnit
{
   return LengthUnit(symbol: Symbol.kilometers, converter: UnitConverterLinear(coefficient: Coefficient.kilometers))
}

public static var meters: LengthUnit
{
   return LengthUnit(symbol: Symbol.meters, converter: UnitConverterLinear(coefficient: Coefficient.meters))
}

// ...

public static var baseUnit : LengthUnit
{
   return LengthUnit.meters
}

public init(symbol: String, converter: UnitConverterLinear)
{
   self.symbol = symbol

   self.converter = converter
}
}

extension LengthUnit : _ObjectiveCBridgeable
{
public typealias _ObjectiveCType = JCLengthUnit
}

public struct FuelEfficiencyUnit : ConvertibleUnit
{
public typealias ConverterType = UnitConverterReciprocal

public let symbol: String

public let converter: UnitConverterReciprocal

private struct Symbol
{
   static let litersPer100Kilometers = "L/100km"
   static let milesPerImperialGallon = "mpg"
   static let milesPerGallon = "mpg"
}

private struct Reciprocal
{
   static let litersPer100Kilometers = 1.0
   static let milesPerImperialGallon = 282.481
   static let milesPerGallon = 235.215
}

public static var litersPer100Kilometers: FuelEfficiencyUnit
{
   return FuelEfficiencyUnit(symbol: Symbol.litersPer100Kilometers, converter: UnitConverterReciprocal(reciprocal: Reciprocal.litersPer100Kilometers))
}

public static var milesPerImperialGallon: FuelEfficiencyUnit
{
   return FuelEfficiencyUnit(symbol: Symbol.milesPerImperialGallon, converter: UnitConverterReciprocal(reciprocal: Reciprocal.milesPerImperialGallon))
}

public static var milesPerGallon: FuelEfficiencyUnit
{
   return FuelEfficiencyUnit(symbol: Symbol.milesPerGallon, converter: UnitConverterReciprocal(reciprocal: Reciprocal.milesPerGallon))
}

public static var baseUnit: FuelEfficiencyUnit
{
   return FuelEfficiencyUnit.litersPer100Kilometers
}

public init(symbol: String, converter: UnitConverterReciprocal)
{
   self.symbol = symbol

   self.converter = converter
}
}

extension FuelEfficiencyUnit : _ObjectiveCBridgeable
{
public typealias _ObjectiveCType = JCFuelEfficiencyUnit
}

public struct Measurement<UnitType : Unit>
{
var value: Double

let unit: UnitType

public init(value: Double, unit: UnitType)
{
   self.value = value

   self.unit = unit
}
}

extension Measurement where UnitType : ConvertibleUnit
{
public func canBeConverted<TargetUnit : Unit>(to unit: TargetUnit) -> Bool
{
   return unit is UnitType
}

public func converting<TargetUnit : ConvertibleUnit>(to unit: TargetUnit) -> Measurement<TargetUnit>
{
   if !canBeConverted(to: unit)
   {
     fatalError("Unit type not compatible")
   }

   let baseUnitValue = self.unit.converter.baseUnitValue(fromValue: value)

   let convertedValue = unit.converter.value(fromBaseUnitValue: baseUnitValue)

   return Measurement<TargetUnit>(value: convertedValue, unit: unit)
}
}

extension Measurement : _ObjectiveCBridgeable
{
init(fromObjectiveC source: JCMeasurement<JCUnit>)
{
   self.value = source.value

   let u: UnitType = source.unit as! UnitType

   self.unit = u
}

public static func _getObjectiveCType() -> Any.Type
{
   return _ObjectiveCType.self
}

public static func _isBridgedToObjectiveC() -> Bool
{
   return true
}

public func _bridgeToObjectiveC() -> JCMeasurement<JCUnit>
{
   let u: JCUnit = self.unit as! JCUnit

   return JCMeasurement(value: self.value, unit: u)
}

public static func _forceBridgeFromObjectiveC(_ source: JCMeasurement<JCUnit>, result: inout Measurement?)
{
   result = Measurement(fromObjectiveC: source)
}

public static func _conditionallyBridgeFromObjectiveC(_ source: JCMeasurement<JCUnit>, result: inout Measurement?) -> Bool
{
   _forceBridgeFromObjectiveC(source, result: &result)

   return true
}

public static func _unconditionallyBridgeFromObjectiveC(_ source: JCMeasurement<JCUnit>?) -> Measurement
{
   return Measurement(fromObjectiveC: source!)
}
}
///////////////////

···

--
Joanna Carter
Carter Consulting
--
Joanna Carter
Carter Consulting