Jiseob Kim

iOS Developer

Swift - Property Wrapper (feat. Codable ์ฝ”๋“œ ์ •๋ฆฌ)

18 Jul 2021 » Swift


์ด๊ฒƒ์€ ์ด์ „ Property Wrapper ์‹œ๋ฆฌ์ฆˆ์˜ ์ตœ์ข… ์ฝ”๋“œ ์ ์–ด ๋‘๊ธฐ์šฉ

๋‚˜์ค‘์— ๋‚ด๊ฐ€ ๋ณด๊ธฐ ์‰ฝ๊ฒŒ!

๊ด€๋ฆฌ๊ฐ€ ์šฉ์ดํ•˜๊ฒŒ ์ฝ”์ฝ”์•„ํŒŸ์ด๋‚˜ SPM์œผ๋กœ ๋งŒ๋“ค์–ด๋ณด๊ณ  ์‹ถ๊ธฐ๋„ ํ•˜๋‹ค.

protocol JSONDecodeWrapperAvailable {
    associatedtype ValueType: Decodable
    static var defaultValue: ValueType { get }
}

protocol JSONStringConverterAvailable {
    static var defaultValue: Bool { get }
}

enum JSONDecodeWrapper {
    typealias EmptyString = Wrapper<JSONDecodeWrapper.TypeCase.EmptyString>
    typealias True = Wrapper<JSONDecodeWrapper.TypeCase.True>
    typealias False = Wrapper<JSONDecodeWrapper.TypeCase.False>
    typealias IntZero = Wrapper<JSONDecodeWrapper.TypeCase.Zero<Int>>
    typealias DoubleZero = Wrapper<JSONDecodeWrapper.TypeCase.Zero<Double>>
    typealias FloatZero = Wrapper<JSONDecodeWrapper.TypeCase.Zero<Float>>
    typealias CGFloatZero = Wrapper<JSONDecodeWrapper.TypeCase.Zero<CGFloat>>
    typealias StringFalse = StringConverterWrapper<JSONDecodeWrapper.TypeCase.StringFalse>
    typealias StringTrue = StringConverterWrapper<JSONDecodeWrapper.TypeCase.StringTrue>
    typealias EmptyList<T: Decodable & ExpressibleByArrayLiteral> = Wrapper<JSONDecodeWrapper.TypeCase.List<T>>
    typealias EmptyDict<T: Decodable & ExpressibleByDictionaryLiteral> = Wrapper<JSONDecodeWrapper.TypeCase.Dict<T>>
    
    // Property Wrapper - Optional Type to Type
    @propertyWrapper
    struct Wrapper<T: JSONDecodeWrapperAvailable> {
        typealias ValueType = T.ValueType

        var wrappedValue: ValueType

        init() {
        wrappedValue = T.defaultValue
        }
    }
    
    // Property Wrapper - Optional String To Bool
    @propertyWrapper
    struct StringConverterWrapper<T: JSONStringConverterAvailable> {
        var wrappedValue: Bool = T.defaultValue
    }
    
    // Property Wrapper - Optional Timestamp to Optinoal Date
    @propertyWrapper
    struct TimestampToOptionalDate {
        var wrappedValue: Date?
    }
    
    @propertyWrapper
    struct TrueByStringToBool {
        var wrappedValue: Bool = true
    }
    
    @propertyWrapper
    struct FalseByStringToBool {
        var wrappedValue: Bool = false
    }

    enum TypeCase {
        // Type Enums
        enum True: JSONDecodeWrapperAvailable {
            // ๊ธฐ๋ณธ๊ฐ’ - true
            static var defaultValue: Bool { true }
        }

        enum False: JSONDecodeWrapperAvailable {
            // ๊ธฐ๋ณธ๊ฐ’ - false
            static var defaultValue: Bool { false }
        }

        enum EmptyString: JSONDecodeWrapperAvailable {
            // ๊ธฐ๋ณธ๊ฐ’ - ""
            static var defaultValue: String { "" }
        }
        
        enum Zero<T: Decodable>: JSONDecodeWrapperAvailable where T: Numeric {
            // ๊ธฐ๋ณธ๊ฐ’ - 0
            static var defaultValue: T { 0 }
        }
        
        enum StringFalse: JSONStringConverterAvailable {
            // ๊ธฐ๋ณธ๊ฐ’ - false
            static var defaultValue: Bool { false }
        }
        
        enum StringTrue: JSONStringConverterAvailable {
            // ๊ธฐ๋ณธ๊ฐ’ - false
            static var defaultValue: Bool { true }
        }
        
        enum List<T: Decodable & ExpressibleByArrayLiteral>: JSONDecodeWrapperAvailable {
            // ๊ธฐ๋ณธ๊ฐ’ - []
            static var defaultValue: T { [] }
        }
        
        enum Dict<T: Decodable & ExpressibleByDictionaryLiteral>: JSONDecodeWrapperAvailable {
            // ๊ธฐ๋ณธ๊ฐ’ - [:]
            static var defaultValue: T { [:] }
        }
    }
}

extension JSONDecodeWrapper.Wrapper: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.wrappedValue = try container.decode(ValueType.self)
    }
}

extension JSONDecodeWrapper.StringConverterWrapper: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.wrappedValue = (try container.decode(String.self)) == "Y"
    }
}

extension JSONDecodeWrapper.TimestampToOptionalDate: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let timestamp = try container.decode(Double.self)
        let date = Date.init(timeIntervalSince1970: timestamp)
        self.wrappedValue = date
    }
}

extension KeyedDecodingContainer {
    func decode<T: JSONDecodeWrapperAvailable>(_ type: JSONDecodeWrapper.Wrapper<T>.Type, forKey key: Key) throws -> JSONDecodeWrapper.Wrapper<T> {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
    
    func decode<T: JSONStringConverterAvailable>(_ type: JSONDecodeWrapper.StringConverterWrapper<T>.Type, forKey key: Key) throws -> JSONDecodeWrapper.StringConverterWrapper<T> {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
    
    func decode(_ type: JSONDecodeWrapper.TimestampToOptionalDate.Type, forKey key: Key) throws -> JSONDecodeWrapper.TimestampToOptionalDate {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}