作者:Ole Begemann,原文鏈接观堂,原文日期:2016-07-28
譯者:粉紅星云妖啥;校對:saitjr芝硬;定稿:CMB
文章更新日志:
- 2016/06/30 增加了一個(gè)“不足之處”小節(jié),主要關(guān)于語法冗長帝璧。還有很少一部分內(nèi)容的重寫先誉。
- 2016/08/02 把代碼更新到 Xcode 8 beta 4 版本的。
這個(gè)系列的其他文章:
- 在 Foundation 框架中的度量值和單位(本篇文章)
- 乘法和除法
- 改良
- 幽靈類型 (Phantom Types)
在 iOS 10 ?和 macOS 10.12 里的 Foundation 框架的烁,新出了?一系列將度量單位模型化的類型褐耳,我們在現(xiàn)實(shí)中真實(shí)使用的度量單位,比如:1 千米渴庆,21 攝氏度铃芦。如果你還沒了解過這個(gè),看看 WWDC session 238 吧襟雷,這里概述講的挺好的刃滓。
介紹
這個(gè)例子向你展示了下用法。讓我們從新建一個(gè)我上次騎行的距離的常量開始耸弄。
let distance = Measurement(value: 106.4, unit: UnitLength.kilometers)
// → 106.4 km
這度量值(Measurement咧虎,在 swift 中是一個(gè)值類型)包含了數(shù)量(106.4)和度量單位(千米)。我們也可以自己定義一個(gè)單位计呈,但是在 Foundation 框架中已經(jīng)有了一堆常見的物理量(physical quantities)砰诵。目前已存在 21 種已定義單位類型征唬。他們都是抽象類(Dimension)的子類,并且類名也是以 Unit
開頭的茁彭。比如:UnitAcceleration总寒,UnitMass,和 UnitTemperature 等等尉间。我們在這里用的是 UnitLength 偿乖。
每一個(gè)單位類提供了類屬性來描述其相關(guān)的各種單位。比如有米哲嘲,千米贪薪,英里和光年。我們可以這么寫眠副,來把我們原來在千米的度量值轉(zhuǎn)換為其他單位:
let distanceInMeters = distance.converted(to: .meters)
// → 106400 m
let distanceInMiles = distance.converted(to: .miles)
// → 66.1140591795394 mi
let distanceInFurlongs = distance.converted(to: .furlongs)
// → 528.911158832419 fur
UnitLength
自帶 22 個(gè)預(yù)定義好的的單位屬性画切,從皮米到光年都有。如果沒有你需要的單位囱怕,新建自定義的也十分簡單霍弹。只要給這個(gè)類擴(kuò)展一個(gè)靜態(tài)的屬性,屬性包含描述新單位的標(biāo)志和它轉(zhuǎn)換為本類型的基本單位的換算因素就行了娃弓。后面這部分是使用 UnitConverter 這個(gè)類搞定的典格。基本單位可以是其他同類型預(yù)定義的單位台丛。它一定是已經(jīng)在文檔里的并且通常與(但不一定是)國際單位制對應(yīng)的基本單位耍缴。對于 UnitLength
來說,基本單位就是米(.meters)挽霉。
extension UnitLength {
static var leagues: UnitLength {
// 1 league = 5556 meters
return UnitLength(symbol: "leagues",
converter: UnitConverterLinear(coefficient: 5556))
}
}
let distanceInLeagues = distance.converted(to: .leagues)
// → 19.150467962563 leagues
(我更傾向使用靜態(tài)存儲常量而不是一個(gè)計(jì)算屬性防嗡,但是在 NSObject
的子類擴(kuò)展中,不怎么支持存儲屬性侠坎。了解更多詳見 SR-993 蚁趁。)
我們也可以使用標(biāo)量值乘上度量值,或給度量值做加減实胸。在需要時(shí)他嫡,單位的轉(zhuǎn)換是自動處理的:
let doubleDistance = distance * 2
// → 212.8 km
let distance2 = distance + Measurement(value: 5, unit: UnitLength.kilometers)
// → 111.4 km
let distance3 = distance + Measurement(value: 10, unit: UnitLength.miles)
// → 122493.4 m
注意到上個(gè)例子,當(dāng)我們添加一個(gè)千米和一個(gè)英里的度量值時(shí)庐完,框架把他們?nèi)D(zhuǎn)換成米( UnitLength
的基本單位)才相加的涮瞻。原始單位的信息丟失了。而在先前的例子中都沒有發(fā)生過假褪,那是因?yàn)橹笆莾蓚€(gè)相同單位的度量值(千米)署咽。
優(yōu)點(diǎn)
安全
目前為止運(yùn)作良好。而且比我們通常的使用簡單的浮點(diǎn)數(shù)字來做度量值、使用變量名來編碼單位宁否,像 distanceInKilometers
或 temperatureInCelsius
等要好多了窒升。不僅預(yù)防了溝通上的誤解,更嚴(yán)謹(jǐn)?shù)念愋鸵沧尵幾g器可以來幫忙檢查我們的邏輯:錯誤的將長度單位添加到溫度單位類中這樣的事情不再可能慕匠,因?yàn)檫@樣代碼就編譯不起來了饱须。
更富有表現(xiàn)力的 API
在未來,采用新類型的 API(無論是蘋果原生台谊,還是第三方)蓉媳,會變得更加有表現(xiàn)力和自動文檔化。
假設(shè)有一個(gè)旋轉(zhuǎn)圖片的方法」Γ現(xiàn)在可能要用 Double
來接收 angle 參數(shù)酪呻,而且作者要寫明這個(gè)方法是接收弧度制還是角度值的參數(shù),調(diào)用 API 的開發(fā)者也需要注意不要傳錯參數(shù)盐须。在有單位的新世界里玩荠,角度參數(shù)的類型一定會是 UnitAngle,同時(shí)解放了 API 的作者和調(diào)用者贼邓。不僅采用了最為明了的處理方式阶冈,并且排除了轉(zhuǎn)換錯誤產(chǎn)生的 bug。
同樣塑径,一個(gè)動畫 API 不再需要文檔解釋 duration 參數(shù)女坑。參數(shù)的單位簡單明了的是 UnitDuration 類型。
MeasurementFormatter
最后统舀,還附帶了一個(gè) MeasurementFormatter 類匆骗。它能將度量值換算為本地化的值,更加地域化(比如使用英里绑咱,而不是公里)绰筛,數(shù)字格式和符號都參與換算枢泰。
let formatter = MeasurementFormatter()
let ???? = Locale(identifier: "de_DE")
formatter.locale = ????
formatter.string(from: distance) // "106,4 km"
let ???? = Locale(identifier: "en_US")
formatter.locale = ????
formatter.string(from: distance) // "66.114 mi"
let ???? = Locale(identifier: "zh_Hans_CN")
formatter.locale = ????
formatter.string(from: distance) // "106.4公里"
不足之處
新 API 有個(gè)不討喜的點(diǎn)描融,太過冗長。 Measurement(value: 5, unit: UnitLength.kilometers)
這句代碼的讀寫性都很差衡蚂。雖然要找到既簡潔窿克,又能清晰表達(dá)的方法命名很難,但這個(gè)方法也有些太過冗長了毛甲。
有種較為極端的初始方式: let d = 5.kilometers
年叮。這個(gè)閱讀性超好,但是還是有一個(gè)缺點(diǎn)——污染了通用的整型和浮點(diǎn)的命名空間玻募。有點(diǎn)像這種表達(dá):5.measure.kilometers
只损。
去掉參數(shù)標(biāo)志對初始化方法來說已經(jīng)是一個(gè)很大的進(jìn)步了。let d = Measurement(5, UnitLength.kilometers)
更好理解。現(xiàn)在很喜歡給每一個(gè)單位類型添加一個(gè)別名跃惫,從而擺脫掉 UnitLength
的前綴叮叹,像下面這樣:
typealias Length = Measurement<UnitLength>
let d = Length(5, .kilometers)
typealias Duration = Measurement<UnitDuration>
let t = Duration(10, .seconds)
這些加到你自己的項(xiàng)目中還是挺容易的,只需要蘋果出一個(gè)更加標(biāo)準(zhǔn)的語法爆存。
單位類之間的關(guān)系
我們已經(jīng)見過相同類型的度量值的相加了蛉顽,如果我需要計(jì)算在單車騎行中的平均速度呢?速度等于距離除于時(shí)間先较,我們新建一個(gè)騎行時(shí)間的度量值然后可以做這個(gè)計(jì)算:
// 8h 6m 17s
let time = Measurement(value: 8, unit: UnitDuration.hours)
+ Measurement(value: 6, unit: UnitDuration.minutes)
+ Measurement(value: 17, unit: UnitDuration.seconds)
let speed = distance / time
// error: binary operator '/' cannot be applied to operands of type 'Measurement<UnitLength>' and 'Measurement<UnitDuration>'
這個(gè)除法運(yùn)算會產(chǎn)生一個(gè)編譯錯誤携冤。發(fā)現(xiàn)蘋果(可能在第一個(gè)版本的時(shí)候更明智些)斷開了類型之間的關(guān)聯(lián)。所以我們不能用 UnitLength
來除以 UnitDuration
闲勺,最后得到一個(gè) UnitSpeed 類型曾棕。不過手動添加很簡單。我們只需要提供一個(gè)對應(yīng)的除法運(yùn)算符 /
的重載方法:
func / (lhs: Measurement<UnitLength>, rhs: Measurement<UnitDuration>) -> Measurement<UnitSpeed> {
let quantity = lhs.converted(to: .meters).value / rhs.converted(to: .seconds).value
let resultUnit = UnitSpeed.metersPerSecond
return Measurement(value: quantity, unit: resultUnit)
}
在執(zhí)行運(yùn)算的時(shí)候霉翔,我們把長度值轉(zhuǎn)換為米的單位睁蕾,持續(xù)時(shí)間用秒的單位,并且返回值的單位是米 / 秒≌洌現(xiàn)在編譯器可開心了:
let speed = distance / time
// → 3.64670802344312 m/s
speed.converted(to: .kilometersPerHour)
// → 13.1281383818845 km/h
能更加優(yōu)雅一些嗎子眶?
這種做法挺好的,但是有點(diǎn)受限序芦。我們需要給各種反向運(yùn)算提供一個(gè)額外的重載方法臭杰,比如:距離 = 速度 × 時(shí)間、時(shí)間 = 距離 / 速度谚中。如果我們還想表達(dá)其他的關(guān)系渴杆,比如:電阻 = 電壓 / 電流,我們要全部再寫一遍宪塔。如果可以一次性陳述表達(dá)各種關(guān)系磁奖,之后使用的時(shí)候自動就能用這個(gè)關(guān)系的話是不是超級厲害。我在下一篇文章中將會向你介紹這個(gè)某筐。
本文由 SwiftGG 翻譯組翻譯比搭,已經(jīng)獲得作者翻譯授權(quán),最新文章請?jiān)L問 http://swift.gg南誊。