기어가더라도 제대로

NSKeyedArchiver, NSKeyedUnarchiver 본문

UIKit 기초

NSKeyedArchiver, NSKeyedUnarchiver

Damagucci-juice 2022. 8. 23. 10:46

목차

1. KeyedArchiver 와 KeyedUnarchiver 란 무엇인가?
        1. 공식문서에서의 정의
2. 사용법
        1. 사용하기 위한 전제 조건
    2. 클래스에서 사용법
3. 요약 

KeyedArchiver 와 KeyedUnarchiver 알아보기

코드스쿼드 미션을 하다가 알게 되었다. 이것을 하다가 보면 깊은 복사와 얕은 복사도 알면 좋은데 그건 다음에 알아보기로 하자.

일단 영어 해석부터 해보자.

  • Keyed
    • 뭔가 키-값 쌍으로 저장할 것같다.
  • Archiver
    • archive 는 기록 보관소라고 직역하면 되는데, er 이 붙었으니까 기록을 보관해주는 녀석 같다.

아! 뭔가를 저장해주는 녀석이구나! 조선시대로 치면 사관같은 녀석이다. 아주 꼼꼼하게 기록하는 모양이다.

어떻게 꼼꼼하게 기록을 하느냐면 이전에 보았던 UserDefaults 에서 사용자의 앱의 설정을 저장 하는법을 배웠는데,

이 때 사용했던 것은 Data() 였다. 근데 이 Data 라는 타입은 바이너리로 저장을 한다.

2진수의 바이너리 타입으로 저장을 해서(인코딩), 2진수에서 다시 코드로 변환할 때(디코딩)

  • 이것의 원래 형태가 Int 인지, String 인지 혹은 이미지인지 사용자가 맵핑을 해줘야하고(오늘 배울것도 매핑 필요)
  • 깊이가 있는 타입의 경우 저장이 불가하다. 예를 들면 상속을 받는 클래스.

KeyedArchiver 는 Data 를 만드는 녀석이다. 그러나 약간의 개선을 했다.

무엇이냐면 클래스의 상위 하위 관계도 저장이 된 채로 Data 가 만들어진다.

눈치가 빠른 분들은 아~ 상위 하위가 저장이되니까 꼭대기도 있겠네? Root 객체도 있겠네 ~ 하면 합격이다. (하산하시라. 더 알려드릴게 없다.)

그래서 상하 관계가 있는 클래스 같은 객체를 데이터화하고 다시 원래 객채로 되돌려 주는 것이 KeyedArchiver, KeyedUnarchiver 이다.

공식문서에서의 정의

오브젝트의 데이터를 key로 참조해서 기록하는 encoder다. (캬 고급지다 고급져)

다른 블로거 분들 글을 보면 공식 문서를 인용하면 있어보여서 나도 해보려 한다.

(근데 나는 아직도 배움이 모자라서 그런지 공식문서부터 보면 알다가도 모르겠다.

제드님 소들이님 김종권님 사랑합니다.

)

encode 를 알려면 decode 도 알아야하는데,

  • Encode
    • 부호화하다.
    • 얼리다.
    • NSKeyedArchiver
  • Decode
    • 해독하다.
    • 녹이다.
    • NSKeyedUnarchiver

이렇게 생각하면 편하다.

  • NSCoder 의 구체 하위 클래스인 NSKeyedArchiver구조에 상관없이 하나의 파일안에 저장하기에 적합한 객체(그리고 스칼라 값)를 부호화한다.
    • Scalar - 방향은 없고 하나의 수치만으로 완전히 표현되는 값
  • 객체의 세트를 아카이브할 때, NSKeyedArchiver 는 클래스 정보와 인스턴스 변수들 각각을 아카이브로 기록한다.
  • 동료 클래스인 NSKeyedUnarchiver 는 기록 형태의 data 를 해석하고 원래의 세트와 동일한 객체의 세트를 생성한다.

아직 봐도 모르겠다. 직접 사용법을 익혀보시죠 ~

사용법

각각 아카이버와 언아카이버 모두 주로 사용하는 메서드가 있습니다.

  • 아카이버

  • 언아카이버

SecureCoding 이 기본값이다.
이것을 false를 주고
이것을 사용해보자.

  • unarchiveTopLevelObjectWithData(data)
    • 이 메서드를 사용하는 글들이 많았는데, deprecated 되서 사용할 수가 없었습니다.
  • unarchivedObject(ofClass:, from:)
    • 대안이 이 메서드인데 알 수 없는 이유로 decoding이 안되었습니다.
    • NSSecureCoding
  • decodeObject(forKey:)
    • 그래서 이 메서드를 사용했습니다.

사용하기 위한 전제 조건

클래스에 상관없이 독자적으로 오브젝트 세트를 엔코딩하고 디코딩할 수 있다고 했으니까요.

하위에 있는 모든 클래스 타입에서 NSCoding 을 채택해야합니다. 루트 객체의 타입에서만 NSCoding을 해주면 될 줄 았는데, 모든 타입이였어요.

예시는 SketchBook 이라는 클래스를 root 로 두고, Circle(원), Tirangle(삼각형) 을 루트의 속성으로 만들어 볼게요.

  1. 루트 객체는 NSObject, NSCoding 을 채택해야하고,
  2. 하위 객체들도 NSObject, NSCoding 을 채택하면 됩니다.
  3. NSCoding 을 구성할 때는
    1. encode(coder:)
      1. 여기서 클래스의 속성들에 값을 넣어줘야합니다.
    2. required init?(coder: NSCoder) { }
      1. 여기서 엔코딩 했던 값을 디코딩 해줘야합니다.
  • 삼각형
class Triangle: NSObject, NSCoding {

    let width: Double
    let height: Double

    required init?(coder: NSCoder) {
        width = coder.decodeDouble(forKey: "width")
        height = coder.decodeDouble(forKey: "height")
    }

    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }

    func encode(with coder: NSCoder) {
        coder.encode(width, forKey: "width")
        coder.encode(height, forKey: "height")
    }
}
  • 스케치북
class SketchBook: NSObject, NSCoding {

    let circle: Circle
    let triangle: Triangle
    let owner: String

    init(circle: Circle, triangle: Triangle, owner: String) {
        self.circle = circle
        self.triangle = triangle
        self.owner = owner
    }
    //Decode
    required init?(coder: NSCoder) {
        guard let circle = coder.decodeObject(forKey: "circle") as? Circle,
              let triangle = coder.decodeObject(forKey: "triangle") as? Triangle,
              let owner = coder.decodeObject(forKey: "owner") as? String
        else { assert(false) }

        self.circle = circle
        self.triangle = triangle
        self.owner = owner
    }

    func encode(with coder: NSCoder) {
        coder.encode(circle, forKey: "circle")
        coder.encode(triangle, forKey: "triangle")
        coder.encode(owner, forKey: "owner")
    }

}

이제 정말 사전 작업이 끝났습니다. 후.. 길죠?

클래스에서 사용법

아카이빙 언아카이빙이 오류가 잘나서 단계를 구분해 줬습니다.

오류가 많이 날 땐 스탭 바이 스탭으로 가면 오류를 줄일 수 있습니다 ~

Archiving

func archiving(rootObject: SketchBook) -> Data {
    do {
        let sketchData = try NSKeyedArchiver.archivedData(withRootObject: rootObject, requiringSecureCoding: false)
        return sketchData
    }
    catch {
        fatalError("encoding error")
    }
}
  • 아카이빙을 하는데요, 에러를 잘 확인하기 위해 do-catch 를 사용했습니다.
  • 제너럴을 하면 좀더 잘할거같은데 이렇게 했네요.

Unarchiving

func unarchiving(archivedData: Data) -> SketchBook {
    do {
        let unarchiver = try NSKeyedUnarchiver.init(forReadingFrom: archivedData)
        unarchiver.requiresSecureCoding = false
        guard let object = unarchiver.decodeObject(of: SketchBook.self, forKey: NSKeyedArchiveRootObjectKey) else { fatalError("") }
        return object
    } catch {
        fatalError("decoding error")
    }
}
  • NSKeyedUnarchiver.unarchiveObejct(ofClass: SketchBook.self, from: data) 을 사용하면 될 거 같은데, 여기서 계속 오류가 납니다.
    • 그래서 객채들이 NSCoding 대신에 NSSecureCoding
      사용하면 된다는데 다음에 한번 공부해보죠.
  • 그래서 위와 같이 해보았습니다.

추가로 알아볼것

  • 구조체에서 NSKeyed(Un)Archiver 사용법
  • NSSecureCoding 을 하면 되는지 확인

출처

Comments