[Swift] DateFormatter는 당신의 생각보다 비싸다

Mason Kim·2023년 11월 17일
4

swift

목록 보기
7/7

TL-DR

  • DateFormatter를 매번 생성해서 사용하는 것은 메모리상 생각보다 더 비싼 비용입니다.

  • DateFormatter는 Thread-Safe 하기 때문에, 전역적으로 생성해서 재사용 (캐싱) 하는 것을 제안합니다.

근거

[ Data Formatting Guide from Apple ]
Cache Formatters for Efficiency
Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, it is typically more efficient to cache a single instance than to create and dispose of multiple instances. One approach is to use a static variable.

  • 날짜 포맷터를 생성하는 것은 비용이 많이 드는 작업입니다. 포맷터를 자주 사용할 가능성이 높은 경우 일반적으로 여러 인스턴스를 생성하고 폐기하는 것보다 단일 인스턴스를 캐시하는 것이 더 효율적입니다. 한 가지 접근 방식은 정적 변수를 사용하는 것입니다.

  • 실제로 메모리 시간을 측정한 테스트 결과를 보면, 많은 경우 UIView 를 생성하는 것보다도 더 비싼 비용임을 확인할 수 있습니다.
    출처: https://sarunw.com/posts/how-expensive-is-dateformatter/

문제는 없을까?

  • DateFormatter를 전역적으로 생성해서 재사용 할 때의 문제는 크지 않을까요?
  • 시간복잡도는 크지만 DateFormatter 인스턴스를 유지하는데 사용되는 공간복잡도는 그리 크지 않습니다.
  • DateFormatterThread-Safe 합니다. (DateFormatter 공식문서)

❌ AS-IS

  • 많은 예제 코드에서, 메서드의 매 호출마다 DateFormatter 를 생성하고 있습니다.
extension Date {
    func format(to dateFormat: String) -> Date {
        let dateString = string(to: dateFormat)
        // 🤯 인스턴스 매번 생성
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = dateFormat
        return dateFormatter.date(from: dateString) ?? self
    }

    func string(to dateFormat: String) -> String {
        // 🤯 인스턴스 매번 생성
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = dateFormat
        dateFormatter.locale = Locale(identifier: "ko_KR")
        return dateFormatter.string(from: self)
    }
}

✅ TO-BE

  • 그러므로 아래와 같은 구조로 사용하는 것을 제안합니다.
enum DateFormat: String {
    /// 날짜 - "yyyy.MM.dd"
    case yearMonthDate = "yyyy.MM.dd"
    /// 월, 일 - "MM.dd"
    case monthDate = "MM.dd"
    ...
}

extension DateFormat {
    /// 각 DateFormat에 따른 formatter
    ///
    /// - 미리 생성해둔 인스턴스를 재사용함
    /// - DateFormatter 의 생성 비용은 굉장히 비싸서 재사용 하는 것이 효율적
    /// - DateFormatter는 thread-safe 하기에 전역적으로 사용 가능
    var formatter: DateFormatter {
        guard let formatter = DateFormat.cachedFormatters[self] else {
            let generatedFormatter = DateFormat.makeFormatter(withDateFormat: self)
            DateFormat.cachedFormatters[self] = generatedFormatter
            return generatedFormatter
        }

        return formatter
    }

    private static var cachedFormatters: [DateFormat: DateFormatter] = [:]

    private static func makeFormatter(withDateFormat dateFormat: DateFormat) -> DateFormatter {
        let formatter = DateFormatter()
        formatter.dateFormat = dateFormat.rawValue
        formatter.locale = .current
        return formatter
    }
}

/// 사용
extension Date {
    func formattedString(dateFormat: DateFormat) -> String {
        // ✅ 딕셔너리로 캐싱된 DateFormatter 를 재사용
        return dateFormat.formatter.string(from: self)
    }
}


  • 계산 프로퍼티에서 매번 DateFormatter 를 생성해서 사용할 경우...
profile
iOS developer

2개의 댓글

comment-user-thumbnail
2023년 11월 17일

이제 sexy하게 code를 작성할 수 있겠네욤 하핫 좋은글 감사합니다

1개의 답글