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

반응형
728x90
반응형
 
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에 파일을 저장하는 부분을 찾아보았는데, 직접 구현하는 것을 시도해보는 것도 중요하지만, 기존에 만들어진 라이브러리를 어떻게 사용하고 활용하는지도 중요하다고 생각합니다. 
728x90
반응형

댓글

Designed by JB FACTORY