> 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