iOS에서 JSON형태로 로컬파일 저장하기(JSON using coable)

 
Introduction

  이번 포스팅은 SwiftUI를 이용하여 iOS App을 개발하는데 있어 필요한 모듈입니다. 이 모듈은 iOS기기 내부 로컬에 파일에 JSON형태로 저장을 하는 모듈입니다. 하지만, 이 모듈을 이용해서 쉽게 저장/로드 할 수 있지만, 몇몇 특이한 형태의 구조를 가지는 클래스/구조체/열거체의 경우 저장하는 모듈이 제대로 수행되지 않습니다. 조금의 삽질 끝으로 문제점과 해결방법을 알아보도록 하겠습니다. 
 
JSON 모듈을 save/load 하기위해서 github 혹은 cocoapods 에서 아래의 Library를 검색 가능합니다. 
 
 

 

 

 
문제점(Problem)

 
먼저 문제점을 확인해보도록 하겠습니다. 상위 라이브러리를 이용하여 쉽게 저장/로드를 쉽게 수행할 수 있습니다. 하지만 아래의 열거체를 그대로 JSON형태로 저장할 때 오류가 발생하게 됩니다. 
enum TalkDirection: Int, Codable {
    case left
    case right
}
TalkDirection 열거체에는 Codable이 있지만 Decodable이 없습니다. 
enum TalkMsgType: Int, Codable {
    case message
    case image
    case webLink
    case emotion
}
TalkMsgType 열거체 또한 Codable이 있지만 Decodable이 없습니다. 
struct Talk: Codable, Identifiable {
    let id:UUID = UUID()
    
    var Name:String
    var Message:String
    var Direction:TalkDirection
    var type:TalkMsgType
    
    init(_ name:String, _ message:String, _ direction:TalkDirection, _ type:TalkMsgType){
        self.Name      = name
        self.Message    = message
        self.Direction  = direction
        self.type      = type
    }
}
그리고 Talk 구조체에서는 상위 TalkDirectionTalkMsgType 열거체를 가지고 있습니다. 이러한 구조체를 JSON형태로 저장을 해야합니다. 하지만, 열거체의 경우, 어떤 Type의 값을 가지는지 알수 없어 JSON형태로 변환 할 수 없습니다. 
 

 

 

 
해결방법(Solve)

 
해결방법은 생성자(init)를 이용하여 Decoder를 구현하는 것입니다. 그리고 반대로 encode를 할 경우 따로 함수를 만들어서 사용하면 됩니다. 문제를 해결하기위해 열거체에서 extension을 이용하여 아래와 같이 코드를 추가합니다. 
extension TalkDirection {
    
    private enum CodingKeys: String, CodingKey {
        case left
        case right
    }
    
    enum CodingError: Error {
        case decoding(String)
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if let value = try? values.superDecoder(forKey: .left){
            self = .left
            return
        }
        
        if let value = try? values.superDecoder(forKey: .right){
            self = .right
            return
        }
        throw CodingError.decoding("Error: TalkDirection-init()")
    }
    
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .left:
            try container.encode("left", forKey: .left)
        case .right:
            try container.encode("right", forKey: .right)
        }
    }
}
상위 코드와 같이 CodingKeys 부터 CodingError를 같이 구현해 주도록 합니다. 그리고 TalkMsgType 열거체도 동일하게 구현을 합니다. 
extension TalkMsgType {
    private enum CodingKeys: String, CodingKey {
        case message
        case image
        case webLink
        case emotion
    }
    
    enum CodingError: Error {
        case decoding(String)
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        if let value = try? values.superDecoder(forKey: .message){
            self = .message
            return
        }
        if let value = try? values.superDecoder(forKey: .image){
            self = .image
            return
        }
        if let value = try? values.superDecoder(forKey: .webLink){
            self = .webLink
            return
        }
        if let value = try? values.superDecoder(forKey: .emotion){
            self = .emotion
            return
        }
        throw CodingError.decoding("Error: TalkDirection-init()")
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .message:
            try container.encode("message", forKey: .message)
        case .image:
            try container.encode("image", forKey: .image)
        case .webLink:
            try container.encode("webLink", forKey: .webLink)
        case .emotion:
            try container.encode("emotion", forKey: .emotion)
        }
    }
}
열거체의 case에 따라 동일하게 구현하시면 됩니다. 그리고 열거체에 여러가지 타입들이 있지만, 이와 유사하게 구현하시면 됩니다. 
이제 사용방법은 다음과 같이 코드를 작성하시면 됩니다. 
    func testTalkLog() {
        
        var key:String = "TalkLog"

        var TalkSamples = [
            Talk("박민호", "안녕하세요",TalkDirection.left, TalkMsgType.message),
            Talk("노은지", "안녕하세요",TalkDirection.right, TalkMsgType.message),
            
            Talk("박민호", "SwiftUI 한잔 어때요?",TalkDirection.left, TalkMsgType.message),
            Talk("노은지", "네, 좋아요 ",TalkDirection.right, TalkMsgType.message),
            
            Talk("박민호", "어디서 볼까요?",TalkDirection.left, TalkMsgType.message),
            Talk("노은지", "근처 카페에서 봐요",TalkDirection.right, TalkMsgType.message),
            
            Talk("박민호", "네, 있다가 뵐께요",TalkDirection.left, TalkMsgType.message),
            Talk("노은지", "네",TalkDirection.right, TalkMsgType.message),
        ]
        
        
        do {
            try storage.save(object: TalkSamples, forKey: key)
            storage.cache.removeAllObjects()
            
            let loadedUsers = try storage.load(forKey: key, as: [Talk].self)
            
            print(loadedUsers)
            
            try storage.remove(forKey: key)
        }catch {
            print("error: testUserInformation()")
        }
    }
상위 코드는 앞서 설명한 EasyStash Library를 이용하여 사용하시면 됩니다. 
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        var myJSONTester:JSONTester = JSONTester()
        myJSONTester.setUp()
        myJSONTester.testTalkLog()
        
        return true
    }
그리고 Xcode에서 시뮬레이터를 이용하여 확인을 하기위해 application() 함수에 상위 코드와 같이 작성을 하시면 됩니다.  코드가 제대로 돌아가는지 확인을 하기위해서는 브레이크 포인트를 설정하고 디버깅 모드를 이용하여 출력되는 값 혹은 실행 중 값을 확인하시면 됩니다. 

 

 

 
마무리

  SwiftUI를 공부하면서 JSON형태로 iOS에 파일을 저장하는 부분을 찾아보았는데, 직접 구현하는 것을 시도해보는 것도 중요하지만, 기존에 만들어진 라이브러리를 어떻게 사용하고 활용하는지도 중요하다고 생각합니다. 

이 글을 공유하기

댓글(0)

Designed by JB FACTORY