> 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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-corelibs-dev