-
앱에 데이터 백업, 복구 기능 추가하기(Swift)iOS/Swift 2022. 1. 22. 21:00
안녕하세요!! 이번엔 Swift에서 앱에 데이터 백업, 복구 기능을 어떻게 추가하는지 정리했습니다.
이번 글도 노션에서 옮겨와 말이 짧습니다!! 양해해주세용😽
백업기능이 왜 필요해??
사용자가 앱을 이용할 때, 사용자의 데이터는 iOS의 Container System에 의해 그 앱의 샌드박스 내에서만 이용 가능하다.
즉, 사용자가 다른 사람들과 데이터를 공유할 수 없고, 만약 어떤 이유로든 앱을 삭제해야 할 경우에는 데이터가 같이 사라져 버리는 문제가 있다.
따라서, 사용자가 원할 때 데이터를 외부로 백업할 수 있는 기능이 필요하다.
iCloud를 이용한 백업, Google Drive를 이용한 백업, 파일 앱을 이용한 백업 등 백업 기능을 구현하기 위한 방법이 많지만, 파일 앱을 이용한 백업으로 백업 기능을 추가해 볼 것이다.
구현
사용한 라이브러리
Zip: 백업 파일 압축, 압축해제를 위한 라이브러리
Realm: 로컬 데이터베이스로 사용한 라이브러리
유저 인터페이스

오른쪽 상단 바 버튼을 누르면, ActionSheet가 올라오고 ActionSheet의 버튼으로 백업버튼, 복구버튼이 있다.
백업버튼을 누르면 백업 기능이 실행되고, 복구버튼을 누르면 복구 기능이 실행된다. 취소를 누를 경우, ActionSheet가 내려가고 아무런 일도 벌어지지 않는다.
코드 작성
먼저 바 버튼에 ActionSheet를 연결해주는 코드를 추가했다.
@IBAction func additionalButtonClicked(_ sender: UIBarButtonItem) { let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let backup = UIAlertAction(title: "백업하기", style: .default) { _ in print("백업 실행") } let restore = UIAlertAction(title: "복구하기", style: .default) { _ in print("복구 실행") } let cancel = UIAlertAction(title: "취소", style: .cancel, handler: nil) alert.addAction(backup) alert.addAction(restore) alert.addAction(cancel) self.present(alert, animated: true, completion: nil) }백업 기능
이제 본격적으로 백업 기능과 관련된 코드를 작성해야 한다.
백업 기능을 추가하기 전 고려해야 할 사항과 백업 진행 도중 고려해야 될 사항들이 있다.
- 사용자의 아이폰 저장 공간이 충분한가?
- 저장 공간이 부족할 경우, 사용자에게 백업이 불가능 하다는 것을 안내한다.
- 어떤 데이터를 백업할 것인지 확인한다. (데이터가 존재하는지)
- 데이터가 없는 경우라면, 백업할 데이터가 없다고 안내한다.
- 백업 진행 도중, 사용자의 행동을 고려해야 한다. (사용자가 백그라운드로 간다던가, 새로운 데이터를 추가한다던가 하는 행동)
- 백그라운드에서 백업 진행, Progress + UI 인터렉션 제한 등으로 처리 가능하다.
- 백업이 완료됐을 경우, UIActivityViewController를 이용하여 공유, 파일 저장 등의 기능을 수행할 수 있도록 한다.
큰 용량의 데이터를 넣지는 않을 것이기 때문에 아이폰 저장 공간을 확인하는 과정은 생략했다.
먼저, 백업할 데이터를 찾기 위해서 Document 디렉토리에 대한 경로가 필요하다.
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 // 경로를 찾을 수 없는 상태이므로, nil 반환 } }위 함수를 실행시키면, /Users/y.b/Library/Developer/CoreSimulator/Devices/8E225BE2-2A2C-4336-B718-04D7D8F1DBC8/data/Containers/Data/Application/74679426-7079-467E-B574-006D508545F3/Documents 위와 같이 경로를 획득할 수 있다.
이제 데이터가 위치한 경로를 알게 됐으니, 백업하고자 하는 파일을 확인해야 한다.
나는 Realm으로 데이터를 관리해서 document에서 realm 파일을 백업하면 된다.
func backupData() { // 백업할 파일에 대한 URL 배열 var urlPaths = [URL]() // 1. 도큐먼트 폴더 위치 if let path = documentDirectoryPath() { // 백업하고자 하는 파일 URL 확인 // 이미지 같은 경우, 백업 편의성을 위해 폴더를 생성하고, 폴데 안에 이미지를 저장하는 것이 효율적 let realm = (path as NSString).appendingPathComponent("default.realm") // 기존 path에 백업할 파일 이름을 추가해준 새로운 경로를 만들었다. // 백업하고자 하는 파일 존재 여부 확인 if FileManager.default.fileExists(atPath: realm) { // URL 배열에 백업 파일 URL 추가 urlPaths.append(URL(string: realm)!) } else { print("DEBUG: 백업할 파일이 없습니다.") } } // 배열에 대해 압축파일 만들기 do { let zipFilePath = try Zip.quickZipFiles(urlPaths, fileName: "archive") // Zip print("압축 경로: \\(zipFilePath)") print("여기서 ActivityController를 불러오면 된다.") } catch { print("DEBUG: 압축파일 만들기 실패") } }그리고 시뮬레이터를 열고 백업하기 버튼을 누르면,
압축 경로: file:///Users/y.b/Library/Developer/CoreSimulator/Devices/8E225BE2-2A2C-4336-B718-04D7D8F1DBC8/data/Containers/Data/Application/B64E4A66-A576-4436-8AD5-FA3A12313C23/Documents/archive.zip
라고 디버그 콘솔 영역에 나오는 것을 확인할 수 있다.

실제로 경로를 찾아가보면, archive.zip 파일이 생성되어 있는 것을 확인할 수 있다.
복구 기능
백업 기능으로 데이터를 저장했으니, 이 데이터를 불러와서 복구하는 기능 또한 추가시켜줘야 한다.
데이터를 복구할 때에도 몇가지 고려해야 할 사항이 있다.
- 저장 공간이 충분한가?
- 백업할 때에도 아이폰의 저장 공간을 신경 써줘야 하고, 복구할 때에도 마찬가지다.
- 파일 앱 열어주기
- 파일 앱을 열어줬을 때, 어떻게 백업파일을 구분할지를 고려해봐야 한다.
- ex) 고유한 형태의 문자열로 백업 파일을 구분한다, zip으로 백업을 했으므로 zip이 아닌 파일은 걸러낸다.
- zip파일로 저장했으므로 압축해제를 해주는 과정이 필요하다.
- 압축 해제를 할 때에도 폴더, 파일 이름을 확인해야 한다.
- 정상적인 파일인가 확인이 필요하다.
- 백업 당시 데이터와 지금 앱에서 사용중인 데이터를 어떻게 합칠 것인가?
- 만약 백업을 해놓고 데이터를 더 추가 및 삭제 하고나서 복구를 진행할 때, 그 데이터들과 백업한 데이터를 어떻게 합칠 것인지에 대한 고민을 해보아야 한다.
- 하지만, 대다수의 앱에서는 그냥 백업 파일을 덮어쓰기 하는 형식으로 복구를 진행한다고 한다.
먼저, 파일 앱에서 복구에 필요한 데이터를 선택해야한다.
@IBAction func restoreButtonClicked(_ sender: UIButton) { // 복구 1. 파일앱 열기 + 확장자 // MobileCoreServices import let documentPicker = UIDocumentPickerViewController(documentTypes: [kUTTypeArchive as String], in: .import) documentPicker.delegate = self documentPicker.allowsMultipleSelection = false // 복구에 필요한 데이터는 하나만 필요하니까 다중 선택 속성을 false로 처리했다. self.present(documentPicker, animated: true, completion: nil) }그리고 파일 앱에서 데이터를 선택했을 때에 대한 이벤트를 처리하기 위해서
UIDocumentPickerDelegate 프로토콜을 채택해주어야 한다.
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { // 선택한 파일에 대한 경로 가져오기 guard let selectedFileURL = urls.first else { return } // 디렉토리 URL let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! // 파일 URL. 디렉토리 경로에 선택한 파일의 경로의 마지막을 붙여놓음. let sandboxFileURL = directory.appendingPathComponent(selectedFileURL.lastPathComponent) // 압축 해제 if FileManager.default.fileExists(atPath: sandboxFileURL.path) { do { let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let fileURL = documentDirectory.appendingPathComponent("archive.zip") try Zip.unzipFile(fileURL, destination: documentDirectory, overwrite: true, password: nil, progress: { progress in print(progress) }, fileOutputHandler: { unzippedFile in print("unzip: \\(unzippedFile)") }) } catch { print("DEBUG: 압축 해제 에러") } } else { // 데이터가 도큐먼트에 없는 경우. 파일 앱의 zip을 도큐먼트 폴더에 복사하고 압축 해제. do { let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let fileURL = documentDirectory.appendingPathComponent("archive.zip") try Zip.unzipFile(fileURL, destination: documentDirectory, overwrite: true, password: nil, progress: { progress in print(progress) }, fileOutputHandler: { unzippedFile in print("unzip: \\(unzippedFile)") }) } catch { print("DEBUG: 압축 해제 에러2") } } }채택한 뒤, 파일 앱에서 파일을 선택했을 때 실행되는 메소드인 didPickDocumentsAt에 코드를 작성해주어야 한다.

그럼 복구하기를 눌렀을 때, 파일 앱으로 진입할 수 있고, 백업해두었던 데이터를 확인할 수 있다.
파일을 선택하고 나면, 콘솔창에서
unzip: /Users/y.b/Library/Developer/CoreSimulator/Devices/8E225BE2-2A2C-4336-B718-04D7D8F1DBC8/data/Containers/Data/Application ... lt.realm
print문을 통해서 압축을 해제했다는 것을 확인할 수 있다.
'iOS > Swift' 카테고리의 다른 글
Swift PHPicker로 여러 이미지를 가져오기 (0) 2022.01.22 Google Maps SDK for iOS 정리 (0) 2022.01.22 Realm-cocoa 라이브러리 활용해보기(Swift) (0) 2022.01.22 TableViewCell 내부의 UIView에서 Gesture로 이벤트 처리하기 (0) 2022.01.22 Swift SocketIO를 활용한 실시간 채팅 앱 구현 (0) 2022.01.15 - 사용자의 아이폰 저장 공간이 충분한가?