在 Foundation 框架中的度量值和單位

作者:Ole Begemann,原文鏈接观堂,原文日期:2016-07-28
譯者:粉紅星云妖啥;校對:saitjr芝硬;定稿:CMB

文章更新日志:

  • 2016/06/30 增加了一個(gè)“不足之處”小節(jié),主要關(guān)于語法冗長帝璧。還有很少一部分內(nèi)容的重寫先誉。
  • 2016/08/02 把代碼更新到 Xcode 8 beta 4 版本的。

這個(gè)系列的其他文章:

  1. 在 Foundation 框架中的度量值和單位(本篇文章)
  2. 乘法和除法
  3. 改良
  4. 幽靈類型 (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ù)字來做度量值、使用變量名來編碼單位宁否,像 distanceInKilometerstemperatureInCelsius 等要好多了窒升。不僅預(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南誊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末身诺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子抄囚,更是在濱河造成了極大的恐慌霉赡,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幔托,死亡現(xiàn)場離奇詭異穴亏,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門嗓化,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锅劝,“玉大人,你說我怎么就攤上這事蟆湖」示簦” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵隅津,是天一觀的道長诬垂。 經(jīng)常有香客問我,道長伦仍,這世上最難降的妖魔是什么结窘? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮充蓝,結(jié)果婚禮上隧枫,老公的妹妹穿的比我還像新娘。我一直安慰自己谓苟,他們只是感情好官脓,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著涝焙,像睡著了一般卑笨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仑撞,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天赤兴,我揣著相機(jī)與錄音,去河邊找鬼隧哮。 笑死桶良,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沮翔。 我是一名探鬼主播陨帆,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鉴竭!你這毒婦竟也來了歧譬?” 一聲冷哼從身側(cè)響起岸浑,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤搏存,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后矢洲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體璧眠,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了责静。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袁滥。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖灾螃,靈堂內(nèi)的尸體忽然破棺而出题翻,到底是詐尸還是另有隱情,我是刑警寧澤腰鬼,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布嵌赠,位于F島的核電站,受9級特大地震影響熄赡,放射性物質(zhì)發(fā)生泄漏姜挺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一彼硫、第九天 我趴在偏房一處隱蔽的房頂上張望炊豪。 院中可真熱鬧,春花似錦拧篮、人聲如沸词渤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掖肋。三九已至,卻和暖如春赏参,著一層夾襖步出監(jiān)牢的瞬間志笼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工把篓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纫溃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓韧掩,卻偏偏與公主長得像紊浩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子疗锐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 作者:Ole Begemann滑臊,原文鏈接口芍,原文日期:2016-08-29譯者:與狼同行;校對:Cwift雇卷;定稿:C...
    梁杰_numbbbbb閱讀 472評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理鬓椭,服務(wù)發(fā)現(xiàn)颠猴,斷路器,智...
    卡卡羅2017閱讀 134,702評論 18 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 11,005評論 6 13
  • 我是很喜歡獨(dú)處的小染。也曾一度被家里人誤以為是個(gè)呆子翘瓮,如果家里條件好一點(diǎn),我估計(jì)早就被帶到了西京醫(yī)院裤翩。 ...
    忍者神鵝閱讀 368評論 5 3