본문 바로가기

iOS/Deep dive!!!

[iOS/Swift] NSCache와 FileManager 등 iOS에서 왜 caching을 할까? 캐싱 개념 탐구하기

 

안녕하세요.

iOS 앱 개발을 할 때 SDWebIamge 라이브러리를 사용해서 url 에서 이미지를 다운받지 않을 때 어떤 일이 발생할 지 앞의 라이브러리나 킹피셔를 사용하지 않고 인스타그램 앱 개발 하고 있는데요. "같은 화면 들어갈 때마다 왜 이미 로드 된 내 이미지를 서버에서 다시 다운 받지?.." 그렇게 알게 된 개념이 caching 입니다. 

 

1. Cache concept in computer structure

컴퓨터 구조를 예로,, 캐시가 없다면 데이터를 가져와 작업을 하기 위해 cpu는 메인 메모리에 있는, 만약 메인 메모리에 원하는 데이터가 없다면 아니면 메모리보다 더 느린 저장 장치에서 메모리를 통해 데이터를 가져와야 합니다.

캐시는 메인 메모리보다 용량이 작은데 그 대신 속도가 엄청 빠릅니다. 그리고 메인 메모리에 로드 된 데이터를 캐시에 로드할 수 있다면 cpu는 메인 메모리보다 속도가 빠른 캐시에서 데이터를 접근할 수 있습니다. 여기서 데이터 지역성이란 개념이 있는데, 자주 사용되는!! 쓸만한 데이터를 메인 메모리 말구 캐시에 두자!!(여기서 cache miss, hit를 고려한 시간 지역성 vs 공간 지역성 개념이 있습니다.) 요약하자면,, 자주 사용되는 데이터를 속도가 빠른 캐시에 저장하면 성능 향상이 된다!!

 

2. Why should we apply caching? 

기본적으로 화면의 구성은 화면에 필요한 데이터를 서버에서 fetch한 후에 사용자에게 보여줍니다. 동일한 화면에서 필요한 images data, models, contents 등의 동일한, 이미 한 번 요청한 데이터를 서버한테 request해서 여러 번 다운받게 된다면 데이터가 reloading됩니다. 이는 불필요한 작업입니다. 

 

따라서 이미 한 번 다운 받은 데이터는 앱 내에서 저장 또는 임시적으로 유지함으로써 재사용할 수 있습니다. REST 방식으로 서버에 여러 번 request 해서 동일한 데이터를 받아오는 시간보다 보다 앱 내부에 존재하는 데이터를 꺼내 오는게 훨씬 빠르기 때문입니다. 또한 서버에서 이미지를 받아 올 경우 (예로 binary data 타입을) 특정 타입에 맞게 변형 시켜야하는 작업을 수행하지 않아도 되는 장점이 있습니다. Caching을 통해 data를 효율적으로 저장, load가 가능하고 memory footprint도 적게 유지 할 수 있어야 합니다.

 

한 번 다운받은 데이터를 앱 내에서 유지 또는 저장 시키는 방법은 크게 3가지가 있습니다. Structured data type를 저장하기 위한 Core data, SQLite, Realm 등 db에 저장, UserDefaults, KeyChains 등 사용자의 정보, 기본사항, 민감한 사항을 저장 등 다양한 방법이 있습니다. Unstructured data 예를들어 blob(binary large object)와 같은 이미지, 동영상, 오디오 등의 binary data는 주로 FileManager에 저장해서 관리합니다. 

 

데이터를 저장하는 방식으로 db도 있는데 캐싱의 주요 개념은 임시적으로 데이터를 저장하고 빠르게 가져오는 방법입니다. 캐싱은 한번 다운받은 이미지, 텍스트 등을 저장할 뿐 아니라 특정 화면에서 사용자가 스크롤 했던 위치 등도 저장할 수 있습니다. 정말 다양한 응용이 가능합니다.

 

3. How to do well-known caching in iOS

Swift에서 caching을 하는 방법 중 가장 잘 알려진 방법이 있습니다. in-memory 방식과 on-disk 방식입니다. in-memory는 휘발성이고 on-disk는 비휘발성적인 특징이 있습니다.

 

in-memory

앱 내에 있는 메모리를 사용해서 데이터를 임시적으로 저장합니다. 휘발성인 특징이 있기에 앱이 종료되면 cache에 저장된 데이터는 사라집니다. 대표적으로 NSCache를 통해서 저장을 할 수 있습니다. NSCache의 특징은 key-value 쌍으로 데이터를 collection 하는 특징이 있습니다.

 

Key-value로 값을 수집하는 object는 Dictionary가 존재합니다. 물론 딕셔너리를 통해 캐싱을 할 수 있습니다. 하지만 NSCache를 사용하는 가장 큰 이유는 Dictionary에는 auto-eviction이 적용되지 않는다는 것입니다. NSCache는 다른 mutable commection과 다른 점은 auto-evition이 적용되는 정책이 있기 때문입니다. 시스템에서 메모리 자원이 부족할 경우에 자동으로 제거 됩니다. 이를통해 NSCache에 저장된 데이터는 너무 많은 system memory를 사용하지 않을 것이 보장됩니다. (memory footprit 최소화)

 

딕셔너리는 이와 같은 정책이 없습니다. Caching을 하기 위해 데이터를 수집하고 이를 활용하는 것인 좋은데 또 중요한 것은 memory footprint를 적게 유지하고, 계속 남아있지만 사용되지 않는 데이터는 버려야 하는 시기를 결정하는 로직을 구성해야 한다는 것입니다. NSCaching을 사용하면 auto-eviction 정책 때문에 해결된다는 점!! 버려진 데이터가 다시 필요로 할 때, 서버에서 받아와서 계산or 특정 타입으로 캐스팅 되야 합니다. 

 

또한 NSCache는 Thread-safe 처리가 가능합니다. 동시성 문제를 해결하기 위한 lock 알고리즘을 통해 synchronization 처리하는 작업을 구성할 필요가 없습니다. 근데 최근 iOS 카톡방에서 caching을 사용한 이미지 캐싱 third party인 킹피셔에서 lock을 사용하는 구간이 있습니다. 크러시가나서 도입한것 같은데.. 왜인지는 모르겠네요.. thread-safe가 가능하다고 공식문서에 나와있는데 말입니다.

 

NSCache의 타입은 NSString, AnyObject 두 개의 타입을 요구합니다. 이를 weapper로 만들 경우에 서버에서 받아오는 Codable타입을 곧 바로 넣을 수 있고, 이를 통해 캐싱에서 특정 데 이터를 꺼내 올 때 계산하지 않아도 된다는 장점이 있는데 이는 Cacing in Swift

블로그에서 잘 알려줍니다.

 

in-memory 는 메모리 기반으로 동작이 된다는 것으로 메모리에 로드된 후에 처리가 가능함을 의미합니다. 인메모리 저장소 타입은 주로 데이터 객체의 런타임 캐싱에 활용됩니다.

 

on-disk

in-memory 캐싱의 특징 중 휘발성이 있는데 이는 곧 단점이 될 수 있습니다. 앱이 종료되면 캐싱된 데이터들은 전부 소멸되기 때문입니다. 이는 오프라인으로 앱을 사용하는 유저에겐 치명적일 수 있습니다. (예를 들어, 지하철이나 산에서 갑자기 데이터가 통하지 않을 때 오프라인이 됩니다.) 오프라인에서도 서버에서 다운받은 데이터를 유지하기 위한 방법으로 크게 앱 내부에 DB를 통한 저장 or 파일 형식의 on-disk 방식으로 저장하는 방법이 있습니다. 즉 서버에서 다운 받은 데이터나 사용하면서 발생한 데이터를 오프라인 환경에서도 사용할 수 있다는 뜻은 앱이 종료되어도 데이터가 날라가지 않는다는 의미입니다.

 

DB의 경우 query와 필터링을 통해 특정 데이터를 가져올 때 유리하지만, 비정형 구조인 blob를 저장할 타입이 없습니다. 이 경우에 주로 fileManager를 사용하는데, 정형 데이터도 많고 비정형 데이터도 많을 경우엔 db와 fileManager를 혼합해서 사용한다고 합니다. 정형 + 비정형을 저장할 수 있는 fileManager!! 근데 파일메니저는 정형 + 비정형이 가능하고 데이터를 불러오는게 훨씬 간편합니다. 근데 파일 시스템이 상대적으로 느립니다.

 

CoreData의 경우 in-memory로 구현이 가능합니다. DB보단 ORM에 가깝다고 합니다. Swift로 코드를 구성해서 데이터를 불러올 수 있다는 장점이 있고, read, write하는 모든 data는 in-memory로만 가능합니다. FileManger는 on-disk방식이기에 iOS내에서 디렉토리 생성하고 파일에 대해서 생성, 수정, 읽기 등의 동작이 이루어집니다. 따라서 data access는 위에서 언급한 in-memory 처럼 메모리에서 값을 access하는게 아니기 때문에 느립니다.

 

fileManager vs db 글을 참고하면 좋을 것 같습니다.

 

 

캐싱을 공부하면서 db와 약간 했갈렸습니다. 근데 '캐싱' 을 하기 위해 NSCache 말고 NSURLCache, NSURLSession등 다양한 object를 활용하는 방법이 있는데 주로 NSCache를 통해 처리하고, 데이터를 오래 유지하기 위해선 fileManager를 이용한다는 것입니다. NSCaching 과 fileManager를 활용 방법은 이 블로그가 좋은 것 같습니다.

 

그리고 URLCache NSCache와 다르게 in-memory 방식과 on-disk 방식 둘 다 구현이 가능하다고 합니다. +_+

 

References: 

https://www.swiftbysundell.com/articles/caching-in-swift/

https://developer.apple.com/documentation/foundation/nscache?changes=_5 

https://medium.com/device-blogs/the-many-offline-options-for-ios-apps-2922c9b3bff3

https://betterprogramming.pub/cache-images-in-a-uicollectionview-using-nscache-in-swift-5-b70ebf090521