์ง๋ํธ์๋ SPM
์ ๋ํด์ ๋ง๋๋ ๊ธฐ๋ณธ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด์๋ค.
์ด๋ฒํธ์์๋ ์ง์ง๋ํธ์ ๋ง๋ฌด๋ฆฌ๋ฅผ ์ง์๋ Property Wrapper
๋ฅผ SPM์ผ๋ก ๋ง๋ค์ด์ ์ ์ฉํ์.
์ง๋ํธ ํ์ผ์๋ค๊ฐ ์๋ก์ด swift
ํ์ผ์ ํ๋ ๋ง๋ค์ด์ฃผ์.
๊ทธ๋ฆฌ๊ณ ์ง๋ํธ์ ๊ธ์์ ์ฝ๋๋ฅผ ๊ฐ์ ธ์ค์.
protocol JSONDecodeWrapperAvailable {
associatedtype ValueType: Decodable
static var defaultValue: ValueType { get }
}
protocol JSONStringConverterAvailable {
static var defaultValue: Bool { get }
}
enum JSWrapper {
typealias EmptyString = Wrapper<JSWrapper.TypeCase.EmptyString>
typealias True = Wrapper<JSWrapper.TypeCase.True>
typealias False = Wrapper<JSWrapper.TypeCase.False>
typealias IntZero = Wrapper<JSWrapper.TypeCase.Zero<Int>>
typealias DoubleZero = Wrapper<JSWrapper.TypeCase.Zero<Double>>
typealias FloatZero = Wrapper<JSWrapper.TypeCase.Zero<Float>>
typealias CGFloatZero = Wrapper<JSWrapper.TypeCase.Zero<CGFloat>>
typealias StringFalse = StringConverterWrapper<JSWrapper.TypeCase.StringFalse>
typealias StringTrue = StringConverterWrapper<JSWrapper.TypeCase.StringTrue>
typealias EmptyList<T: Decodable & ExpressibleByArrayLiteral> = Wrapper<JSWrapper.TypeCase.List<T>>
typealias EmptyDict<T: Decodable & ExpressibleByDictionaryLiteral> = Wrapper<JSWrapper.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 JSWrapper.Wrapper: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = try container.decode(ValueType.self)
}
}
extension JSWrapper.StringConverterWrapper: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = (try container.decode(String.self)) == "Y"
}
}
extension JSWrapper.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: JSWrapper.Wrapper<T>.Type, forKey key: Key) throws -> JSWrapper.Wrapper<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
func decode<T: JSONStringConverterAvailable>(_ type: JSWrapper.StringConverterWrapper<T>.Type, forKey key: Key) throws -> JSWrapper.StringConverterWrapper<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
func decode(_ type: JSWrapper.TimestampToOptionalDate.Type, forKey key: Key) throws -> JSWrapper.TimestampToOptionalDate {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
์กฐ๊ธ ๊ธธ์๋ค.
์๋ฌดํผ! ์ด์ํ๋ก ๋น๋ํด๋ณด๋ฉด ์คํจํ๋ค.
CGFloat
์ ์ฌ์ฉ ํ์๋๋ฐ, UIKit
์ import
์ํด์คฌ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฐ๋ฐ, import UIKit
์ ํ๋๋ผ๋ ํ๊ฒ์ ๋ฐ๋ผ ์ฑ๊ณตํ ์๋ ์คํจํ ์๋ ์๋ค.
์๋ํ๋ฉด ๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด UIKit์ ๊ฒฝ์ฐ iOS, tvOS์๋ง ๊ฐ๋ฅํ ํ๋ ์์ํฌ์ด๊ธฐ ๋๋ฌธ!
ํ๊ฒ์ Any iOS Device
์ ๋ง์ถฐ์ฃผ์,
๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์๋ iOS์์๋ง ๊ฐ๋ฅํ๊ฒ ํ ๊ฑฐ๋๊น package
๋ฅผ ์ด์ง ์๋ด์ฃผ์
์๋ฏธ๋ ๊ฐ๋จํ๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ํ๋ซํผ์ ๋ฐฐ์ด ํํ๋ก ์ ์ ํด์ค๋ค.
๋ฐฐ์ด์ ๋ค์ด๊ฐ๋ ์๋ฃํ์ SupportedPlatform
์ผ๋ก ๊ตฌ์กฐ์ฒด
์ด๋ค
์์ ์๋ iOS
๋ enum
์ ์ธ๊ฑฐ๋ผ ์์ ํ๋๋ฐ, ๊ธฐ๊ฐ๋งํ๊ฒ๋ ์๋ฒฝํ ํ๋ ธ๋ค.
.iOS
: ํ์ ๋ฉ์๋.v9
: ํ์ ํ๋กํผํฐ
๋๋ฌด๋๋ ์ง๊ด์ ์ผ๋ก ํด์๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ํ๋ซํผ์ iOS
๊ณ v9
์ด์์์๋ง ๊ฐ๋ฅํ๋ค.
๋ฐ๋ผ์ ์ค๋ช ํจ์ค.
ํ๊ฒ ๋ง์ถ๊ณ , ํ๋ซํผ์ ์ ์ํด์ฃผ๊ณ ๋์ ๋น๋๋ฅผ ํ๋ฉด ์ฑ๊ณต์ ํ๊ฒ ๋๋ค.
ํ์ง๋ง,
์ ์ ๊ธ์ ๋ดค๋ค๋ฉด ์คํจํ ๊ฒ์ด๋ ๊ฒ์ ์ ์ ์๋ค.
์ ๊ทผ์ ์ด์๊ฐ internal
๋ก ๋์ด์๊ธฐ์ ์์ ์ด ์ฐ๋ ค๋ ํ๋ก์ ํธ์์
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ import
ํด์ฃผ์ด๋ ์ ๊ทผ์ด ๋ถ๊ฐํ๋ค.
์จ์ผํ ๊ณณ๋ค์ ์ ๊ทผ์ ์ด์
๋ฅผ public
๋ก ์์ ํด์ฃผ์.
public protocol JSONDecoderWrapperAvailable {
associatedtype ValueType: Decodable
static var defaultValue: ValueType { get }
}
public protocol JSONStringConverterAvailable {
static var defaultValue: Bool { get }
}
public enum JSWrapper {
public typealias EmptyString = Wrapper<JSWrapper.TypeCase.EmptyString>
public typealias True = Wrapper<JSWrapper.TypeCase.True>
public typealias False = Wrapper<JSWrapper.TypeCase.False>
public typealias IntZero = Wrapper<JSWrapper.TypeCase.Zero<Int>>
public typealias DoubleZero = Wrapper<JSWrapper.TypeCase.Zero<Double>>
public typealias FloatZero = Wrapper<JSWrapper.TypeCase.Zero<Float>>
public typealias CGFloatZero = Wrapper<JSWrapper.TypeCase.Zero<CGFloat>>
public typealias StringFalse = StringConverterWrapper<JSWrapper.TypeCase.StringFalse>
public typealias StringTrue = StringConverterWrapper<JSWrapper.TypeCase.StringTrue>
public typealias EmptyList<T: Decodable & ExpressibleByArrayLiteral> = Wrapper<JSWrapper.TypeCase.List<T>>
public typealias EmptyDict<T: Decodable & ExpressibleByDictionaryLiteral> = Wrapper<JSWrapper.TypeCase.Dict<T>>
// Property Wrapper - Optional Type to Type
@propertyWrapper
public struct Wrapper<T: JSONDecoderWrapperAvailable> {
public typealias ValueType = T.ValueType
public var wrappedValue: ValueType
public init() {
wrappedValue = T.defaultValue
}
}
// Property Wrapper - Optional String To Bool
@propertyWrapper
public struct StringConverterWrapper<T: JSONStringConverterAvailable> {
public var wrappedValue: Bool = T.defaultValue
public init() {
wrappedValue = T.defaultValue
}
}
// Property Wrapper - Optional Timestamp to Optinoal Date
@propertyWrapper
public struct TimestampToOptionalDate {
public var wrappedValue: Date?
public init() {
wrappedValue = nil
}
}
public enum TypeCase {
// Type Enums
public enum True: JSONDecoderWrapperAvailable {
// ๊ธฐ๋ณธ๊ฐ - true
public static var defaultValue: Bool { true }
}
public enum False: JSONDecoderWrapperAvailable {
// ๊ธฐ๋ณธ๊ฐ - false
public static var defaultValue: Bool { false }
}
public enum EmptyString: JSONDecoderWrapperAvailable {
// ๊ธฐ๋ณธ๊ฐ - ""
public static var defaultValue: String { "" }
}
public enum Zero<T: Decodable>: JSONDecoderWrapperAvailable where T: Numeric {
// ๊ธฐ๋ณธ๊ฐ - 0
public static var defaultValue: T { 0 }
}
public enum StringFalse: JSONStringConverterAvailable {
// ๊ธฐ๋ณธ๊ฐ - false
public static var defaultValue: Bool { false }
}
public enum StringTrue: JSONStringConverterAvailable {
// ๊ธฐ๋ณธ๊ฐ - false
public static var defaultValue: Bool { true }
}
public enum List<T: Decodable & ExpressibleByArrayLiteral>: JSONDecoderWrapperAvailable {
// ๊ธฐ๋ณธ๊ฐ - []
public static var defaultValue: T { [] }
}
public enum Dict<T: Decodable & ExpressibleByDictionaryLiteral>: JSONDecoderWrapperAvailable {
// ๊ธฐ๋ณธ๊ฐ - [:]
public static var defaultValue: T { [:] }
}
}
}
extension JSWrapper.Wrapper: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = try container.decode(ValueType.self)
}
}
extension JSWrapper.StringConverterWrapper: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = (try container.decode(String.self)) == "Y"
}
}
extension JSWrapper.TimestampToOptionalDate: Decodable {
public 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 {
public func decode<T: JSONDecoderWrapperAvailable>(_ type: JSWrapper.Wrapper<T>.Type, forKey key: Key) throws -> JSWrapper.Wrapper<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
public func decode<T: JSONStringConverterAvailable>(_ type: JSWrapper.StringConverterWrapper<T>.Type, forKey key: Key) throws -> JSWrapper.StringConverterWrapper<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
public func decode(_ type: JSWrapper.TimestampToOptionalDate.Type, forKey key: Key) throws -> JSWrapper.TimestampToOptionalDate {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
์ฎ์ด๊ณ ์ฎ์ด๋ค๋ณด๋ ์๊ฐ๋ณด๋ค ๋ง์ ๊ณณ์ public
์ ๋ถ์ฌ์คฌ๋ค.
๊ฑฐ์ง ๋ค๋ถ์๋ค๊ณ ๋ด์ผํ ๋ฏ ํ๋ค.
์ด์ ์ปค๋ฐํ๊ณ ํธ์๊น์ง ํด์ฃผ์.
์ด๋ฒ์๋ Branch
๋ก ๋ถ๋ฌ์ค๊ฒ ํ ๊ฒ์ด๊ณ , ์ด๋ฒ ๋ธ๋์น ๋ช
์ Feature/PropertyWrapper
๋ก ์ ํ๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐ์ ธ์ค์.
๊ฐ์๊ธฐ ๋คํฌ๋ชจ๋ ๋๊ฒ ๊ฐ๋ค๋ฉด ๊ทธ๊ฒ์ ๊ธฐ๋ถํ.
๊ทธ๋ฆฌ๊ณ , import JSLibrary
ํด์ฃผ๊ณ ํ์ธ ์ฝ๋์ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ๋ค
// ์ฃผ์1. wrapper ํ
์คํธํ ํด๋์ค
class TestClass: Decodable {
@JSWrapper.EmptyString var emptyString: String
@JSWrapper.IntZero var zeroInt: Int
@JSWrapper.False var falseBool: Bool
}
์ฃผ์ 1์ ์ฝ๋๋ ์์ ๊ฐ์๋ฐ, SPM์ผ๋ก ๋ง๋ค์ด์ค JSWrapper
๋ฅผ ์ด์ฉํ๋ค๋๊ฒ ์ค์ ์ด๋ค.
๊ทธ ์ญํ ์ ์์ ๊ฒฐ๊ณผ์๋ ๊ฐ์ผ๋ฉฐ ์ฌ์ฉ๋ฒ์ ์ด์ ๊ธ๋ค์ ์ฐธ๊ณ ํ๋ฉด ์ข์ ๋ฏํ๋ค.
๊ฒฐ๋ก ์ ์๋๋ค!
์์ ์ ํ๋ ์์ํฌ ๋ง๋ค๋ค๊ฐ ์คํจํ์ ์ด ์์๋๋ฐ,
SPM ๋ง๋ค๊ธฐ๊ฐ ์๊ฐ๋ณด๋ค ๋๋ฌด ๊ฐ๋จํด์ ์ ์ฉํ๋ค ์๊ฐ๋๋ ๊ฒ๋ค์
๋ด ์ด๋ฆ์ ๋ฃ๊ณ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ๋ ๋ง๋ค๋ฉด ์ข์ ๋ฏํ๋ค.
๋!