실수

NSCache 파일 key - value 설정 실수

elisha0103 2023. 6. 19. 13:49

토이프로젝트 진행 중 URLSession에서 가져온 이미지 데이터를 Filemanager, NSCache를 사용하여 기기 메모리 캐싱, 디스크 캐싱을 시도했다.

이미지를 View에 할당하는 과정은 다음과 같이 작동한다.

Image Cache 순서도

  1. NSCache로 메모리 캐시에서 이미지 파일을 검색 후 있으면 View에 이미지 할당하고 없으면 FileManader를 검사한다.
  2. FileManager로 디스크 캐시에서 이미지 파일을 검색 후 있으면 View에 이미지 할당하고 NSCache로 메모리 캐시에 이미지 파일을 저장한다. URLSession으로 파일을 다운한다.
  3. URLSession으로 이미지 파일을 서버로부터 다운 후 View에 이미지 할당하고 FileManager로 디스크 캐시에 이미지 파일을 저장하고 NSCache로 메모리 캐시에 이미지 파일을 저장한다.

해당 로직 설계도중, URLSession으로 이미지를 할당받아 View에 할당하는 로직은 정상작동함을 확인하였다.

 

이미지 파일을 캐싱할 때, 이미지 파일 저장 경로를 다음과 같이 설정했다.

        // 디스크 캐시 폴더
        guard let diskCachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first else { return nil }
        
        var filePath = URL(fileURLWithPath: diskCachePath)
        filePath.appendPathComponent(url.lastPathComponent)

'캐시 폴더/URL 마지막컴포넌트' 이 경로로 파일을 저장하고 로드한다. 즉, 이미지 파일의 key는 URL의 마지막 컴포넌트가 된다.

URL 마지막 컴포넌트를 key로 설정한 이유는 서버에 업로드된 이미지의 파일 이름이 URL 마지막 컴포넌트이기 때문이다.

 

이렇게 캐시 기능을 구현하고 작동한 결과 다음과 같은 에러가 발생했다.

이미지 캐시 오류

해당 리스트는 UITableView로 구현하였다.

UITableViewCell에 있는 Label 내용과 일치하지 않는 이미지가 UIImage에 할당되는 상황이다.

 

UIKit에 대한 숙련도가 부족한 탓에, 해당 에러는 UITableView가 스크롤되는 동안 이미지를 할당할 때 UITableViewCell의 Index 정보가 바뀌어 잘못 할당되는 것이라고 생각했다.

URLSession으로 다운받는 이미지를 UITableViewCell에 있는 UIImage에 할당할 때 다음과 같은 조건문을 할당했었다.

if let currentCell = tableView.cellForRow(at: indexPath) as? ArticleTableViewCell, currentCell == self {
	guard let img: UIImage = UIImage(data: data) else { return }
                            
	self.articleImage.image = img
}

 

따라서 캐시 파일이 잘못 할당되는 이유도 위와같은 조건문이 없기 때문이라고 생각했지만, 위 조건문을 캐시 코드에도 사용해봤지만 소용 없었다.

(알고보니, 서버로부터 이미지를 받아올 때에는 비동기적으로 이미지를 할당하기 때문에 위 조건문이 필요한 것이었지만, 캐시 파일을 가져오는 것은 비동기 코드가 아니기 때문에 필요가 없었다.)

 

이틀을 고생하며, 로그를 계속 확인하면서 확인한 결과 다음과 같은 파일들이 일괄적으로 문제가 발생되는 것이 파악됐다.

문제되는 파일들

이미지의 URL은 서로 다르지만 URL의 마지막 컴포넌트 즉 '840_560.jpeg' 이름의 이미지 파일들이 문제가 있던 것이다.

 

나는 그동안 이미지 파일을 캐싱할 때, 파일의 key로 '이미지 파일 이름'을 사용했었는데 서버에 올라간 이미지 파일의 이름이 고유값이 아니어서 해당 에러가 발생했던 것이다.

 

URL의 전체 경로는 고유값이 될 순 있어도 URL에 있는 URN(이미지 파일 이름)은 고유값이 아닐 수 있는 것이어서 캐시 경로를 다음과 같이 수정했다.

        guard let diskCachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first else { return nil }
        
        var filePath = URL(fileURLWithPath: diskCachePath)
        
        var urlsPathComponents = url.pathComponents
        urlsPathComponents.removeFirst()
        filePath.appendPathComponent(urlsPathComponents.joined(separator: ""))

 

URL의 컴포넌트를 모두 분리하여 배열로 만든 후, 첫 번째 요소(Index 0) 값인 '/'만 삭제한 후 하나의 문자열로 반환한 값을 캐시 경로로 수정하고 해당 값을 이미지 파일의 key 로 사용했다.

 

예를 들어, "https://tistory.com/48392/123df/3193.jpeg" URL이 있다고 하고 URL의 모든 컴포넌트를 분리하게 되면 다음과 같은 배열이 반환된다.

'/', '48392', '123df', '3193.jpeg'

여기서 첫번째 요소 '/'는 식별에 필요없으므로 삭제 후 하나의 문자열로 반환하면

'48392123df3193.jpeg'가 되고 이를 key 로 사용한다는 뜻이다.

 

 

그리고 캐시 파일을 저장할 때에도 같은 과정을 거쳐 반환된 값을 key를 사용했다.

self.cache.setObject(img, forKey: filePath.path as AnyObject)
self.fileManager.createFile(atPath: filePath.path, contents: img.jpegData(compressionQuality: 0.4))

 

이 코드를 적용한 결과,

위와 같이 이미지 URL의 컴포넌트를 하나의 문자열로 캐시의 key 값으로 설정하여 파일이 저장되는 것을 볼 수 있고 UITableViewCell의 이미지도 정상적으로 할당되는 것을 확인할 수 있었다.