ios의 파일시스템은 조금 특별합니다 높은 보안성을 자랑합니다.
기본적으로 앱마다 별도의 파일을 생성하고 Sandbox안에서만 접근이 가능합니다. 만약 특정 리소스에 접근하려고 하는데 내가 속한 sandbox 외부의 공간이라면 접근할 수 없습니다.
그렇기에 icloud로 동기화를 하지 않는 이상 외부 앱의 데이터를 열람할 수 없습니다. 왜냐면 다른 sandbox에 있기 때문이죠


우리가 오늘 주목해야할 부분은 DataContainer입니다.
만약 서로다른 sandbox로의 접근이 필요하다면 App Groups을 설정해주면 됩니다.
한가지 의문점이 생겼습니다.
iOS에서도 사용할 수 있는 다양한 File Viewer 앱이 있는데 이렇게 엄격한 Sandbox 정책 속에서 어떻게 앱의 외부에서 File을 열람하고 다운을 받는 것일까요?
File 탐색 관련 앱이나 뷰어 앱에서 크게 2가지로 File을 가져오고 열람할 수 있는 기능을 제공해 준다는 사실을 깨달았습니다.
이 방식은 뷰어 앱에서 언제나 외부 파일을 열람할 수 있게 하는 방식입니다.
a. ‘파일 앱’ 을 선택하면 UIDocumentPickerController를 통해 filepicker가 실행됩니다.
- 사용자는 이로부터 가져오고자하는 파일을 탐색하고 선택할 수 있습니다.

b. 파일을 선택하면 즉시 선택된 파일이 앱에서 열리게 됩니다.
c. FilePicker로부터 가져온 파일 데이터를 활용해 앱 내부에서 해당 파일에 접근하고 열람이 가능해집니다.
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: contentTypes, asCopy: true)
picker를 호출하고 정보를 가져오기 위해 필수적으로 작성해야하는 코드인데요 만약 ascopy를 true로 설정하게 되면 앱 sandbox내 tmp(일시적으로 저장하는 영역) 영역에 받아와지게 됩니다. 영구적으로 앱에서 파일을 열람할 수 있게 하기위해 해당 파일을 복사해 앱내 sandbox의 Library 혹은 Documents영역에 복사해올 수 있습니다.

사진과 같은 뷰어 앱은 외부 앱의 최신 데이터를 실시간으로 확인하거나 열람할 수 있습니다.
이 기능은 어떻게 동작할 수 있을까요?
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: contentTypes, asCopy: false)
앞서 filepicker를 호출할 때 써줘야하는 코드라고 소개하였습니다.
하지만 한가지가 변경되어있습니다.
바로 asCopy 매개변수의 값입니다.
false로 되어있다면 다음과 같은 기능이 가능해집니다.
다만 역시 깐깐한 Apple의 보안규칙으로 인해 받아오는 파일의 데이터 형태는 Security-Scoped URL로 받아와지게 됩니다.
이 url은 정해진 범위 내에서만 유효하기 때문에 매번 파일을 참조하기 위해서는 BookMarkData 형태로 저장하여 접근할 수 있습니다.
저는 두번째 기능을 직접 구현해보기 위해 샘플프로젝트를 생성해보았는데요 간단한 테스트를 위해 사용했던 코드를 첨부하겠습니다.
import UIKit
import UniformTypeIdentifiers
//secuity scoped url을 가져오며 위치 참조하는 경우
class FilePickerClass: NSObject, UIDocumentPickerDelegate, ObservableObject {
//어노테이션이 swiftui뷰에 자동으로 업데이트를 알리게됨
@Published var result = "디렉토리 리스트를 가져옵니다"
func showFilePicker() {
//asCopy => 선택한 파일을 복사하여 뷰어앱내에서 관리한다 만약 false라면 원본 파일을 그대로 참조한다
let contentTypes: [UTType] = [.folder, .item]
let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: contentTypes, asCopy: false)
documentPicker.delegate = self
//다중선택 가능하도록 바꾸기 default는 false
documentPicker.allowsMultipleSelection = true
// 현재 활성화된 ViewController에서 Document Picker를 표시
guard let topViewController = UIApplication.shared.windows.first?.rootViewController else {
print("Root ViewController를 찾을 수 없습니다.")
return
}
topViewController.present(documentPicker, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let selectedFileUrl = urls.first else {
print("선택된 파일이 없습니다.")
return
}
do {
guard selectedFileUrl.startAccessingSecurityScopedResource() else {
print("보안 스코프를 활성화할 수 없습니다")
return
}
defer {
selectedFileUrl.stopAccessingSecurityScopedResource()
}
print("선택된 파일: \(selectedFileUrl)")
//나중에 url에 접근하기 위해서
let bookmarkData = try selectedFileUrl.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
try bookmarkData.write(to: getMyUrlForBookMark())
} catch let error {
print("북마크 생성 도중 에러 발생 \(error)")
}
handleSelectedFile()
}
//북마크 url값을 다른 디렉토리에 저장한다
func getMyUrlForBookMark() -> URL {
let fileManager = FileManager.default
let dir = fileManager.urls(for: .libraryDirectory, in: .userDomainMask).first
let bookMarkUrl = dir?.appendingPathComponent("MyBookMark").appendingPathExtension("bookmark")
return bookMarkUrl!
}
private func handleSelectedFile() {
do {
let fileData = try Data(contentsOf: getMyUrlForBookMark())
var isStale = false
//북마크 데이터를 url 복원
let url = try URL(resolvingBookmarkData: fileData, bookmarkDataIsStale: &isStale)
guard !isStale else {
return
}
//보안 권한 활성화
guard url.startAccessingSecurityScopedResource() else {
print("handleSelectedFile 보안범위 리소스 접근 실패")
return
}
defer {
url.stopAccessingSecurityScopedResource()
}
} catch {
print("파일 데이터를 가져올 수 없습니다: \(error.localizedDescription)")
}
}
//접근하고자 하는 앱의 디렉토리 히스토리를 출력함
func getDirectoryList() {
do {
let fileData = try Data(contentsOf: getMyUrlForBookMark())
//북마크가 오래되었는지 확인하는 변수
//만약 이 값이 true라면 북마크데이터 새로 생성하거나 다른방법으로 파일경로 복원해야함
var isStale = false
//북마크 데이터를 url 복원
let url = try URL(resolvingBookmarkData: fileData, bookmarkDataIsStale: &isStale)
guard !isStale else {
return
}
guard url.startAccessingSecurityScopedResource() else {
return
}
defer {
url.stopAccessingSecurityScopedResource()
}
let fileManager = FileManager.default
let directoryHistory = try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])
result = ""
for hi in directoryHistory {
result.append(hi.path)
result.append("\n")
result.append("\n")
}
} catch {
result = "파일 디렉토리 히스토리를 읽어올 수 없습니다"
print("파일 디렉토리 히스토리를 읽어올 수 없습니다")
}
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
print("사용자가 파일 선택을 취소했습니다.")
}
}
Apple의 보안 규칙과 개발 생태계를 배울 수 있는 좋은 기회였습니다!💻