iOS 개발에서 plist 파일 읽기: PropertyListDecoder 활용하기

iOS 애플리케이션을 개발하다 보면 구성 데이터나 설정 정보를 저장하고 로드해야 하는 경우가 많습니다. Apple의 생태계에서는 이러한 데이터를 관리하기 위한 표준 형식으로 Property List(plist)를 제공합니다. 이번 포스팅에서는 Swift에서 plist 파일을 읽는 방법에 대해 알아보겠습니다.

Property List(plist)란?

Property List는 애플 생태계에서 구조화된 데이터를 직렬화하는 데 사용되는 XML 기반의 파일 형식입니다. iOS와 macOS 앱에서 설정, 환경 설정, 구성 데이터 등을 저장하는 데 널리 사용됩니다.

plist는 다음과 같은 데이터 타입을 지원합니다:

  • 배열 (Array)
  • 딕셔너리 (Dictionary)
  • 문자열 (String)
  • 숫자 (Number) – 정수 및 실수
  • 날짜 (Date)
  • 불리언 (Boolean)
  • 데이터 (Data)

plist 파일 예제

다음과 같은 간단한 정수 배열이 포함된 test.plist 파일이 있다고 가정해 봅시다:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <integer>73</integer>
    <integer>72</integer>
    <integer>142</integer>
    <integer>143</integer>
    <integer>132</integer>
    <integer>133</integer>
    <integer>133</integer>
    <integer>122</integer>
    <integer>102</integer>
    <integer>102</integer>
    <integer>102</integer>
    <integer>73</integer>
</array>
</plist>

이 파일은 단순히 12개의 정수가 포함된 배열을 정의합니다.

Swift에서 plist 파일 읽기

Swift 4 이상에서는 PropertyListDecoder를 사용하여 plist 파일을 쉽게 디코딩할 수 있습니다. 이 클래스는 Codable 프로토콜과 함께 작동하여 plist 데이터를 Swift 타입으로 변환합니다.

다음은 위의 test.plist 파일을 읽어 Array<Int> 타입으로 디코딩하는 코드입니다:

var dataSource: Array<Int> = Array()

let lvPath = Bundle.main.url(forResource: "test", withExtension: "plist")!
let lvArray = try! Data(contentsOf: lvPath)
let decoder = PropertyListDecoder()
dataSource = try! decoder.decode(Array<Int>.self, from: lvArray)

print("jhFile_dataSource => ", dataSource)

이 코드의 실행 결과는 다음과 같습니다:

jhFile_dataSource =>  [73, 72, 142, 143, 132, 133, 133, 122, 102, 102, 102, 73]

코드 분석

위 코드를 단계별로 분석해 보겠습니다:

  1. 데이터 소스 배열 초기화: var dataSource: Array<Int> = Array() 정수 배열을 저장할 변수를 선언합니다.
  2. plist 파일 경로 가져오기: let lvPath = Bundle.main.url(forResource: "test", withExtension: "plist")! 앱 번들에서 “test.plist” 파일의 URL을 가져옵니다. Bundle.main은 현재 실행 중인 앱의 메인 번들을 나타냅니다.
  3. 파일 데이터 로드: let lvArray = try! Data(contentsOf: lvPath) URL에서 파일의 내용을 Data 객체로 로드합니다.
  4. PropertyListDecoder 생성: let decoder = PropertyListDecoder() plist 데이터를 디코딩하기 위한 PropertyListDecoder 인스턴스를 생성합니다.
  5. 데이터 디코딩: dataSource = try! decoder.decode(Array<Int>.self, from: lvArray) 로드된 데이터를 Array<Int> 타입으로 디코딩합니다. 여기서 .self는 타입 자체를 참조하는 데 사용됩니다.
  6. 결과 출력: print("jhFile_dataSource => ", dataSource) 디코딩된 배열을 콘솔에 출력합니다.

에러 처리 개선하기

위 코드에서는 간결성을 위해 try!를 사용했지만, 실제 애플리케이션에서는 적절한 에러 처리가 필요합니다. 다음은 에러 처리를 개선한 버전입니다:

var dataSource: Array<Int> = Array()

do {
    if let lvPath = Bundle.main.url(forResource: "test", withExtension: "plist") {
        let lvArray = try Data(contentsOf: lvPath)
        let decoder = PropertyListDecoder()
        dataSource = try decoder.decode(Array<Int>.self, from: lvArray)
        print("jhFile_dataSource => ", dataSource)
    } else {
        print("Error: plist 파일을 찾을 수 없습니다.")
    }
} catch {
    print("Error: plist 로딩 중 오류 발생 - \(error)")
}

복잡한 plist 구조 다루기

단순한 배열 외에도 plist는 복잡한 중첩 구조를 가질 수 있습니다. 예를 들어, 앱 설정을 저장하는 plist 파일의 구조가 다음과 같다고 가정해 봅시다:

<plist version="1.0">
<dict>
    <key>appName</key>
    <string>MyAwesomeApp</string>
    <key>version</key>
    <string>1.0.0</string>
    <key>settings</key>
    <dict>
        <key>darkMode</key>
        <true/>
        <key>fontSize</key>
        <integer>14</integer>
        <key>refreshInterval</key>
        <integer>60</integer>
    </dict>
    <key>recentSearches</key>
    <array>
        <string>Swift</string>
        <string>plist</string>
        <string>iOS</string>
    </array>
</dict>
</plist>

이런 복잡한 구조를 디코딩하려면 해당 구조에 맞는 Swift 타입을 정의해야 합니다:

struct AppConfig: Codable {
    let appName: String
    let version: String
    let settings: Settings
    let recentSearches: [String]
    
    struct Settings: Codable {
        let darkMode: Bool
        let fontSize: Int
        let refreshInterval: Int
    }
}

// 디코딩
do {
    if let configPath = Bundle.main.url(forResource: "AppConfig", withExtension: "plist") {
        let configData = try Data(contentsOf: configPath)
        let decoder = PropertyListDecoder()
        let appConfig = try decoder.decode(AppConfig.self, from: configData)
        
        print("App name: \(appConfig.appName)")
        print("Dark mode enabled: \(appConfig.settings.darkMode)")
        print("Recent searches: \(appConfig.recentSearches)")
    }
} catch {
    print("Error: \(error)")
}

plist 파일 쓰기

파일을 읽는 것뿐만 아니라 plist 파일을 생성하거나 업데이트해야 하는 경우도 있습니다. 이를 위해 PropertyListEncoder를 사용할 수 있습니다:

let numbers = [73, 72, 142, 143, 132, 133, 133, 122, 102, 102, 102, 73]

do {
    let encoder = PropertyListEncoder()
    encoder.outputFormat = .xml  // XML 형식의 plist 생성
    
    let data = try encoder.encode(numbers)
    
    // 문서 디렉토리에 plist 파일 저장
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
    let fileURL = documentsDirectory.appendingPathComponent("numbers.plist")
    
    try data.write(to: fileURL)
    print("plist 파일이 성공적으로 저장되었습니다: \(fileURL)")
} catch {
    print("Error: plist 저장 중 오류 발생 - \(error)")
}

사용자 정의 plist 파일 대안

iOS 앱 개발에서 사용자 설정이나 앱 상태를 저장하는 데 plist 파일을 직접 사용하는 대신 UserDefaults를 사용하는 경우가 많습니다. UserDefaults는 내부적으로 plist 형식을 사용하지만, 더 간단한 API를 제공합니다:

// 설정 저장
UserDefaults.standard.set(true, forKey: "darkModeEnabled")
UserDefaults.standard.set(14, forKey: "fontSize")
UserDefaults.standard.set(["Swift", "plist", "iOS"], forKey: "recentSearches")

// 설정 로드
let darkModeEnabled = UserDefaults.standard.bool(forKey: "darkModeEnabled")
let fontSize = UserDefaults.standard.integer(forKey: "fontSize")
let recentSearches = UserDefaults.standard.stringArray(forKey: "recentSearches") ?? []

결론

plist 파일은 iOS 애플리케이션에서 구조화된 데이터를 저장하고 로드하는 데 매우 유용한 형식입니다. Swift의 PropertyListDecoderPropertyListEncoder를 사용하면 plist 데이터를 간편하게 Swift 객체로 변환하고, 반대로 Swift 객체를 plist 형식으로 인코딩할 수 있습니다.

이 포스팅에서는 기본적인 정수 배열을 포함하는 plist 파일을 읽는 방법부터 복잡한 중첩 구조를 다루는 방법, 그리고 plist 파일을 생성하는 방법까지 다양한 예제를 통해 살펴보았습니다.

plist는 앱 설정, 구성 파일, 정적 데이터 등을 관리하는 데 적합하며, Swift의 Codable 프로토콜과 함께 사용하면 더욱 강력한 데이터 직렬화 및 역직렬화 기능을 구현할 수 있습니다.

더 자세한 내용은 Apple의 Property List Programming Guide를 참조하세요.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다