NSMeasurement

Мощният API на Foundation за измерване на #AllTheThings 📐

Кратка бележка — всички мои бъдещи публикации ще бъдат публикувани на моя „специализиран уебсайт“ и тази публикация вече не се актуализира. Благодаря за четенето!

Измерването на единици, неща или елементи в софтуера не винаги ще бъде най-бляскавата работа за софтуерните инженери. Въпреки че не е толкова вълнуващо, колкото писането на производителни библиотеки с потребителски интерфейс, нито толкова удовлетворяващо, колкото създаването на мрежов код, който вплита едновременно изпълнение с лекота, измерването на единици в iOS все още се прави по-често, отколкото не, погрешно.

Как може човек да създаде такъв код по интернационализиран начин, а не по внезапен? Фондацията, както често се случва, крие отговора. И това е от iOS 10.

Тази седмица нека разгледаме NSMeasurement и приятели.

По-често, отколкото си мислите

Софтуерът винаги е имал ситуации, които изникват, които представят на потребителите няколко неща, които са или измерими, или общо измерими количествено. И когато този софтуер (iOS) работи на милиарди устройства по целия свят, необходимостта да се представят интернационализирани ценности на тези неща става не само по-важна, но и доста ключова.

Получаването на правилните измервания и единици не е основно изживяване, просто трябва да го очаквате от вашия софтуер.

И така с това – тези API помагат не само с очевидните приложения за „конвертор“, но и с игри, списъци за пазаруване и всичко останало между тях:

  • Представлявате измерване на времето ⌚️?
  • Изминато разстояние 🚙?
  • Скоростта, с която пътувахме 🗺?
  • Теглото на предмет ⚖️?

Като такова, изкушението да се използва прост двоен тип има смисъл на повърхностно ниво и просто се разпада навсякъде другаде:

//Technically we even set up the variable name for failure
let milesTraveled:Double

NSMeasurement и всички негови единици

Следвайки предишния пример, това, от което наистина се нуждаем, е точна, истинска конструкция, която да представлява измерване. И докато милите със сигурност са мярка, тя е само тази, която като цяло има смисъл в контекста на Murica’ 🇺🇸. Като такава, подкрепата на Фондацията за измерване е тактично обща:

public struct Measurement <UnitType:Unit>

Обърнете внимание, че в рамките на текста, за стилистични цели и тъй като ежедневно кодирам в Objective-C, ще се позовавам на рамките с техния NS префикс. Всички тези обекти са правилно свързани към Swift като стойностни типове и структури.

Предоставя начин за предоставяне на обща единица за измерване, както и стойност, която й съответства. Неговият инициализатор ще поиска и двете:

public init(value:Double, unit:UnitType)

Всяка единица винаги ще има символ, въпреки че не всяка единица има измерение, нито винаги ще бъдат еквивалентни една на друга. Трябва да се отбележи, разбира се, но най-вероятно човек ще работи с мерни единици. Но поради тази причина определеният инициализатор на NSUnit ще изисква само символ:

public class Unit: NSObject, NSCopying
{
    public let symbol:String
    public init(symbol: String)
}

Що се отнася до NSMeasurement, този тип единица управлява по-голямата част от работата и можете да определите коя да използвате чрез неговия инициализатор, но по-често ще използвате NSDimension (която подкласира NSUnit), предоставена ни от Apple. След това всяка мерна единица ще се спусне по-надолу в дадено измерение, което съществува в единицата.

Например, да кажем, че искаме да измерим времето. Нашата единица ще бъде продължителност, но имаме няколко начина да представим продължителността, като секунда, минута и час:

let abstractValue = 1.0
// 1 second
let seconds = Measurement(value: abstractValue, unit: UnitDuration.seconds)
// 1 minute
let minutes = Measurement(value: abstractValue, unit: UnitDuration.minutes)
// 1 hour
let hours = Measurement(value: abstractValue, unit: UnitDuration.hours)

Фондацията включва камион с размерни единици, всичко от електрически токове до налягане. Въпреки че сте свободни да създавате подкласове и да създавате свои собствени единици, всъщност има доста стабилна поддръжка за това, не съм сигурен, че някога ще ви се наложи.

Ето някои общи мерни единици, изпечени безплатно:

  • Дължина на единица: Основната единица е метри (m)
  • Единица за маса: Основната единица е килограми (kg)
  • UnitDuration: Основната единица е секунди (сек)
  • UnitArea: Основната единица е квадратни метри (m²)
  • Единица за ускорение: Основната единица е метри в секунда на квадрат (m/s²)

Има около 170 типа мерни единици и вероятно Foundation е помислил за вашия случай на използване.

Работа с измервания

Работата с измерванията е тривиална поради факта, че те съответстват на equatable от кутията, така че сравненията се извършват еднакво:

let abstractValue = 1.0
let seconds = Measurement(value: abstractValue, unit: UnitDuration.seconds)
let minutes = Measurement(value: abstractValue, unit: UnitDuration.minutes)
// 61.0 seconds, measured in the dimension's base unit
let totalTime = seconds + minutes
// 30.5 seconds
let halfTheTime = totalTime/2

Целият API за измерване върши цялата тежка работа вместо вас. Това е вярно, когато работите с единици от едно и също измерение, но в различни форми. Резултатът от операцията просто ще предостави типа на базовата единица:

let imperialLength = Measurement(value: 5280.0, unit: UnitLength.feet)
let metricLength = Measurement(value: 0.62, unit: UnitLength.kilometers)
// 2229.344 meters
let totalLength = imperialLength + metricLength

Разширяването на полезността е NSUnitConverter, който е абстрактен клас, който преобразува единица към и от основната единица на нейното дадено измерение. В повечето случаи това ще бъдат единици, които се придържат към линейно уравнение или мащабен фактор. Като такъв, UnitConverterLinear ще бъде доставен:

let imperialLength = Measurement(value: 5280.0, unit: UnitLength.feet)
let metricLength = Measurement(value: 0.62, unit: UnitLength.kilometers)
// 2229.344 meters
let totalLength = imperialLength + metricLength
// 1.385 miles
let justMiles = totalLength.converted(to: UnitLength.miles)

Не се притеснявайте да създавате реализации, които не са свързани едно с друго. Те ще доведат до грешки във времето за изграждане поради преобразуването на всеки тип, изискващо неговия общ тип единица:

// Build error
let nonsense = totalLength.converted(to: UnitTemperature.celsius)

Ценности, ориентирани към потребителя

Показването на тези стойности във вашия потребителски интерфейс ще ви бъде доста познато, ако сте се впускали във водите на NSNumberFormatter. Използването на неговия близък братовчед, NSMeasurementFormatter, е по същество идентично.

Това е доста идеално, тъй като писането на тези видове низове в наше време бързо би се превърнало в скучна работа.

if (isCanada)
{
    // kilometers 👌
}
else if (isChinese)
{
    // Translate the unit 😐
}
else if (isArabic)
{
    // Translate the unit, number representation AND make it right to left 😱
}

Разбира се, Фондацията и приятелите го правят:

let distance = Measurement(value:10, unit: UnitLength.miles)
let frenchDistance = MeasurementFormatter()
frenchDistance.locale = Locale(identifier: "fr")
let chineseDistance = MeasurementFormatter()
chineseDistance.locale = Locale(identifier: "zh")
let arabicDistance = MeasurementFormatter()
arabicDistance.locale = Locale(identifier: "ar")
// 🇫🇷 -> 16,093 km
print("🇫🇷 -> \(frenchDistance.string(from: distance))")
// 🇨🇳 -> 16.093公里
print("🇨🇳 -> \(chineseDistance.string(from: distance))")
// 🇯🇴 -> ١٦٫٠٩٣ كم
print("🇯🇴 -> \(arabicDistance.string(from: distance))")

Тъй като измерванията са тясно свързани с числата, също така е възможно да сдвоите инструмент за форматиране на измерване с формататор на числа, за да посочите цифри, например:

let distance = Measurement(value:0.2, unit: UnitLength.miles)
let frenchDistance = MeasurementFormatter()
frenchDistance.locale = Locale(identifier: "fr")
// 🇫🇷 -> 0,322 km
print("🇫🇷 -> \(frenchDistance.string(from: distance))")
let digitFormat = NumberFormatter()
digitFormat.minimumSignificantDigits = 4
frenchDistance.numberFormatter = digitFormat
// 🇫🇷 -> 0.321868 km
print("🇫🇷 -> \(frenchDistance.string(from: distance))")

Има няколко конфигурации, които измервателният формат ви предоставя. Не забравяйте да прегледате неговата „документация“. Също така имайте предвид, че форматиращият ще вземе системния си локал по подразбиране, когато се инициализира, така че обикновено не е необходимо директно да се присвоява на неговото свойство за локал, както направихме тук.

Тестване на локали — .easy начинът

Кратка бележка. Част от магията на използването на API за измерване и единици на Foundation е, че те са наясно с локала. Ако в момента променяте местоположението в симулатора на iOS, за да видите как се развиват нещата, има друг начин, който може да предпочетете.

Просто измамете схемата си и изберете желания локал:

  • Редактиране на схема
  • Натиснете „Duplicate Scheme“, това е долу вляво в Xcode 9
  • Назовете го
  • Под Run -› Options -› Application Region, след това изберете региона, с който да тествате

Това е хубаво, защото е без суетене и преднамерен подход. Не изисква промени в кода или бъркане с (винаги надеждния 🤞) iOS симулатор.

Освен това можете да използвате същия подход за тестване на локализации на низове във вашия интерфейс, като редактирате „Езика на приложението“ по същия начин, по който сте редактирали региона.

Обобщавайки

Едва когато започнах да работя в международен екип, наистина започнах да оценявам точните измервания в iOS. Или — дори правилната мерна единица период.

Докато останалият свят прегръща метричната система, тук аз стърча като възпален палец, докато съобщавам разстояния чрез имперската система. Сири има душа и аз знам това, защото й е писнало да отговаря колко мили се равняват на 1 километър. Чувам го в нейния студен, твърд, дигитален глас, когато ми отговаря за 144-ти път 🤖.

Нека всички се стремим да не бъдем това приложение, което доставя грешни единици или неправилни измервания в нашия собствен софтуер. Фондацията ни покрива.

До следващия път ✌️



If you enjoyed this week's post, please feel free to go ahead and NSRecommend(this, where: below);