Youngminah/TIL

로컬 데이터 백업/복구 (with Zip)

Youngminah opened this issue · 1 comments

백업/ 복구를 위한 사전지식 - Sandbox

image

  • 앱 하나당 샌드박스 시스템은 앱끼리 서로 공유하지 않는다.
  • 각각의 앱은 샌드박스 시스템 안에서 도큐먼트 폴더를 필수로 갖는다.
  • 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 데이터 형식으로 변환❗️하여 백업을 해야한다.
  • 스키마 문제 말고도 마이그레이션 등 많은 이슈에 유연한 대처가 가능하다.

샌드박스안의 도큐먼트에 있는 파일 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파일을 로컬에 복사하여 복구 한다.