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]
코드 분석
위 코드를 단계별로 분석해 보겠습니다:
- 데이터 소스 배열 초기화:
var dataSource: Array<Int> = Array()
정수 배열을 저장할 변수를 선언합니다. - plist 파일 경로 가져오기:
let lvPath = Bundle.main.url(forResource: "test", withExtension: "plist")!
앱 번들에서 “test.plist” 파일의 URL을 가져옵니다.Bundle.main
은 현재 실행 중인 앱의 메인 번들을 나타냅니다. - 파일 데이터 로드:
let lvArray = try! Data(contentsOf: lvPath)
URL에서 파일의 내용을Data
객체로 로드합니다. - PropertyListDecoder 생성:
let decoder = PropertyListDecoder()
plist 데이터를 디코딩하기 위한PropertyListDecoder
인스턴스를 생성합니다. - 데이터 디코딩:
dataSource = try! decoder.decode(Array<Int>.self, from: lvArray)
로드된 데이터를Array<Int>
타입으로 디코딩합니다. 여기서.self
는 타입 자체를 참조하는 데 사용됩니다. - 결과 출력:
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의 PropertyListDecoder
와 PropertyListEncoder
를 사용하면 plist 데이터를 간편하게 Swift 객체로 변환하고, 반대로 Swift 객체를 plist 형식으로 인코딩할 수 있습니다.
이 포스팅에서는 기본적인 정수 배열을 포함하는 plist 파일을 읽는 방법부터 복잡한 중첩 구조를 다루는 방법, 그리고 plist 파일을 생성하는 방법까지 다양한 예제를 통해 살펴보았습니다.
plist는 앱 설정, 구성 파일, 정적 데이터 등을 관리하는 데 적합하며, Swift의 Codable
프로토콜과 함께 사용하면 더욱 강력한 데이터 직렬화 및 역직렬화 기능을 구현할 수 있습니다.
더 자세한 내용은 Apple의 Property List Programming Guide를 참조하세요.
답글 남기기