앱을 개발하다 보면 FileManager를 이용해 이미지를 저장하거나 불러오는 기능을 자주 구현하게 된다.
특히 나처럼 Core Data + FileManager를 함께 쓰는 경우라면,
이미지 저장 폴더 경로를 여러 곳에서 접근해야 하는 상황이 생긴다.
예를 들어 DiaryImageFileManager 클래스 내부에서
이런 코드가 있다고 해보자 👇
private func getDocumentsDirectory() -> URL {
guard let doc = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
LogManager.print(.error, "Document 디렉토리를 찾을 수 없습니다.")
return URL(fileURLWithPath: "")
}
let folder = doc.appendingPathComponent(folderName)
if !FileManager.default.fileExists(atPath: folder.path) {
try? FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
}
return folder
}
여기서 문제가 생긴다.
이 함수가 private으로 선언되어 있어서, 다른 클래스에서는 이 경로를 직접 가져올 수 없다는 점.
그래서 단순히 이렇게 생각하게 된다.
“그럼 그냥 private을 빼면 되지 않나?” 🤔
하지만… 그건 좋은 방법이 아니다.
🚨 왜 private을 쉽게 풀면 안 될까?
private은 단순한 접근 제어가 아니라 “의도”를 담는 장치다.
즉, “이 메서드는 외부에서 직접 호출하지 말라”는 의미야.
FileManager에서 폴더 경로를 생성하거나 관리하는 로직은
앱 내부 데이터의 구조와 직접적으로 연결돼 있다.
이걸 외부에서 무분별하게 호출할 수 있게 두면,
1. 다른 클래스에서 중복 폴더 생성
2. 잘못된 시점의 파일 삭제
3. 데이터 정합성 깨짐
등의 문제가 발생할 수 있다.
즉, 정보 은닉(Encapsulation) 을 지켜야만
클래스 내부의 일관성과 안정성을 유지할 수 있다.
🧩 그렇다면 대안은?
✅ 방법 1. 읽기 전용(computed) 프로퍼티로 노출하기
가장 깔끔하고 많이 사용하는 패턴이다.
폴더 경로를 직접 계산해 반환하되, 외부에서는 읽기만 가능하게 한다.
final class DiaryImageFileManager {
static let shared = DiaryImageFileManager()
private init() {}
private let folderName = "EmotionDiaryImages"
/// 👍 외부에서 접근 가능한 읽기 전용 폴더 URL
var folderURL: URL {
getDocumentsDirectory()
}
private func getDocumentsDirectory() -> URL {
guard let doc = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
LogManager.print(.error, "Document 디렉토리를 찾을 수 없습니다.")
return URL(fileURLWithPath: "")
}
let folder = doc.appendingPathComponent(folderName)
if !FileManager.default.fileExists(atPath: folder.path) {
do {
try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true)
} catch {
LogManager.print(.error, "이미지 폴더 생성 실패: \(error.localizedDescription)")
}
}
return folder
}
}
💡 핵심 포인트
- getDocumentsDirectory()는 여전히 private 상태 유지
- 외부에서는 DiaryImageFileManager.shared.folderURL 로 접근
- 외부 클래스는 폴더 생성 로직을 전혀 알 필요가 없음
✅ 결과: “읽기만 가능하지만, 내부 구조는 보호됨”
즉, 정보 은닉(Encapsulation) 과 재사용성(Reusability) 의 완벽한 균형!
✅ 방법 2. 경로 대신 결과만 반환하는 헬퍼 메서드 제공
만약 외부에서 “폴더 안의 파일 목록”처럼
결과물만 필요하다면, 경로 자체를 노출하지 않고
필요한 데이터만 반환하는 메서드를 만들어도 된다.
extension DiaryImageFileManager {
/// 감정일기 폴더의 URL 리스트 반환
func fetchDiaryFolderURLs() -> [URL] {
let baseFolder = getDocumentsDirectory()
return (try? FileManager.default.contentsOfDirectory(at: baseFolder, includingPropertiesForKeys: nil)) ?? []
}
}
이렇게 하면 외부에서는 baseFolder에 직접 접근할 수 없고,
FileManager 내부의 경로 관리 로직도 완전히 캡슐화된다.
⚙️ 접근 제어자의 의도 다시 보기
| 접근 제어자 | 설명 | 사용 시점 |
| private | 오직 이 클래스 내부에서만 접근 가능 | 내부 로직, 헬퍼 함수 |
| fileprivate | 같은 파일 내 다른 타입에서 접근 가능 | 협력형 타입 있을 때 |
| internal | 같은 모듈 내 어디서든 접근 가능 (기본값) | 앱 내부 공용 |
| public | 외부 모듈에서도 접근 가능 | 프레임워크 공개용 |
| open | 외부 모듈 + subclass/override 허용 | SDK, 라이브러리 제작 시 |
✨ 결론
| 항목 | 결론 |
| getDocumentsDirectory() | 그대로 private 유지 |
| folderURL | ✅ 읽기 전용 computed property로 외부 접근 허용 |
| 외부에서 폴더 접근 | folderURL 또는 별도 메서드로만 가능 |
'감정일기(가칭)' 카테고리의 다른 글
| 🧪 DiaryTestManager, 어디에 두는 게 맞을까? (0) | 2025.10.20 |
|---|---|
| 🍋 Core Data Fetch 고급 설계 — Predicate, Compound, FetchLimit 완전정복 (0) | 2025.10.20 |
| 🚀 Swift 6에서 바뀐 Concurrency 규칙 완전 정리 (0) | 2025.10.19 |
| 🧠 Swift Concurrency — “Capture of 'self' with non-sendable type” 완전 정리 (1) | 2025.10.19 |
| 💾 왜 Core Data의 Read는 반드시 async여야 할까? (0) | 2025.10.18 |