2010-12-27 22 views
12

Piszę aplikację, która musi przechowywać pamięć podręczną w pamięci wielu obiektów, ale to nie wymyka się spod kontroli, więc planuję użyć NSCache do przechowywania wszystkich . Wygląda na to, że zadba o oczyszczanie i takie dla mnie, co jest fantastyczne.Zapisz zawartość NSCache na dysku

Chciałbym również zachować pamięć podręczną między uruchomieniami, więc muszę zapisać dane z pamięci podręcznej na dysku. Czy istnieje prosty sposób na zapisanie zawartości NSCache do pliku plist lub czegoś podobnego? Czy są może lepsze sposoby osiągnięcia tego przy użyciu czegoś innego niż NSCache?

Ta aplikacja będzie na iPhone, więc będę potrzebował tylko klasy, które są dostępne w iOS 4+ i nie tylko OS X.

dzięki!

Odpowiedz

13

Piszę aplikację, która musi zachować pamięci podręcznej w pamięci kiści obiektów, ale to nie wyjść z strony więc ja zamierzam użyciu NSCache do przechowywania to wszystko. Wygląda na to, że będzie dbać o oczyszczanie i takie dla mnie, co jest fantastyczne. Chciałbym również utrzymywać pamięć podręczną między wersjami, więc muszę zapisać danych z pamięci podręcznej na dysk. Czy istnieje prosty sposób na zapisanie zawartości NSCache do pliku plist lub czegoś podobnego? Czy są jakieś lepsze sposoby osiągnięcia tego przy użyciu czegoś innego niż NSCache?

Opisałeś dokładnie, co robi CoreData; Trwałość wykresów obiektów z funkcjami oczyszczania i przycinania.

NSCache nie jest zaprojektowany, aby pomóc w utrzymaniu.

Biorąc pod uwagę, że zasugerowałeś, aby uporać się z formatem plist, użycie Core Data zamiast tego nie ma aż tak dużej różnicy koncepcyjnej.

+0

Używam Core Data na prawie każdej stworzonej przeze mnie bazie danych, ale wydaje mi się, że to nie jest najlepsze dopasowanie. Używam pamięci podręcznych do przechowywania wyników API, utrzymywania zippy aplikacji podczas uruchamiania i ładowania rzeczy, które zostały już załadowane. Martwię się, że główna baza danych wymyka się spod kontroli i rośnie. Po prostu nie wydaje się, że dane podstawowe są najlepiej wykorzystywane do buforowania "tymczasowych" danych. Wytrwałość, której potrzebuję, jest w zasadzie funkcją drugorzędną dla potrzebnej mi funkcji buforowania w pamięci, dlatego pochylałem się do NSCache. –

+0

Yah - Widzę twoją zagadkę. NSCoding nie jest * trudny do wdrożenia - zawsze można przejść tą ścieżką. Wtedy pojawia się pytanie, kiedy napisać/zaktualizować trwałą wersję pamięci podręcznej. Z mojego doświadczenia wynika, że ​​łatwo jest udać się na ścieżkę, która kończy się na ponownym wynalezieniu koła uporczywości ze wszystkimi jego złożonościami. Oczywiście najlepiej działającą aplikacją jest ta, która jest wysyłana jako pierwsza. ;) – bbum

+0

@CoryImdieke, mamy teraz do czynienia z tą samą sytuacją i zamierzam użyć NSCache. Zastanawiasz się, które rozwiązanie wybrałeś? – Koolala

9

użyć TMCache (https://github.com/tumblr/TMCache). To jak NSCache, ale z wytrwałością i czyszczeniem pamięci podręcznej. Napisane przez zespół Tumblr.

+1

Niestety, nie jest już aktywnie konserwowane:/ – manicaesar

+0

najlepszą odpowiedzią jest teraz użycie albo CoreData albo Realm. Coredata jest najbardziej standardowa, królestwo łatwiejsza do nauczenia. –

0

Czasami wygodniej jest nie zajmować się Core Data i tylko zapisać zawartość pamięci podręcznej na dysku. Możesz to osiągnąć dzięki NSKeyedArchiver i UserDefaults (używam Swift 3.0.2 w przykładach kodu poniżej).

Zacznijmy abstrahować od NSCache i wyobrazić sobie, że chcemy, aby móc utrzymywać żadnych cache, który jest zgodny z protokołem:

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 związane typ musi być Hashable bo to wymóg Set typu parametru.

Następny musimy wdrożyć NSCoding dla Cache używając klasy pomocnika 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) 
    } 
} 

tutaj:

  • C jest typ, który jest zgodny z Cache.
  • C.Key powiązanych rodzajów musi odpowiadać:
    • Swift protokołu CustomStringConvertible być wymienialne na String ponieważ NSCoder.encode(forKey:) sposób przyjmuje String dla kluczowych parametrów.
    • Swift protokół ExpressibleByStringLiteral przekonwertować [String] z powrotem do Set<Key>
  • Musimy przekształcić Set<Key> do [String] i przechowywać go do NSCoder z keys klucza, ponieważ nie ma sposobu, aby wyodrębnić podczas dekodowania z NSCoder klucze, które były wykorzystywane podczas kodowania obiekty. Ale może się zdarzyć, że mamy także wpis w pamięci podręcznej z kluczem keys, aby odróżnić klucze cache od specjalnego klucza keys, przedrostek kluczy pamięci podręcznej z numerem _.
  • C.Value związane typ musi być zgodne z protokołem NSCodingConvertible dostać NSCoding przypadkach od wartości przechowywane w pamięci podręcznej:

    protocol NSCodingConvertible { 
        associatedtype Coding: NSCoding 
    
        var coding: Coding { get } 
    } 
    
  • Value.Coding musi zgodne z protokołem ValueProvider bo trzeba uzyskać wartości z powrotem NSCoding przypadkach:

    protocol ValueProvider { 
        associatedtype Value 
    
        var value: Value { get } 
    } 
    
  • C.Value.Coding.Value i C.Value muszą być równoważne becau se wartość, z której otrzymujemy instancję NSCoding, gdy kodowanie musi mieć ten sam typ co wartość, którą otrzymujemy z NSCoding podczas dekodowania.

  • CB to typ, który jest zgodny z protokołem Builder i pomaga stworzyć instancję podręcznej C typu:

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

Następny zróbmy NSCache zgodne z protokołem Cache. Tutaj mamy problem. NSCache ma ten sam problem co NSCoder - nie zapewnia sposobu na wyodrębnienie kluczy dla przechowywanych obiektów. Istnieją trzy sposoby, aby obejść ten:

  1. Wrap NSCache z niestandardowego typu, który będzie posiadał klucze Set i używać go wszędzie zamiast 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) 
        } 
    } 
    
  2. Jeśli trzeba jeszcze przejść NSCache gdzieś potem możesz spróbować rozszerzyć go w Objective-C, robiąc to samo co powyżej z BetterCache.

  3. Użyj innej implementacji pamięci podręcznej.

Teraz masz typ, który jest zgodny z protokołem Cache i jesteś gotowy, aby go używać.

Zdefiniujmy typ Book który przypadki będziemy przechowywać w pamięci podręcznej i NSCoding dla tego typu:

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) 
    } 
} 

Niektóre typealiases dla lepszej czytelności:

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

i budowniczym, które pomogą nam instancję Cache instancja:

class BookCacheBuilder: Builder { 
    required init() { 
    } 

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

Przetestuj:

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) 
    } 
} 
0

Powinieneś spróbować AwesomeCache. Jego główne cechy:

  • napisane Swift
  • zużywa na dysku buforowania
  • poparte NSCache dla maksymalnej wydajności i wsparcia dla upływie pojedynczych obiektów

Przykład:

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

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