Dimension, Unit and Measurement of the Foundation

업데이트:

Foundation 프레임워크의 Dimension, Unit 그리고 Measurement를 탐구합니다.

개요

지금 듣고 있는 음악의 길이는 3분 10초, 작업하고 있는 맥북 크기는 14인치이며, 무게는 1.6kg입니다. 이렇게 우리는 일상생활에서 항상 단위를 사용하고 있는데요. 물물교환을 시작했을 때쯤 먼 옛날 사람들은 측정 도구 대신 신체의 일부를 측정의 기준으로 삼았습니다. 이 측정 기준은 사람의 체격마다 단위가 달라지기 때문에 정확한 측정이 어렵다는 한계가 있었습니다. 이후 시간이 지나면서 사람들은 거래를 위해 보다 정확하게 측정할 필요성을 느끼게 되었고, 길이와 부피, 무게 등 단위에 대한 개념과 이를 측정하는 도구들이 생겨났습니다. 단위는 인간의 공동생활을 유지하기 위한 중요한 기준이었기 때문에 제도화할 필요가 있었던 것입니다. 공동생활의 범위가 나라에 한정되어 있을 시기에는 나라별로 야드, 피트, 인치, 자 등 다양한 단위를 사용했지만, 범위가 전 세계로 넓어지면서 전 세계가 공통으로 사용할 수 있는 단위계를 형성해야 할 필요성이 생겼습니다. 국제단위계가 탄생한 것이죠.

국제단위계(SI)는 몇 가지 기본단위를 바탕으로 하여 이들의 곱이나 비의 형식으로 모든 물리량을 나타내는 일관성 있는 체계를 형성하므로 다른 체계와의 혼합에서 오는 공식 내의 인자들이 없어지게 됩니다. 여기서 일관성(coherent)이라는 표현은 1 외의 어떤 다른 수치적 인자 없이 순전히 SI 단위 끼리 만의 곱하기와 나누기로 이루어진 단위의 체계라는 의미입니다. 이 특징 덕분에 일정한 규칙만 알고 그에 따라 적용하면 되므로 배우기와 사용하기가 쉽습니다.

사실 소프트웨어 엔지니어에게 소프트웨어에서 단위를 측정하는 일은 항상 가장 매력적인 일이 아닙니다. 성능이 뛰어난 사용자 인터페이스 라이브러리를 작성하는 것만큼 스릴 있지도 않고, 동시 실행을 쉽게 사용할 수 있도록 하는 네트워킹 코드를 만들어내는 것만큼 만족스럽지는 않지만, 단위 측정을 올바른 방식으로 수행하는 것은 중요합니다. 저 또한 개발을 처음 시작할 때는 단위를 측정하고, 변환하는 것보다 화려한 기술을 사용해 멋진 코드를 가진 프로젝트를 만드는 것이 훨씬 중요하다고 생각했습니다. 하지만 다양한 프로젝트를 경험하면서 여러 가지 단위를 사용자에게 제공해야 하는 상황이 발생했고, 이는 전 세계의 사용자에게 국제화된 정보를 표현해야 할 필요성이 큰 프로젝트일수록 중요해진다는 것을 알게 되었습니다.

단위 변환을 우습게 여겼던 시기에는 다음 코드와 같이 간단한 Double 타입을 사용해 단위를 표현했었습니다.

var gram: Double

이 단순한 코드에는 여러 문제가 있습니다:

  • 개발자는 변수 이름을 통해 이 변수가 어떤 단위를 표현하는지 알 수 있지만, 컴파일러는 다른 단위를 가진 변수를 동일한 타입으로 취급합니다.
  • 서로 다른 단위를 이용해 계산할 때 단위 변환이 올바르게 이루어졌는지 직접 확인해야 합니다. 휴먼 에러가 발생한다면 컴파일 단계에서 알 수 없기 때문에 버그로 직결됩니다.
  • 변수 이름을 명확하게 짓지 않는다면, 다른 개발자는 이 변수가 어떤 단위를 표현하는지 알 수 없습니다.

이 문제들을 해결하기 위해 애플은 국제단위계의 특징을 활용하여 Foundation 프레임워크의 Measurement라는 구조체를 제공합니다. Measurement는 단위 측정 및 변환을 처리하는 편리한 방법을 제공합니다. 값과 단위를 가지며, 동일한 차원의 다른 단위로 변환할 수 있습니다.

Dimension, Unit and Measurement of the Foundation

Dimension

  • 같은 타입의 다른 단위로 변환될 수 있는 단위의 차원을 나타내는 추상 클래스
  • Dimension subclass는 UnitConverter를 사용하여 동일한 차원의 다른 단위로 변환할 수 있는 baseUnit 및 여러 추가 단위를 정의합니다.
  • UnitConverter 클래스는 차원 단위를 기본 단위로 변환하는 계수와 상수를 가지는 UnitConverterLinear subclass를 가집니다.
  • 예를 들어, UnitLength에는 m symbol이 있는 .meters baseUnit과, km symbol이 있는 .kilometers unit이 있습니다. 이 때 UnitConverterLinear는 1000m가 1km와 같기 때문에 계수 1000, 상수는 0의 값을 가집니다.
  • 국제단위계에 포함된 대부분의 차원 및 단위를 제공합니다. 제공되는 차원 및 단위는 링크를 참조해주세요.

Unit

  • 측정 단위를 나타내는 추상 클래스
  • Unit subclass는 MeasurementFormatter 클래스를 사용하여 Measurement 객체의 문자열 표현을 만드는데 사용할 수 있는 symbol로 구성됩니다.

Measurement

  • 수량과 측정 단위를 나타냅니다.
  • Measurement 타입은 측정을 다른 단위로 변환하고 두 측정 간의 합계 또는 차이를 계산하기 위한 프로그래밍 인터페이스를 제공합니다.
  • Measurement 객체는 Unit 객체와 double 값으로 초기화되고, 불변이며 생성된 후에는 변경할 수 없습니다.
  • Measurement는 사칙연산을 비롯한 다양한 연산자 사용을 지원합니다.

Usage

import Foundation

// 1m
let meter = Measurement(value: 1, unit: UnitLength.meters)
// 1km
let kilometer = Measurement(value: 1, unit: UnitLength.kilometers)

// 1km == 1m
if meter == kilometer {
    print("1km is 1m")
} else {
    // 당연히 1km는 1m가 아님이 출력
    print("1km is not 1m")
}

let killometerInMeter = kilometer.converted(to: UnitLength.meters)
// 1000m 출력
print(killometerInMeter)

// 같은 차원의 단위가 아니므로 컴파일 에러 발생
let nonsense = meter.converted(to: UnitAngle.degrees)

MeasurementFormatter

측정한 값을 국제화하여 사용자에게 보여주기 위해서는 MeasurementFormatter를 사용할 수 있습니다. 직접 모든 유형의 문자열을 작성하는 것은 복잡하고, 많은 문제를 야기할 수 있기 때문에 MeasurementFormatter는 아주 유용합니다. MeasurementFormatter는 NumberFormatter의 사용법은 본질적으로 동일합니다.

다음과 같이 복잡한 코드를 😫

let distance = Measurement(value: 10, unit: UnitLength.miles)

switch locale {
case .canada:
    // km로 변환하여 display

case .arabic:
    // km로 변환 후 right to left에 따라 `심볼 + 아랍 숫자`형식으로 display 🤯
}

MeasurementFormatter를 사용하면 간단하게 해결할 수 있습니다 😁

let distance = Measurement(value: 10, unit: UnitLength.miles)

let frenchDistance = MeasurementFormatter()
frenchDistance.locale = Locale(identifier: "fr")
let arabicDistance = MeasurementFormatter()
arabicDistance.locale = Locale(identifier: "ar")

// 🇫🇷 -> 16,093 km
print("🇫🇷 -> \(frenchDistance.string(from: distance))")
// 🇯🇴 -> ١٦٫٠٩٣ كم
print("🇯🇴 -> \(arabicDistance.string(from: distance))")

MeasurementFormatter는 여러 구성을 제공하는데 문서를 살펴보시면 많은 도움이 될 것 같습니다. Formatter는 기본적으로 시스템의 기본 Locale을 사용하기 때문에 실제 프로젝트에서는 예제코드와 같이 locale 프로퍼티에 직접 할당할 필요가 거의 없을 것입니다. 또한 MeasurementFormatter의 numberFormattter 프로퍼티를 통해 다양한 NumberFormatter의 기능도 함께 사용할 수 있으니 참고하세요.

Custom Units

아마 국제 단위계를 벗어나는 단위를 사용하는 일은 흔치 않겠지만, 만약 프로젝트에서만 사용되는 특정 단위가 있다면 링크Working with Custom Units 섹션을 참고하여 커스텀 단위를 만들 수 있습니다.

References

  • https://blog.daekyo.com/3125
  • https://www.kriss.re.kr/menu.es?mid=a10302020000
  • https://medium.com/the-traveled-ios-developers-guide/nsmeasurement-2be0d84bda64
  • https://betterprogramming.pub/unit-and-measurement-in-swift-7c6be4a25586

프로그래밍에서의 단위를 설명하는 좋은 자료가 있다면 댓글로 남겨주세요! 오타, 잘못된 정보 제보 또한 환영합니다 :)

댓글남기기