이모저모/Swift

ios - 이미지 중복 사용 방지 처리

ARpple 2024. 1. 25. 17:09

RCManager

앱 내 파일 관리하기

Realm 패키지 의존 모듈

RCManager 구성

  • RCManager (Reference Count Manager)는 참조 계수 매니저로 Swift와 Objective-C의 메모리 관리 기법에서 비롯함.
  • 샌드박스 내부(로컬)에서 저장해야하는 파일 (이미지, docs 등등...)의 여러 로컬 DB에서 사용하는 경우 파일을 중복 생성해서 보관하는 것을 방지하기 위함
  • 각각의 디비 레코드에서 특정 파일의 이름들 보관하는 경우 Reference Count 1 증가
  • 특정 파일의 이름을 삭제하는 경우 Reference Count 1 감소...
  • Reference Count가 0인 경우 해당 파일 삭제 및 참조 계수 매니저 테이블에 해당 레코드 삭제

RCManager 생성 지침

  1. 싱클톤 패턴 적용, 하나의 파일 타입을 하나의 매니저 인스턴스로 관리
  2. Snapshot 패턴 적용, 하나의 파일 타입(이미지, 영상, 문서 등등...)에 맞는 매니저 인스턴스에 apply를 통한 관리
  3. 파일 저장은 각각의 파일 타입에 따라 다르기 때문에 직접 구현해야함
    1. 해당 파일 이름이 존재하는지 확인하는 기능 제공
    2. 파일 ReferenceCount가 0이면 삭제하는 기능 제공
💡 이 서비스 관련 타입은 앞에 RCM이라고 붙임

코어 로직

💡 코어 로직을 상속 혹은 채택해서 실제 파일 타입에 맞는 매니저를 구현한다.

#클래스 - Realm과 직접 연결됨

1. RCMTable - Class

실제 Realm에 담길 래퍼런스 카운트 Realm 테이블 구조

2. RCMRepository - Class

Realm DB 시스템에서 CRUD를 도와주는 Realm Repository... RCMTable과 직접 소통할 수 있다.

3. RCMTableConvertable - Protocol

  • 커스텀 Table을 Realm Table을 [RCMTable]로 변환하기 위한 프로토콜
  • [RCMRepository]에서 이 프로토콜을 준수한 인스턴스를 사용함

#매니저 관련

1. RCMAble - Protocol ⭐

  • 래퍼런스 카운트 매니저 클래스는 프로토콜을 준수해서 구현

2. RCMAble - Struct

  • RCMTableConvertable에서 사용하는 제네릭 스냅샷

#FileManager Extension

ReferenceCount에 의해 관리되는 로컬 파일 경로(이름)을 조회하거나 삭제할 수 있는 익스텐션

사용 예시

💡 채팅방 서비스에서 각 사용자별 프로필 이미지를 중복 없이 확인하고 추가하기
var ircSnapShot: ImageRC.SnapShot!
// 특정 서비스[ex: A 채팅방]에서 추가할 이미지 아이디들
func searchSelectionUpdate(ids:[String]){ 
        // 웹 URL 기반으로 로컬 이미지 이름 만들기
        let newItems = ids.enumerated().map { ($1.getLocalPathName(type: .search),$0) }
        let dict = newItems.reduce(into: [:]) { $0[$1.0] = $1.1 }
        let newFileNames = newItems.map{$0.0}
        // 기존에 존재한 이미지들을 제외하고 새로 로컬에 저장할 이미지들 (다른 서비스[ex: B 채팅방]에서는 사용하고 있을 가능성이 있다.)
        let appendFileNames = Array(Set(newFileNames).subtracting(selectedItems))
        // 새로 로컬에 저장할 이미지들의 원래 존재한 인터넷 URL 가져오기
        let downloadURLs = appendFileNames.compactMap { dict[$0] }.map{ids[$0]}
        Task{
            // 만약 로컬이 이미지 이름이 없으면 이미지 다운로드, 있으면 로컬에서 가져온다.
            try await ImageService.shared.saveToDocumentBy(searchURLs: downloadURLs,fileNames: appendFileNames)
            passthoroughLoading.send(false)
            // 특정 서비스[ex: A 채팅방]에 기존에 사용하는 로컬 이미지들과 새로 다운받은 로컬 이미지들을 합치기
            self.selectedItems = selectedItems.union(appendFileNames)
            // 특정 서비스[ex: A 채팅방]에 새로 추가한 이미지들 ImageRCM 업데이트
            await updateImageRC(appends:appendFileNames,deletes:[])
        }
    }

    func updateImageRC(appends: any Sequence<String>,deletes:any Sequence<String>) async {
        await appends.asyncForEach {
            if !ircSnapShot.existItem(id: $0){
                await repository.insert(item: ImageItem(name: $0, count: 0))
            }
            await ircSnapShot?.plusCount(id: $0)
        }
        Task.detached {[weak self ] in
            await deletes.asyncForEach { await self?.ircSnapShot?.minusCount(id: $0) }
        }
    }

RCM 코드

RCM(ReferenceCountManager).zip
0.01MB