로컬 데이터 백업/복구 (with Zip)
Youngminah opened this issue · 1 comments
Youngminah commented
백업/ 복구를 위한 사전지식 - Sandbox
- 앱 하나당
샌드박스 시스템
은 앱끼리 서로 공유하지 않는다. - 각각의 앱은 샌드박스 시스템 안에서
도큐먼트
폴더를 필수로 갖는다. Document
: user data 저장Library
: non-user data 저장Realm
은 기본적으로 도큐먼트 경로를 사용하지만,- 보안을 위해서
Library/Application Support
폴더 경로를 사용하기도 한다.
사용자 데이터를 앱 안에 (로컬) 저장하는 경우
- 다른사람들과 데이터를 공유하는 네트워크 통신이 없는 개인앱의 경우 적합
- 개인앱의 경우 사진이 포함되어있더라도 별도로 파이어베이스 Storage를 이용하는 것은 비추천이라고 함.
- 하지만, 외부 저장소에 백업/복구 시스템이 필요하다.
외부저장소 종류
-
iCloud
- 장점 : iPhone 전용 앱일 경우 적합
- 단점 : 유료로 이용해야하는 용량문제가 있긴함
-
Third Party Cloud Services (Google Drive, Dropbox)
- 장점 : 아이폰 안드로이드 대응 가능
- 단점 : 사용자가 계정을 있어야 이용가능하고 여전히 용량에 재한이 있다.
-
파일앱 ( iOS11 이상부터 등장)
- 장점 : 공유를 할 수 있다 ( zip으로 압축하여 이메일, 카톡 등등 데이터 전송하여 외부 저장 가능)
- 단점 : 사용자가 직접 공유를 해야지만 외부에 저장됨
Zip으로 Realm파일을 백업할 경우 문제점 및 해결
문제점
- 스키마가 동일하지 않을 경우, 테이블 충돌 때문에 오류가 남 (생각해보면 당연한것..!)
- 즉, 이러한 경우 유저 백업을 위해서 데이터베이스 스키마 구조를 이후에 다시 변경할 수 없게됨
해결
- Realm 자체를 백업하기 보다는
Json 데이터 형식으로 변환
❗️ 하여 백업을 해야한다. - 스키마 문제 말고도 마이그레이션 등 많은 이슈에 유연한 대처가 가능하다.
Youngminah commented
샌드박스안의 도큐먼트에 있는 파일 String으로 가지고 오기
func documentDirectoryPath() -> String? {
let documentDirectory = FileManager.SearchPathDirectory.documentDirectory
let userDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let path = NSSearchPathForDirectoriesInDomains(documentDirectory, userDomainMask, true)
if let directoryPath = path.first {
return directoryPath
} else {
return nil
}
}
백업 버튼 누르면 ZIP파일로 백업하기
@IBAction func backupButtonTap(_ sender: UIButton) {
//4. 백업할 파일에 대한 URL 배열
var urlPaths = [URL]()
//1. 도큐먼트 폴더 위치
if let path = documentDirectoryPath() {
//2. 백업하고자 하는 URL 확인
//이미지 같은 경우 백업 편의성을 위해 폴더를 생성하고, 폴더 내에 이미지를 저장하는 것이 효율적
let realm = (path as NSString).appendingPathComponent("default.realm")
//2. 백업하고자 하는 파일 존재 여부 확인
if FileManager.default.fileExists(atPath: realm) {
// 5. URL 배열에 백업 파일 URL 추가
urlPaths.append(URL(string: realm)!)
} else {
print("백업할 파일이 없습니다.")
}
}
//3. 4번 배열에 대해 압축 파일 만들기
do {
let zipFilePath = try Zip.quickZipFiles(urlPaths, fileName: "test_archive") // Zip
print("압축 경로: \(zipFilePath)")
presentActivityViewController()
}
catch {
print("Something went wrong")
}
}
공유할 UIActivityViewController 띄우기 (유저 스스로 공유해야함)
func presentActivityViewController() {
//압축 파일 경로 가지고오기
let fileName = (documentDirectoryPath()! as NSString).appendingPathComponent("test_archive.zip")
let fileURL = URL(fileURLWithPath: fileName)
let vc = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
self.present(vc, animated: true, completion: nil)
}
백업 버튼 누를시 백업하기
UIDocumentPickerViewController 띄우고 delegate 연결
@IBAction func restoreButtonTap(_ sender: UIButton) {
//복구 1. 파일 앱 열기
let vc = UIDocumentPickerViewController(documentTypes: [kUTTypeArchive as String], in: .import)
vc.delegate = self
vc.allowsMultipleSelection = false //여러가지 선택지
self.present(vc, animated: true, completion: nil)
}
- 파일앱을 띄울 수 있다.
extension SettingViewController: UIDocumentPickerDelegate {
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
print(#function)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print(#function)
//복구 - 2. 선택한 파일에 대한 경로 가져와야 함
guard let selectedFileURL = urls.first else { return }
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let sandboxFileURL = directory.appendingPathComponent(selectedFileURL.lastPathComponent)
//복구 - 3. 로컬에 있는지 없는지 판단하기
if FileManager.default.fileExists(atPath: sandboxFileURL.path) {
//기존에 복구하고자 하는 zip파일을 로컬에 도큐먼트에 가지고 있을 경우, 도큐먼트에 위치한 zip을 압축 해제 하면 됨!
do {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentDirectory.appendingPathComponent("test_archive.zip")
try Zip.unzipFile(fileURL,
destination: documentDirectory,
overwrite: true,
password: nil,
progress: { progress in
print("progress: \(progress)")
//복구가 완료되었습니다 메시지, 얼럿
},
fileOutputHandler: { unzippedFile in
print("unzippedFile: \(unzippedFile)")
})
} catch {
print("압축해제 에러!")
}
} else { //복구하고자 하는 zip이 없는 경우
//파일 앱의 zip -> 도큐먼트 폴더에 복사
do {
try FileManager.default.copyItem(at: selectedFileURL, to: sandboxFileURL)
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentDirectory.appendingPathComponent("test_archive.zip")
try Zip.unzipFile(fileURL,
destination: documentDirectory,
overwrite: true,
password: nil,
progress: { progress in
print("progress: \(progress)")
//복구가 완료되었습니다 메시지, 얼럿
},
fileOutputHandler: { unzippedFile in
print("unzippedFile: \(unzippedFile)")
})
} catch {
print("압축해제 에러!")
}
}
}
}
- 중복된 코드는 개선이 필요해 보임.
- 선택한 zip파일 경로가 로컬에 있는지 없는지 우선 판단하여 있으면 로컬에 있는 것으로 복구
- 아닐경우 선택한 zip파일을 로컬에 복사하여 복구 한다.