[EasyCloset] ๐Ÿ’ฐ ์ด๋ฏธ์ง€ ์บ์‹ฑ ๊ตฌํ˜„

Mason Kimยท2023๋…„ 6์›” 23์ผ
0

EasyCloset ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

๋ชฉ๋ก ๋ณด๊ธฐ
2/5

NSCache๋ฅผ ํ†ตํ•œ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ ๊ตฌํ˜„ / ์ €์žฅ ๊ฐฏ์ˆ˜, ์šฉ๋Ÿ‰ ์ œํ•œ

๋ฐฐ๊ฒฝ

  • EasyCloset ์•ฑ์˜ ์ด๋ฏธ์ง€ ๋กœ๋”ฉ ํ”„๋กœ์„ธ์Šค
    image
  • ์ฒ˜์Œ์—” ๋‹จ์ˆœํžˆ NSCache์— ์ €์žฅ๋งŒ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ตฌํ˜„ํ•˜์˜€์œผ๋‚˜ ๋ฌธ์ œ์ ์ด ๋– ์˜ค๋ฆ„.
  • NSCache๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค€๋‹ค๊ณ ๋Š” ํ•˜์ง€๋งŒ, ํ˜น์‹œ๋‚˜ ์บ์‹ฑ์œผ๋กœ ์ €์žฅ๋˜๋Š” ์ด๋ฏธ์ง€๊ฐ€ ๋„ˆ๋ฌด ํฌ๊ฑฐ๋‚˜ ์ €์žฅ๋˜๋Š” ์ด๋ฏธ์ง€์˜ ๊ฐฏ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ๋‹ค๋ฉด,
    ์•ฑ์ด ๊ตฌ๋™๋˜๋Š” ๋Ÿฐํƒ€์ž„์— ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์„๊นŒ? ๋ผ๋Š” ์ƒ๊ฐ์ด ๋– ์˜ค๋ฆ„
  • ์•„์ฃผ ํฐ ์šฉ๋Ÿ‰์˜ ์ด๋ฏธ์ง€๋กœ ํ…Œ์ŠคํŠธ ํ•ด๋ณธ ๊ฒฐ๊ณผ, ์•ฑ์ด ํ—ˆ์šฉํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ๊นŒ์ง€๋Š” ๊ฑฐ์˜ ๋์—†์ด ์ €์žฅํ•˜๋Š ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ์Œ

์ดˆ๊ธฐ ๊ตฌํ˜„ _ ๋‹จ์ˆœํžˆ NSCache์— ์ €์žฅ

final class ImageCacheManager {
  static let shared = ImageCacheManager()  
	
  private let cache = NSCache<NSString, UIImage>()
	
  func get(for key: String) -> UIImage? {
    cache.object(forKey: key as NSString)
  }
  
  func store(_ value: UIImage, for key: String) {
    cache.setObject(value, forKey: key as NSString)
  }

NSCache์— ๋Œ€ํ•ด ์ข€ ๋” ๊นŠ์ด ํ•™์Šตํ•˜๋ฉฐ ๋ฆฌํŒฉํ„ฐ๋ง ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•จ

NSCache์— ๋Œ€ํ•œ ํ•™์Šต

  1. Thread Safeํ•œ ์ด์œ ๋Š”?
  • ๋‚ด๋ถ€์ ์œผ๋กœ NSLock์„ ์‚ฌ์šฉํ•˜์—ฌ lock, unlock์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— thread safe ํ–ˆ๋˜ ๊ฒƒ
open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
    private let _lock = NSLock()
    ...
  1. ๋‚ด๋ถ€ ๊ตฌ์กฐ๋Š” Dictionary + Linked List ๊ตฌ์กฐ์ž„
  • ์บ์‹ฑ์ด๋ผ๋Š” ์ž‘์—… ํŠน์„ฑ์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€, ์‚ญ์ œํ•˜๋Š” ์ž‘์—…์ด ๋นˆ๋ฒˆํ•˜๊ฒŒ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ€๊ณ  ๋‹น๊ธฐ๊ธฐ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋งํฌ๋“œ๋ฆฌ์ŠคํŠธ๋ฅผ ํ™œ์šฉํ•ด์„œ ํ•ด๋‹น ์ž‘์—…์„ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹๊นŒ.๋ผ๊ณ  ์ƒ๊ฐ
  • ๋˜ํ•œ, ๋งŒ์•ฝ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ ๊ตฌ์กฐ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ํƒ์ƒ‰์— O(n)์ด ๋ฐœ์ƒ๋˜๊ธฐ์— ๋™์‹œ์— ๋”•์…”๋„ˆ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ key๊ฐ’์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ ‘๊ทผํ•  ๋•Œ O(1)์œผ๋กœ ๋น ๋ฅด๊ฒŒ ํƒ์ƒ‰ํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐ
open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
    private var _entries = Dictionary<NSCacheKey, NSCacheEntry<KeyType, ObjectType>>()
...
private class NSCacheEntry<KeyType : AnyObject, ObjectType : AnyObject> {
    var key: KeyType
    var value: ObjectType
    var cost: Int
    var prevByCost: NSCacheEntry?
    var nextByCost: NSCacheEntry?
...
  1. ๋”•์…”๋„ˆ๋ฆฌ์™€ ๋‹ค๋ฅด๊ฒŒ ํ‚ค ๊ฐ’์„ ๋ณต์‚ฌํ•˜์ง€ ์•Š๋Š”๋‹ค?
  • ๋ณต์‚ฌํ•˜์ง€ ์•Š๊ณ  ์ฐธ์กฐํ•œ๋‹ค = ์ฐธ์กฐ ํƒ€์ž…๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค = AnyObject๋กœ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.
  • ๊ทธ๋ž˜์„œ key๋กœ struct ํƒ€์ž…์ธ String, Int ๋“ฑ์€ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๊ธฐ์— ๋ธŒ๋ฆฟ์ง•์„ ํ†ตํ•ด NSString ๋“ฑ์œผ๋กœ ํ‚ค๊ฐ’์„ ์ง€์ •ํ•ด์ค˜์•ผ ํ–ˆ๋˜ ๊ฒƒ.
  • ๋˜ํ•˜ ๋‚ด๋ถ€ entry Dictionary์˜ Key๋ฅผ Wrappingํ•˜๋Š” NSCacheKey๋ผ๋Š” ํด๋ž˜์Šค๊ฐ€ ์กด์žฌํ•จ
open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
    let g = max(g, 0) // costLimit์„ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ์€ 0์ž„.
    let keyRef = NSCacheKey(key)
fileprivate class NSCacheKey: NSObject {
    var value: AnyObject
    init(_ value: AnyObject) {
        self.value = value
        super.init()
    }
  1. ์บ์‹ฑ์ด ์‚ญ์ œ๋˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ์ฒดํฌ
  • NSCacheDelegate ์˜ willEvictObject๋ฅผ ๊ตฌํ˜„ํ•จ์œผ๋กœ์„œ ์บ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ญ์ œ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ
extension ImageCacheManager: NSCacheDelegate {
  func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: Any) {
    print("\(obj as? UIImage) ์ •๋ณด๊ฐ€ ์บ์‹œ์—์„œ ์ง€์›Œ์ง„๋‹ค.")
  }
}
  1. countLimit, totalCostLimit
  • countLimit์œผ๋กœ ์ €์žฅ ๊ฐฏ์ˆ˜์˜ ์ œํ•œ์„ ์ค„ ์ˆ˜ ์žˆ๊ณ ,totalCostLimit์œผ๋กœ ์ €์žฅ์˜ ๋น„์šฉ์˜ ๊ฐ€์ค‘์น˜๋ฅผ ๋ถ€์—ฌํ•ด ํŠน์ • ๋น„์šฉ๋งŒํผ ์ €์žฅ๋  ์ˆ˜ ์žˆ๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Œ.
  • ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ์€, ํ•ด๋‹น ์ˆ˜์น˜๋“ค๋กœ ์ค„ ์ˆ˜ ์žˆ๋Š” ์ œ์•ฝ์€ imprecise/not strict ์ •ํ™•ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค๋ผ๊ณ  ๋ช…์‹œ๋˜์–ด ์žˆ๊ณ ,
  • ๋˜ํ•œ ์ €์žฅ ์‹œ cost๋ฅผ ์ง€์ • ํ•ด ์ฃผ์ง€ ์•Š์œผ๋ฉด totalCostLimit๋ฅผ ์ง€์ •ํ–ˆ๋”๋ผ๋„ ๊ธฐ๋ณธ cost๋Š” 0์ด๊ธฐ ๋•Œ๋ฌธ์— ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Œ.
open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
  private var _totalCost = 0
  open var totalCostLimit: Int = 0 // limits are imprecise/not strict
  open var countLimit: Int = 0 // limits are imprecise/not strict

  open func setObject(_ obj: ObjectType, forKey key: KeyType) {
    setObject(obj, forKey: key, cost: 0)
  }
  
  open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
    let g = max(g, 0)
...

์ตœ์ข… ๊ตฌํ˜„

  • countLimit ์บ์‹ฑ ๊ฐฏ์ˆ˜ ์ œํ•œ์„ ์ฃผ์—ˆ์Œ
  • totalCostLimit๋ฅผ ์ง€์ •ํ•˜๊ณ , ์ €์žฅ ์‹œ ๊ฐ ์ด๋ฏธ์ง€์˜ ๋ฐ”์ดํŠธ ์šฉ๋Ÿ‰์— ๋”ฐ๋ผ ์ฐจ๋“ฑ์ ์ธ CostLimit์„ ์คŒ์œผ๋กœ์„œ, ์šฉ๋Ÿ‰ ์ œํ•œ์„ ๊ฐ„์ ‘์ ์œผ๋กœ ์ค„ ์ˆ˜ ์žˆ์Œ.
final class ImageCacheManager {
  private let cache = NSCache<NSString, UIImage>()
  
  // ์ด 100๊ฐœ ๊นŒ์ง€๋งŒ ์บ์‹ฑํ•จ
  var countLimit = 100 {
    didSet { cache.countLimit = countLimit }
  }
  // ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ์‹œ์˜ ์šฉ๋Ÿ‰ ์ œ์•ฝ (๊ธฐ๋ณธ๊ฐ’: 200๋ฉ”๊ฐ€๋ฐ”์ดํŠธ)
  var byteLimit: Int = Constants.initialByteLimit {
    didSet { cache.totalCostLimit = byteLimit }
  }
  var megaByteLimit: Int {
    get { byteLimit / Constants.megaByteUnit }
    set { byteLimit = newValue * Constants.megaByteUnit }
  }
  
  func get(for id: UUID) -> UIImage? {
    cache.object(forKey: id.uuidString as NSString)
  }
  
  func store(_ value: UIImage, for id: UUID) {
    let bytesOfImage = value.pngData()?.count ?? 0
    cache.setObject(value, forKey: id.uuidString as NSString, cost: bytesOfImage)
  }
...
profile
iOS developer

0๊ฐœ์˜ ๋Œ“๊ธ€