Apple의 앱 Sandbox에 대하여(feat. FileManager)

Uno·2022년 11월 23일
0

Tip-Swift

목록 보기
20/26
post-custom-banner

서론

2010, Android 사용자들이 iPhone 을 사용하지 않는 이유 중 이런 이유를 들은 적이 있습니다.

안드로이드는 그냥 USB 꼽아서 바로 파일 관리하면되는데, iPhone 은 안되니까 불편해

글쓰는 시점인 2022년에는 애플 제품에 "File(파일)" 이라는 앱을 통해서 관리할 수 있어서 편리해지긴 했습니다만, 과거에는 위 불편사항이 있었습니다.

iOS(기타 Apple 제품) 의 파일 저장구조 혹은 앱 저장구조가 어떠하길래, 이런 불편함이 있을까 조사하다가, Sandbox 개념에 대해서 정리하게 되었습니다.

SandBox?


(출처: www.sandboxie.com)

Programs 에 보면, 친근한 친구들이 몇몇 보이죠. 아이콘 상으로는 없으나, 우리가 만든 App 들도 이곳에 위치합니다. 위에 그림은 샌드박스가 없는 하드디스크이고, 아래 있는 그림은 샌드박스가 적용된 하드디스크 입니다.

빨간색 박스가 새롭게 들어가는 데이터 입니다. 샌드박스가 없는 경우, 정해진 위치에 저장되는 것이 아니라, 무작위로(혹은 우리가 규칙을 알 수 없는) 저장되고 있습니다. 이에 반해, 샌드박스가 적용된 하드디스크는, 노랑색 상자 안에만 저장됩니다.

이렇게 특정 지역에서만 '안전하게' 저장하도록 하는 정책을 "샌드박스(SandBox)" 라고 칭합니다.

어원

현실 세계에서의 SandBox 는 위 그림입니다. 아이들이 놀 곳을 정해놓은 모래상자이죠. 우리나라로 적용해보자면, 모래가 있는 놀이터 내부에만 놀이기구를 추가하니, 그곳을 샌드박스라고 칭할 수 있겠네요.

SandBox 가 적용되지 않은 하드디스크는, 데이터를 추가하는 과정에서 '직접적인' 접근이 가능합니다. 이 때, OS를 손상시킬 목적으로 잘못된 데이터를 추가할 우려가 발생하죠.

반면에,

SandBox 가 적용된 하드디스크의 경우, 해당 범위가 한정됩니다. 그러므로 설사 피해가 가더라도 정해진 범위로 축소되겠죠.

iOS App 에서의 SandBox

그러면, iOS App 에서는 어떤 방식으로 SandBox 를 적용하고 있을까요?

(출처: Exploring iOS's Sandbox)

  • 좌측에 있는 그림이 SandBox 가 적용되지 않은 그림입니다.
  • 우측에 있는 그림이 SandBox 가 적용된 그림입니다.
  • SandBox 정책 아래에 동작하는 iOS App은 OS 혹은 앱 간의 소통은 사용자에게 권한 동이 없이는 접근이 불가능합니다.
  • 하지만 SandBox 내 에서는 동작이 가능하죠. 그래서 Document 라는 앱 내의 파일에서는 데이터를 읽고 쓰기가 가능합니다.

FileManager 다루기

  • FileManager 는 iOS 와 MacOS 에서 파일을 다룰 수 있도록 도와주는 Class 입니다.
class FileManager: NSObject
  • 이 클래스를 통해서 데이터를 지지고 볶을 수 있습니다.

  • 해당 위치는 "Document" 에 위치한 데이터 입니다.

  • App 이 설치된 경로

/Users/kimwoosung/Library/Developer/CoreSimulator/Devices/242C41B9-01E6-4A9A-AC57-087610FF5848/data/Containers/Data/Application/A58B3A4C-BBCD-4EF6-9B2C-A1C027A2BB4D

(Simulator 로 확인한 App 위치)

  • 해당 위치를 확인하시고자 하시면, 아래 Print Statement 를 실행해보시면 됩니다.
print(NSHomeDirectory)

앱에서 저장된 데이터는 "Application/{app id}/Documents" 에 저장됩니다.

let filemgr = FileManager.default
let dirPaths = filemgr.urls(for: .documentDirectory, in: .userDomainMask)
let docsDir = dirPaths[0].path
print("DEBUG: " + docsDir)
  • FileManager 에 접근합니다.
  • 위 코드에서 .documentDirectory 는 앱의 home directory 에 해당합니다.
/Users/kimwoosung/Library/Developer/CoreSimulator/Devices/242C41B9-01E6-4A9A-AC57-087610FF5848/data/Containers/Data/Application/E09202B4-28E2-481D-98D4-A883E70B4123/Documents

(위 코드로 실행한 Documents Path)

애플이 정해둔 Sandbox 내에서는 자유롭게 데이터를 추가할 수 있습니다.

let docsURL = dirPaths[0]
let newDirectory = docsURL.appending(path: "newPath").path
print("DEBUG: " + newDirectory)
  • 결과
/Users/kimwoosung/Library/Developer/XCPGDevices/35CF0D01-2CC4-4886-891A-8135F6F42211/data/Containers/Data/Application/30FD8D27-2D8C-441A-BB8E-268F523C595F/Documents/newPath

폴더 생성 및 파일 생성 전체 코드입니다.

import Foundation

protocol FileHandling {
    func createFolder() throws -> FileHandler
    func createFile(_ fileName: String,
                    content: String
    ) throws -> FileHandler
}

  

class FileHandler: FileHandling {
    // 에러 정의
    enum FileHandlingError: Error {
        case failedCreateFolder
        case filedCreateFile
    }

    let fileManager: FileManager
    var directoryURL: URL!

    init() {
        fileManager = FileManager.default
    }

    // 폴더 생성
    func createFolder() throws -> FileHandler {

        guard let documentsURL = fileManager
        .urls(for: .documentDirectory,
	          in: .userDomainMask).first else {
            throw FileHandlingError.failedCreateFolder
        }
        directoryURL = documentsURL.appending(path: "TestFolor")
        
        do {
            try fileManager.createDirectory(
                at: directoryURL,
                withIntermediateDirectories: false,
                attributes: nil
            )
            return self
        } catch {
            print("\(#function): " + error.localizedDescription)
            throw FileHandlingError.failedCreateFolder
        }
    }

    // 파일 생성
    func createFile(
        _ fileName: String,
        content: String) throws -> FileHandler {
            let filePath = directoryURL.appending(
                path: fileName)
            do {
                try content.write(
                    to: filePath,
                    atomically: false,
                    encoding: .utf8
                )
                return self

            } catch {
                throw FileHandlingError.filedCreateFile
            }
        }
}

이 코드는 다음 코드로 실행했습니다.

struct ContentView: View {
    let fileHandler: FileHandler
    init() {
        self.fileHandler = FileHandler()
    }

    var body: some View {
        VStack {
            Button(
                action: {
                    do {
                        try _ = fileHandler
                                .createFolder()
                                .createFile(
                                    "TestingFolder",
                                    content: "Test 22.11.23"
                                )

                    } catch {
                        print("DEBUG: ERROR")
                    }

                    let filemgr = FileManager.default
                    let dirPaths = filemgr.urls(for: .documentDirectory, in: .userDomainMask)
                    let docsDir = dirPaths[0].path
                    print("DEBUG: " + docsDir)
                },
                label: {
                    Text("파일저장합니다.")
                        .foregroundColor(.white)
                        .padding()
                        .background(Color.blue)
                        .cornerRadius(8)
                })
        }
        .padding()
    }
}
  • 결과

요약

  • App 은 App 각각 자신만의 메모리 사용량과 데이터 저장공간을 할당받고, 서로 침범할 수 없다.
  • 그 범위를 우리는 SandBox 라고 부르기로 했다.
  • 제한된 범위를 컨트롤하는 객체는 FileManager 이다.

참고자료

profile
iOS & Flutter
post-custom-banner

0개의 댓글