[swift-corelibs-dev] Swiftier? implementation of Measurement and Unit in Foundation
Joanna Carter
joanna at carterconsulting.org.uk
Wed Aug 24 05:28:03 CDT 2016
> 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
More information about the swift-corelibs-dev
mailing list