안녕하세요!
계속해서 앱 기능과 코드에 대한 내용으로 글을 이어가보겠습니다.
이번 글의 내용은 파일 매니저인데요
아래 그림처럼 앱의 이미지 부분을 누르면 앨범의 사진을 가져올 수 있습니다.
앨범의 사진을 저장한 경우 앱에 할당된 영역 안의 폴더에 이미지가 저장됩니다.
원래 처음에 앱을 구상할때는 전부 코어 데이터로 로컬 데이터를 처리하려고 했습니다만...
자료를 찾다보니 이미지 처럼 용량이 큰 경우에는 파일 매니저로 해야한다고 하더라구요?
코어 데이터를 사용만 하면서 개념에 대한 이해가 부족했었는데 이참에 조금 더 자세하게 알아봤습니다.
단순하게 로컬에 데이터를 저장할때는 무조건 코어 데이터를 사용한다고 생각하면
이미지 파일을 코어 데이터로 저장해도 되고 실제로도 가능합니다.
하지만 코어 데이터는 '객체 그래프 관리 프레임 워크'라는 말을
코어 데이터에 검색해보면 한번쯤 들어보셨을텐데요.
저도 완벽하게 이해하진 못했지만 중요한 내용은
'객체'라는 각각의 데이터를 코어 데이터가 데이터들간의 관계성을 통해 '객체 그래프'로 관리하는데
이 작업을 메모리에 올려놓고 한다고 합니다.
(lazy 형식으로 실질적으로 사용할 때에 메모리에 올린다고 합니다.)
그래서 용량이 적을 경우에는 비교적 빠른 성능을 보이지만 반대로
이미지 파일과 같이 용량이 큰 데이터들을 메모리에 많이 올려놓게 되면
속도 저하와 메모리 부족이라는 문제가 생깁니다.
그래서 이번 앱에서는 String 타입의 이미지 경로만 코어 데이터에 저장하고
파일 매니저를 통해 해당 경로에 이미지를 저장하는 방식으로 개발을 진행했습니다.
경로와 관련된 데이터로 Date()를 통해 생성된 날짜~시간의 값을 설정하였습니다.
초 단위로 데이터 경로를 설정해두면 겹치는 일을 피할 수 있을 것이라고 생각했는데
더 찾아보니 경로와 관련된 특정한 값을 따로 지정해두는것이 더 좋다고 하여 다음 프로젝트에 참고할 예정입니다.
다음은 파일 매니저를 사용한 코드와 각 메서드에 대한 설명입니다!
- makeDirectory()
앱이 실행될 때 실행하기 위해 AppDelegate의 didFinishLaunchingWithOptions시점에서 동작하도록 하였습니다.
처음 실행될 때 파일을 만들고 다음 실행에 생성된 파일이 이미 존재하면 파일이 존재한다는 로그를 남기고 리턴합니다.
- createFile(urlPath: String, image: UIImage)
이미지를 makeDirectory()에서 생성된 파일에 저장합니다.
파라미터로 저장할 경로와 이미지를 받습니다.
경로는 이미지를 저장하는 시점의 Date()로 생성되었습니다. makeDirectory()에서 생성된 디렉토리에 파라미터로 전달받은 경로로 이미지 파일을 저장합니다.
- readFile(urlPath: String) -> UIImage
경로를 파라미터로 받아서 해당 경로에 해당하는 이미지를 리턴합니다.
경로를 통해 받아온 데이터는 Data 타입이기 때문에 UIImage 타입으로 타입 캐스팅을 합니다.
값이 없을 때 기본 이미지로 UIImage(named: "restaurant")을 사용하였습니다.
- deleteFile(urlPath: String)
경로를 파라미터로 받아서 해당 경로에 해당하는 이미지를 삭제합니다.
이미지를 업데이트 하는 코드는 createFile(urlPath: String, image: UIImage)를 실행했을 때
같은 경로에 있던 파일이 변경되어 따로 코드를 작성하지 않았습니다.
// 싱글톤
static let shared = ImageManager()
private let fileManager = FileManager.default
// 파일 매니저 생성(앱 초기화 때 한번만 실행)
func makeDirectory() {
// urls 메소드 => 요청된 도메인에서 지정된 공통 디렉토리에 대한 URL배열을 리턴해주는 메소드
// for: 폴더를 정해주는 요소. Download 혹은 Document 등등
// in: 제한을 걸어주는 요소. 그 이상은 못가게 하는
guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
// 생성할 디렉토리 경로(url에 경로 추가)
let directoryPath = url.appendingPathComponent("RestuarantImages")
// 디렉토리 생성
do {
// at: 경로 및 폴더명, 위에서 만든 URL 사용
// withIntermediateDirectories: “중간 디렉토리들도 만들거니?” 라는 의미
// attributes: 파일 접근 권한, 그룹 등등 폴더 속성 정의
try fileManager.createDirectory(
at: directoryPath,
withIntermediateDirectories: false,
attributes: [:]
)
} catch {
print(error)
}
print("\(url.path)")
}
// MARK: - Creare: 파일 생성 && Update -> 동일 코드로 동작 시 파일 변경
func createFile(urlPath: String, image: UIImage) {
// urls 메소드 => 요청된 도메인에서 지정된 공통 디렉토리에 대한 URL배열을 리턴해주는 메소드
// for: 폴더를 정해주는 요소. Download 혹은 Document 등등
// in: 제한을 걸어주는 요소. 그 이상은 못가게 하는
guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
// 이미지 처리
guard let imageData = image.jpegData(compressionQuality: 1) else { return } // ?? image.pngData() else { return }
// 생성할 디렉토리 경로(url에 경로 추가)
let directoryPath = url.appendingPathComponent("RestuarantImages")
// 이전 디렉토리 경로에 경로 추가
let imagePath: URL = directoryPath.appendingPathComponent("\(urlPath).jpeg")
// 경로에 파일 만들기
do {
try imageData.write(to: imagePath)
} catch {
print(error)
}
}
// MARK: - Read: 파일 읽기
func readFile(urlPath: String) -> UIImage {
// urls 메소드 => 요청된 도메인에서 지정된 공통 디렉토리에 대한 URL배열을 리턴해주는 메소드
// for: 폴더를 정해주는 요소. Download 혹은 Document 등등
// in: 제한을 걸어주는 요소. 그 이상은 못가게 하는
guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return UIImage(named: "restaurant")! }
// 생성할 디렉토리 경로(url에 경로 추가)
let directoryPath = url.appendingPathComponent("RestuarantImages")
// 이전 디렉토리 경로에 hi.txt 경로 추가
let imagePath: URL = directoryPath.appendingPathComponent("\(urlPath).jpeg")
do {
// URL을 불러와서 Data타입으로 초기화
let imageData: Data = try Data(contentsOf: imagePath)
// Data to UIImage
let image: UIImage = UIImage(data: imageData) ?? UIImage(systemName: "person")!
return image
} catch {
print(error)
return UIImage(systemName: "person")!
}
}
// MARK: - Delete: 파일 삭제
func deleteFile(urlPath: String) {
// urls 메소드 => 요청된 도메인에서 지정된 공통 디렉토리에 대한 URL배열을 리턴해주는 메소드
// for: 폴더를 정해주는 요소. Download 혹은 Document 등등
// in: 제한을 걸어주는 요소. 그 이상은 못가게 하는
guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
// 생성할 디렉토리 경로(url에 경로 추가)
let directoryPath = url.appendingPathComponent("RestuarantImages")
// 이전 디렉토리 경로에 hi.txt 경로 추가
let imagePath: URL = directoryPath.appendingPathComponent("\(urlPath).jpeg")
do {
try fileManager.removeItem(at: imagePath)
} catch {
print(error)
}
}
앨범의 사진을 저장한 경우에만 메모리를 차지하도록 하도록
해봤는데 이 내용은 다음에 올리도록 하겠습니다.
++ 누적해서 정리한 내용은 깃허브에 올리고 있습니다. 링크