기어가더라도 제대로

[SwiftUI-기초] @Published 프로퍼티를 지닌 Codable 타입 저장 본문

SwiftUI - 기초

[SwiftUI-기초] @Published 프로퍼티를 지닌 Codable 타입 저장

Damagucci-juice 2022. 11. 24. 01:44

2022.11.23 - [SwiftUI - 기초] - [SwiftUI-기초] UserDefaults 와 SwiftUI

이 글은 위의 포스팅의 후속편입니다. 

위의 글은 User 타입이 구조체였지만 이번 포스팅에서 다룰 내용은 User가 클래스고
@Published 속성의 프로퍼티를 가지고 있을 때 대처하는 방법입니다. 

Struct + Codable

struct User: Codable { }
  • 구조체의 타입을 Encodig, Decoding하는 방법은 별다를게 없고, Codable만 설정해주면 됩니다.
  • 자동적으로 Encoding, Decoding 로직이 돈다는 것이죠. 
  • 요약하자면 Struct 타입을 디스크에 저장하는 방법은 Codable 을 이용한다는 것입니다. 

Class + Codable + @Published ...? 

class User: ObservableObject, Codable {
	@Published var name = "Damagucci_Juice"
}
  • 위의 타입을 구조체에서 클래스로 변경하라하면 이렇게 할 거 같습니다. 
  • 그렇지만 이 코드는 빌드가 안되는 단점이 있는데요. 
  • 왜 그럴까요?

@Published

  • 이 속성은 오는 타입을 발행해주는 마법같은 존재라기보단 내부적으로 어떤 타입으로 변환시켜주는 것입니다. 
  • 위의 name 프로퍼티는 원래 String 타입이지만, @Published가 붙으면 Published<String> 타입이 되는 것이지요. 
  • 마치 Set, Array, Dictionary 와 역할이 비슷하네요. 
  • Set<Int> 는 Int가 아니죠. Int의 Set이죠. 
  • @Published 된 프로퍼티는 Published<Value> 가 되는 것입니다.

Array, Set, Dictionary <-> @Published 

  • 공통점: 위 제목에 나온 타입들은 그들 내부에 특정한 타입을 가져야한다는 것입니다.
    • 예) Set<Int>, Array<String>, Published<Int>
  • 차이점: 
  • 왼쪽들은 오는 Value가 Codable 을 채택할 수 있다면 합쳐진 타입도 Codable합니다.
    • 예) Int + Array = [Int], Int 타입이 Codable 하면 [Int] 도 Codable합니다.
  • @Published 는 그렇지 않죠.
    • 예) Int + Published = Published<Int>, Int 타입이 Codable 하지만 Published<Int> 는 Codable 하지 않습니다.
  • 우리의 목표는 Published<Value> 타입을 Codable 하게 만들면 되겠네요!

 

@Published 를 Codable 채택하게 만들기

User 클래스가 Codable 하게 되려면 Published<Value> 타입의 Codable이 가능해져야합니다. 

그런데 말입니다. Codable이 무어란 말입니까? 어떤 타입이 Encoding 되고 Decoding 될 수 있음을 보장하는 프로토콜입니다. 

그러면 클래스의 모든 프로퍼티에 대해서 Encoding, Decoding 하는 방법을 마련해준다면 Codable을 채택할 수 있겠죠?

CodingKeys

  • CodingKey 라는 프로토콜을 채택한 열거형을 만들면 되는데 보통 이런식으로 많이 합니다. 
  • User 클래스 내부의 중첩 타입으로 선언합니다. 
enum CodingKeys: CodingKey {
	case name
}
  • Archive 와 Unarchive 하려는 모든 프로퍼티를 나열해줍니다.
    • 우리의 경우엔 name 프로퍼티 하나 있으므로 name 케이스 하나만 적습니다. 

Decoding(Unarchiving) - Custom Initializer

  • User 클래스가 생성될 때 디스크에 저장되어 있는 값이 있다면 Decoding(Unarchiving)을 해줘야합니다.
  • 커스텀 이니셜라이저를 사용해서 해보겠습니다. 다음의 4단계를 거칩니다. 
    • 1. Decoder 타입의 인스턴스를 매개변수로 받습니다.
    • 2. User 의 하위 클래스들도 이 과정을 거칠 수 있도록 커스텀 이니셜라이저를 required 로 선언해줍니다. 
      • User가 상속하지 않는다면 final 키워드를 붙여줌으로써 required 넣기를 생략합니다. 
    • 3. Decoder 인스턴스로 Container 를 불러옵니다. 컨테이너는 CodingKeys 에서 정의한 코딩키들에 매칭됩니다.
      • 이 과정에서 Key 가 존재하지 않을 수 있어서 오류의 가능성이 있어 throws 으로 에러 가능성을 내포합니다.
    • 4. 코딩키에 있는 케이스를 참조해 컨테이너로부터 값을 직접 읽을 수 있습니다. 
required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    name = try container.decode(String.self, forKey: .name)
}

Encoding(Archiving) - func encode(to: )

  • 위의 Decoding 과정과 반대되는 경우입니다. 
  • Decoding + Encoding = Codable 이니까 필요합니다. 
  • 과정은 Decoding 과 비슷합니다. 
    • 1. Encoder 타입의 인스턴스를 파라미터로 받습니다. 
    • 2. encoder 로 CodingKeys 의 케이스에 맞게 container를  불러옵니다. 
    • 3. 각각의 key 에 해당하는 value 를 저장합니다. 
func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CondingKeys.self)
    try container.encode(name, forKey: .name)
}

 

결론

Comments