Подтвердить что ты не робот

Сохранить содержимое NSCache на диск

Я пишу приложение, которое должно хранить кеш в памяти кучи объектов, но это не выходит из-под контроля, поэтому я планирую использовать NSCache для его хранения. Похоже, что он позаботится о чистке и для меня, что фантастично.

Я также хотел бы сохранить кеш между запусками, поэтому мне нужно записать данные кэша на диск. Есть ли простой способ сохранить содержимое NSCache в plist или что-то еще? Существуют ли, возможно, лучшие способы достижения этого, используя что-то другое, чем NSCache?

Это приложение будет на iPhone, поэтому мне нужны только классы, доступные в iOS 4+, а не только OS X.

Спасибо!

4b9b3361

Ответ 1

Я пишу приложение, которое нужно сохранить в кеше в памяти пучка объектов, но это не выходит из так что я планирую использовать NSCache чтобы сохранить все это. Похоже, это будет позаботьтесь о чистке и для меня, который является фантастическим. Я также хотел бы сохранить кеш между запусками, поэтому мне нужно написать данные кэша на диск. Есть простой способ сохранить содержимое NSCache к plist или что-то еще? Здесь возможно, лучшие способы достижения этого используя что-то, кроме NSCache?

Вы в значительной степени просто описали, что делает CoreData; постоянство графиков объектов с возможностями очистки и обрезки.

NSCache не предназначен для обеспечения стойкости.

Учитывая, что вы предложили сохранить формат plist, использование Core Data вместо этого отличается от концептуальной разницы.

Ответ 2

используйте TMCache (https://github.com/tumblr/TMCache). Это похоже на NSCache, но с сохранением и очисткой кэша. Написано командой Tumblr.

Ответ 3

Иногда может быть удобнее не иметь дело с Core Data и просто сохранять содержимое кеша на диск. Вы можете достичь этого с помощью NSKeyedArchiver и UserDefaults (я использую Swift 3.0.2 в примерах кода ниже).

Сначала позвольте отвлечься от NSCache и представьте, что мы хотим сохранить любой кеш, соответствующий протоколу:

protocol Cache {
    associatedtype Key: Hashable
    associatedtype Value

    var keys: Set<Key> { get }

    func set(value: Value, forKey key: Key)

    func value(forKey key: Key) -> Value?

    func removeValue(forKey key: Key)
}

extension Cache {
    subscript(index: Key) -> Value? {
        get {
            return value(forKey: index)
        }
        set {
            if let v = newValue {
                set(value: v, forKey: index)
            } else {
                removeValue(forKey: index)
            }
        }
    }
}

Key связанный тип должен быть Hashable, поскольку это требование для параметра типа Set.

Далее мы должны реализовать NSCoding для Cache с помощью вспомогательного класса CacheCoding:

private let keysKey = "keys"
private let keyPrefix = "_"

class CacheCoding<C: Cache, CB: Builder>: NSObject, NSCoding
where
    C.Key: CustomStringConvertible & ExpressibleByStringLiteral,
    C.Key.StringLiteralType == String,
    C.Value: NSCodingConvertible,
    C.Value.Coding: ValueProvider,
    C.Value.Coding.Value == C.Value,
    CB.Value == C {

    let cache: C

    init(cache: C) {
        self.cache = cache
    }

    required convenience init?(coder decoder: NSCoder) {
        if let keys = decoder.decodeObject(forKey: keysKey) as? [String] {
            var cache = CB().build()
            for key in keys {
                if let coding = decoder.decodeObject(forKey: keyPrefix + (key as String)) as? C.Value.Coding {
                    cache[C.Key(stringLiteral: key)] = coding.value
                }
            }
            self.init(cache: cache)
        } else {
            return nil
        }
    }

    func encode(with coder: NSCoder) {
        for key in cache.keys {
            if let value = cache[key] {
                coder.encode(value.coding, forKey: keyPrefix + String(describing: key))
            }
        }
        coder.encode(cache.keys.map({ String(describing: $0) }), forKey: keysKey)
    }
}

Здесь:

  • C - тип, соответствующий Cache.
  • C.Key связанный тип должен соответствовать:
    • Swift CustomStringConvertible для преобразования в String, потому что метод NSCoder.encode(forKey:) принимает String для ключевого параметра.
    • Swift ExpressibleByStringLiteral для преобразования [String] обратно в Set<Key>
  • Нам нужно преобразовать Set<Key> в [String] и сохранить его в NSCoder с помощью keys, потому что нет способа извлечь во время декодирования из NSCoder ключей, которые использовались при кодировании объектов. Но может возникнуть ситуация, когда у нас также есть запись в кеше с ключом keys, поэтому для различения ключей кеша из специального ключа keys мы используем кеширование ключей с _.
  • C.Value связанный тип должен соответствовать протоколу NSCodingConvertible, чтобы получить экземпляры NSCoding из значений, хранящихся в кеше:

    protocol NSCodingConvertible {
        associatedtype Coding: NSCoding
    
        var coding: Coding { get }
    }
    
  • Value.Coding должен соответствовать протоколу ValueProvider, потому что вам нужно вернуть значения из экземпляров NSCoding:

    protocol ValueProvider {
        associatedtype Value
    
        var value: Value { get }
    }
    
  • C.Value.Coding.Value и C.Value должны быть эквивалентными, потому что значение, из которого мы получаем экземпляр NSCoding, когда кодирование должно иметь тот же тип, что и значение, которое мы получаем от NSCoding при декодировании.

  • CB - это тип, который соответствует протоколу Builder и помогает создать экземпляр кеша типа C:

    protocol Builder {
        associatedtype Value
    
        init()
    
        func build() -> Value
    }
    

Далее сделаем NSCache совместимым с протоколом Cache. Здесь у нас есть проблема. NSCache имеет ту же проблему, что и NSCoder - это не обеспечивает способ извлечения ключей для сохраненных объектов. Существует три способа обхода этого пути:

  • Оберните NSCache специальным типом, который будет содержать клавиши Set и использовать его везде, а не NSCache:

    class BetterCache<K: AnyObject & Hashable, V: AnyObject>: Cache {
        private let nsCache = NSCache<K, V>()
    
        private(set) var keys = Set<K>()
    
        func set(value: V, forKey key: K) {
            keys.insert(key)
            nsCache.setObject(value, forKey: key)
        }
    
        func value(forKey key: K) -> V? {
            let value = nsCache.object(forKey: key)
            if value == nil {
                keys.remove(key)
            }
            return value
        }
    
        func removeValue(forKey key: K) {
            return nsCache.removeObject(forKey: key)
        }
    }
    
  • Если вам все равно нужно передать NSCache где-нибудь, тогда вы можете попробовать расширить его в Objective-C, делая то же самое, что и я, с помощью BetterCache.

  • Используйте другую реализацию кэша.

Теперь у вас есть тип, который соответствует протоколу Cache, и вы готовы его использовать.

Пусть определите тип Book, какие экземпляры мы будем хранить в кеше и NSCoding для этого типа:

class Book {
    let title: String

    init(title: String) {
        self.title = title
    }
}

class BookCoding: NSObject, NSCoding, ValueProvider {
    let value: Book

    required init(value: Book) {
        self.value = value
    }

    required convenience init?(coder decoder: NSCoder) {
        guard let title = decoder.decodeObject(forKey: "title") as? String else {
            return nil
        }
        print("My Favorite Book")
        self.init(value: Book(title: title))
    }

    func encode(with coder: NSCoder) {
        coder.encode(value.title, forKey: "title")
    }
}

extension Book: NSCodingConvertible {
    var coding: BookCoding {
        return BookCoding(value: self)
    }
}

Некоторые типы для лучшей читаемости:

typealias BookCache = BetterCache<StringKey, Book>
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder>

И построитель, который поможет нам создать экземпляр экземпляра Cache:

class BookCacheBuilder: Builder {
    required init() {
    }

    func build() -> BookCache {
        return BookCache()
    }
}

Проверьте это:

let cacheKey = "Cache"
let bookKey: StringKey = "My Favorite Book"

func test() {
    var cache = BookCache()
    cache[bookKey] = Book(title: "Lord of the Rings")
    let userDefaults = UserDefaults()

    let data = NSKeyedArchiver.archivedData(withRootObject: BookCacheCoding(cache: cache))
    userDefaults.set(data, forKey: cacheKey)
    userDefaults.synchronize()

    if let data = userDefaults.data(forKey: cacheKey),
        let cache = (NSKeyedUnarchiver.unarchiveObject(with: data) as? BookCacheCoding)?.cache,
        let book = cache.value(forKey: bookKey) {
        print(book.title)
    }
}

Ответ 4

Вам следует попробовать AwesomeCache. Его основные возможности:

  • написан в Swift
  • использует кеширование на диске
  • поддерживаемый NSCache для максимальной производительности и поддержки для истечения срока действия отдельных объектов

Пример:

do {
    let cache = try Cache<NSString>(name: "awesomeCache")

    cache["name"] = "Alex"
    let name = cache["name"]
    cache["name"] = nil
} catch _ {
    print("Something went wrong :(")
}