> 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 :-)

///////////////////
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

_______________________________________________
swift-corelibs-dev mailing list
swift-corelibs-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-corelibs-dev

Reply via email to