본문 바로가기

학습 노트/iOS (2021)

181 ~ 182. User Defaults and Property List

User Defaults (Default Database)

특징

  • Key - Value의 형태
  • 메모리가 아닌 'plist'파일로 저장
  • 직접 수정할 수 없고 반드시 UserDefaultClass를 사용해야 함
  • 앱 실행시 병목 해소를 위해 Cache에 Load된 후 사용됨
  • 위의 이유로 최대 4GB 까지 사용할 수 있는 것으로 안내되지만 KB급 이상에는 부적합
즉시 사용 가능 해당 형태로 변환 필요
Bool
String
Data
Array
Dictionary
Data
URL
NSCoding

saveData()

	let key = "sample"
   @IBAction func saveData(_ sender: Any) {
//	   UserDefaults.standard.set("Hello", forKey: key)
	   UserDefaults.standard.set(12.34, forKey: key)
   }

UserDefaults의 singletone 인스턴스를 사용한다.
이때 Value에는 기존에 저장된 것과 다른 형태의 데이터를 저장해도 문제 없다.

set(:forKeys:)

 

Apple Developer Documentation

 

developer.apple.com

Key가 존재하면 새 값으로 대체하고, 존재하지 않으면 새로 생성한다.

이 때 전달하는 Key는 String 형식으로 오탈자에 주의해야한다.
위와 같이 직접 작성하기 보다는 변수로 지정해 전달하는 쪽이 이러한 문제에서 조금 더 자유롭다.

iOS 10 이전에는 UserDefault를 수정한 이후 synchronize 메서드를 사용해 동기화 해야했지만,
이후에는 사용하지 않는 쪽을 권장하고 있다.

loadData()

   @IBAction func loadData(_ sender: Any) {
	   valueLabel.text = UserDefaults.standard.string(forKey: key) ?? "Not Exist"
	   keyLabel.text = thresholdKey
   }

데이터를 읽을 때도 Singletone 인스턴스를 사용한다.

string(forKey:)

 

Apple Developer Documentation

 

developer.apple.com

string(forKey:)를 제외하고도 데이터의 형식에 따라 사용할 수 있는 인스턴스가 존재한다.
파라미터로 전달한 Key가 존재하면 값을 반환하고 이외엔 nil을 전달한다.
반환되는 값은 값의 형식에 따라 optional과 nonoptional이 존재할 수 있다.

앞서 UserDefault를 수정할때 최초에 저장되어있던 Value의 형식에 관계 없이 저장할 수 있다고 언급했다.
따라서 값의 형식이 달라졌을 경우 메서드는 오류로 판단하지 않고 최대한 값을 반환하기 때문에,
읽으려는 값의 형식을 잘 파악하는 게 중요하다.
따라서 Key의 이름을 Value의 형식을 추측할 수 있는 이름으로 정하는 것이 도움이 된다.

UserDefault Debugging

UserDefault를 사용하는 중에 의도치 않은 문제가 생겼을 경우,
내용을 직접 확인하면 디버깅에 도움이 될 수 있다.
사용할 수 있는 메서드는 두개이다.

dictionaryRepresentation()

dictionaryWithValues(forKeys:)

 

dictionaryRepresentation()

 

Apple Developer Documentation

 

developer.apple.com

dictionaryWithValues(forKeys:)

 

Apple Developer Documentation

 

developer.apple.com

전자는 UserDefault를 전부 반환하고,
후자는 전달된 Key에 해당하는 결과만 반환한다.
결과는 다음과 같다.

dictionaryRepresentation()
dictionaryWithValues(forKeys:)

UserDefault 삭제

사용할 수 있는 방법은 두가지다.

set(nil, forKey: "Key")

removeObject()

set(_:forKey:)

 

Apple Developer Documentation

 

developer.apple.com

 

removeObject()

 

Apple Developer Documentation

 

developer.apple.com

전자는 해당하는 Key의 값을 nil로 변경하는 방식이다.
후자는 메서드를 사용하지면 결과는 같다.

UserDefault 기본값 설정

앱을 최초로 실행하는 경우 여러 설정값들을 위한 기본값이 필요하다.

AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
		// Override point for customization after application launch.
		
		if !UserDefaults.standard.bool(forKey: initialLaunchKey) {
			let defaultSettings = [thresholdKey: 123] as [String: Any]
			UserDefaults.standard.register(defaults: defaultSettings)
			UserDefaults.standard.set(true, forKey: initialLaunchKey)
			
			print("initial launch")
		}
		return true
	}

AppDelegate의 application(launchOptions:) 메서드로 최초 실행인지를 판단하고,
기본값을 설정하도록 구현할 수 있다.
앱 실행시 도움말이나 기타 안내를 출력하는 방식으로 이용할 수 있다.

bool(forkey:)

 

Apple Developer Documentation

 

developer.apple.com

해당 메서드는 전달된 Key의 Value가 존재하지 않는 경우 false를 반환한다.
따라서 값이 존재하지 않는 경우 기본값을 UserDefault에 저장한다.

register(defaults:)

 

Apple Developer Documentation

 

developer.apple.com

해당 메서드를 사용해 다수의 Key-Value 쌍을 저장할 수 있다.

미리 생성해둔 기본값들을 전달한다.
이후 initialLaunchKey의 값을 true로 변경해 다시 실행되지 않도록 변경한다.

UserDefaultViewController.swift

   @IBAction func loadData(_ sender: Any) {
//	   valueLabel.text = UserDefaults.standard.string(forKey: key) ?? "Not Exist"
	   valueLabel.text = "\(UserDefaults.standard.integer(forKey: thresholdKey))"
	   keyLabel.text = thresholdKey
   }

load 버튼을 누르면 새로 생성된 thresholdKey를 출력하도록 코드를 수정한다.

최초 실행시 기본값을 설정하고 해당 문자열을 콘솔에 출력한다.

실제 앱에서도 Load 버튼을 누르면 오류 없이 새롭게 생성된 thresholdKey를 출력한다.

UserDefaultNotification

UserDefault가 업데이트 되는 경우 특별한 Notification이 전송된다.

didChangeNotification

 

Apple Developer Documentation

 

developer.apple.com

해당 Notification은 User Default가 업데이트 됐다는 것을 알려줄 뿐,
특정 Key의 업데이트를 감지할 수는 없다.
따라서 특정 Key의 업데이트를 감지하려면 기존의 Value를 저장했다가 Notification이 전달된 시점에 비교하는 식으로 구현한다.

	var token: NSObjectProtocol?
	
	deinit {
		if let token = token {
			NotificationCenter.default.removeObserver(token)
		}
	}
   
   
   override func viewDidLoad() {
	  super.viewDidLoad()
	  
	   token = NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: OperationQueue.main, using: { [weak self] (noti) in
		   self?.updateDateLabel()
	   })

Notification을 감지할 Observer의 생성은 위와 같다.
화면이 표시되는 시점에 observer를 생성하고, 이를 해제하기 위한 소멸자를 추가한다.
Notification이 감지되면 updateDateLabel 메서드를 호출한다.

   func updateDateLabel() {
	  let formatter = DateFormatter()
	  formatter.dateStyle = .none
	  formatter.timeStyle = .medium
	  
	  lastUpdatedLabel.text = formatter.string(from: Date())
   }

해당 메서드는 위와 같고,

결과는 위와 같다.
Save 버튼을 누르면 UserDefault가 업데이트 되고,
Observer가 이를 감지해 Last Update 아래에 시점을 출력한다.

 

Property List

  • 설정값을 저장하는데 주로 사용된다.
  • 앱 안의 info.plist 파일로 존재한다.
  • Key-Value 형태로 저장된다.
  • Read Only 파일이다.
  • XML 포맷으로 저장된다.
  • 하나의 Root 객체 이아에 데이터를 추가한다.

기본적으로 Key-Value 타입을 사용하지만 Root 객체의 타입에 따라 Array 방식을 사용할 수 도 있다.

XML 포맷으로 저장되기 때문에 XML 문법을 사용해 직접 편집하는 것도 가능하다.

loadFromBundle()

   @IBAction func loadFromBundle(_ sender: Any) {
	   guard let url = Bundle.main.url(forResource: "data", withExtension: "plist") else {
		   fatalError("Property List not Found")
	   }
       
	   if #available(iOS 11.0, *) {
		   if let dict = try? NSDictionary(contentsOf: url, error: ()) {
			   print(dict)
		   }
	   } else {
		   if let dict = try? NSDictionary(contentsOf: url) {
			   print(dict)
		   }
	   }
   }

 

Bundle에 저장된 PropertyList의 Url을 생성한다.

Url을 사용해 PropertyList를 불러오는 방법은 두 가지이고,
PropertyList의 형식에 따라 접근 방식이 달라진다.

NSDictionary(contentsOf:error:)

 

Apple Developer Documentation

 

developer.apple.com

NSDictionary(contentsOf:)

 

Apple Developer Documentation

 

developer.apple.com

전자는 iOS 11 이상에서 사용할 수 있고,
후자는 iOS 10 이하에서만 사용할 수 있다.
만약 iOS 10 이하의 버전을 지원하는 앱이라면 원문의 코드처럼 버전에 따라 분기시켜야 한다.

Dictionary가 아닌 Array인 경우 NSArray로 이름 붙여진 동일한 형태의 메서드를 사용한다.

Dictionary
Array

loadFromDocuments()

	let fileUrl: URL = {
		let documentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
		return documentsDirectory.appendingPathComponent("data").appendingPathExtension("plist")
	}()
   
   @IBAction func loadFromDocuments(_ sender: Any) {
	   do {
		   let data = try Data(contentsOf: fileUrl)
		   let decoder = PropertyListDecoder()
		   let dict = try decoder.decode(Development.self, from: data)
		   print(dict)
	   } catch {
		   print(error)
	   }
   }

PropertyList를 읽을 때는 기타 파일들과 마찬가지로 FileManager로 Url을 생성해 사용한다.

PropertyListDecoder()

 

Apple Developer Documentation

 

developer.apple.com

PropertyList를 읽기 위한 Decoder 인스턴스를 생성하고,

decode(_:from:)

 

Apple Developer Documentation

 

developer.apple.com

해당 메서드를 사용해 생성한 decoder를 사용해 파일을 읽어 온다.

결과는 위와 같다.

saveToDocuments()

   @IBAction func saveToDocuments(_ sender: Any) {
	   do {
		   let dict = ["language": "Swift", "os": "iOS"]
		   let encoder = PropertyListEncoder()
		   let data = try encoder.encode(dict)
		   try data.write(to: fileUrl)
		   print("success")
	   } catch {
		   print(error)
	   }
   }
}

Property List를 새로 저장하는 것도 비슷한 매커니즘으로 동작한다.

PropertyListEncoder()

 

Apple Developer Documentation

 

developer.apple.com

Decoder의 반대 개념인 Encoder를 사용한다.

decoder와 동일한 방식으로 사용후 wrute(to:) 메서드를 사용해 저장한다.

CustomPropertyList

기본적인 PropertyList가 아닌 별도의 형식의 PropertyList를 만들수 있다.

struct Development: Codable {
	let language: String
	let os: String
}

Codable 프로토콜을 채용한 구조체를 정의하고,

   @IBAction func saveToDocuments(_ sender: Any) {
	   do {
		   let dev = Development(language: "Swift", os: "iOS")
		   let encoder = PropertyListEncoder()
		   let data = try encoder.encode(dev)
		   try data.write(to: fileUrl)
		   print("success")
	   } catch {
		   print(error)
	   }
   }

해당 구조체를 encoder와 decoder에 전달하기만 하면 된다.

'학습 노트 > iOS (2021)' 카테고리의 다른 글

185. CoreData  (0) 2022.02.05
183 ~ 184. NSCoding and Codable  (0) 2022.01.27
180. File Manager #3  (0) 2022.01.19
179. File Manager #2  (0) 2022.01.19
178. File Manager #1  (0) 2022.01.12