이 글에서는 애플리케이션에 사용자 정의 객체(커스텀 오브젝트)를 저장하는 방법을 배워보겠습니다. 그에 앞서, 사용자 정의 객체가 정확히 무엇인지 먼저 살펴보겠습니다.
사용자 정의 객체란?
사용자 정의 객체란 Int, Double, String처럼 기본으로 제공되는 네이티브 데이터 타입이 아닌, 개발자가 직접 만든 클래스나 구조체 또는 기타 데이터를 의미합니다. 이러한 객체는 기본 타입과 달리 NSUserDefaults에 바로 저장할 수 없기 때문에, 별도의 직렬화 과정이 필요합니다.
NSUserDefaults에 데이터를 저장하는 과정은 크게 세 단계로 이루어집니다.
- 객체 생성 – NSCoding 프로토콜을 채택한 커스텀 클래스 작성
- 인코딩 – NSKeyedArchiver로 객체를 Data로 변환 후 저장
- 디코딩 – NSKeyedUnarchiver로 저장된 Data를 다시 객체로 복원
1단계: 사용자 정의 객체 생성하기
먼저 name과 age 변수를 가진 Person이라는 커스텀 클래스를 만들어 보겠습니다. NSUserDefaults에 저장하려면 반드시 NSObject를 상속하고 NSCoding 프로토콜을 구현해야 합니다.
class Person: NSObject, NSCoding {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
required convenience init(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey: "name") as! String
let age = aDecoder.decodeInteger(forKey: "age")
self.init(name: name, age: age)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(age, forKey: "age")
aCoder.encode(name, forKey: "name")
}
}여기서 encode(with:) 메서드는 객체를 저장할 때 각 속성을 어떤 키로 인코딩할지 정의하고, init(coder:)는 저장된 데이터를 읽어올 때 키를 기준으로 값을 복원하는 역할을 합니다.
2단계: 객체 인코딩하기
이제 Person 객체 배열을 만들고 임의의 데이터로 초기화해 보겠습니다. 배열이 준비되면 NSKeyedArchiver를 사용해 객체를 아카이브(직렬화)한 뒤, UserDefaults에 저장합니다.
let people = [
Person.init(name: "P1", age: 1),
Person.init(name: "P2", age: 2),
Person.init(name: "P3", age: 3),
]
let encoded = NSKeyedArchiver.archivedData(withRootObject: people)
UserDefaults.standard.set(encoded, forKey: "encodedData")이렇게 하면 아카이브된 객체가 UserDefaults에 성공적으로 저장됩니다. 이제 저장된 객체를 언아카이브(역직렬화)하여 출력해 보겠습니다.
3단계: 객체 디코딩하기
NSKeyedArchiver와 마찬가지로, 아카이브를 해제하는 용도의 NSKeyedUnarchiver 클래스가 제공됩니다. 이를 사용해 저장된 객체를 다시 불러옵니다.
let decoded = UserDefaults.standard.object(forKey: "encodedData") as! Data let decodedPeople = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! [Person]
이제 decodedPeople 객체에는 두 번째 단계에서 생성했던 객체들이 담겨 있습니다. 이 객체들을 활용해 원하는 작업을 자유롭게 수행할 수 있습니다.
전체 코드 살펴보기
지금까지의 과정을 모두 포함한 ViewController 파일의 전체 코드는 다음과 같습니다.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let people = [
Person.init(name: "P1", age: 1),
Person.init(name: "P2", age: 2),
Person.init(name: "P3", age: 3),
]
let encoded = NSKeyedArchiver.archivedData(withRootObject: people)
UserDefaults.standard.set(encoded, forKey: "encodedData")
print("data decoded, encoding now")
let decoded = UserDefaults.standard.object(forKey: "encodedData") as! Data
let decodedPeople = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! [Person]
print(decodedPeople)
}
}결과 확인하기
마지막 print 문에 중단점(breakpoint)을 설정한 뒤 코드를 실행하고, 디버거에서 아래 명령어를 입력해 보세요.
po decodedPeople.first?.name
그러면 다음과 같은 결과가 출력됩니다.
Optional<String> - some : "P1"
배열의 첫 번째 요소인 P1의 이름이 정상적으로 복원된 것을 확인할 수 있습니다. 최종 실행 화면은 아래와 같습니다.

참고: 최신 Swift 버전에서의 주의 사항
위 예제에서 사용된 archivedData(withRootObject:)와 unarchiveObject(with:) 메서드는 iOS 12 이후부터 deprecated(사용 중단 권고) 상태입니다. 최신 프로젝트에서는 archivedData(withRootObject:requiringSecureCoding:error:)와 unarchivedObject(ofClass:from:) 같은 Secure Coding 기반 API를 사용하는 것이 좋습니다. 또한 저장하려는 데이터가 많거나 구조가 복잡하다면 UserDefaults 대신 Core Data나 파일 시스템(Codable 활용)을 고려하는 편이 더 안전하고 효율적입니다.