Muzahidul
2/11/2019 - 12:47 PM

SingleCache

Basic idea:

  • Get value of a give context if exist at current version.
  • If the value locally not found then fetch from repository and save as current version.
  • Resolver gives the options which version we want. Old one or new one or both.

struct Version<ContextType, ValueType> {
    let context: ContextType
    let value: ValueType
}

class SingleCache<ContextType, ValueType> {
    
    typealias CacheVersion = Version<ContextType, ValueType>
    typealias CompletionHandler = (_ oldValue: CacheVersion?, _ newValue: CacheVersion?) -> ()
    typealias FetchCompletionHandler = (ValueType?)->()
    
    var curVersion: CacheVersion?
    let fetcher: (_ context: ContextType, _ completion: @escaping FetchCompletionHandler) -> ()
    let equalityChecker: (ContextType, ContextType) -> Bool
    let resolver: (_ oldVersion: CacheVersion?, _ newVersion: CacheVersion?) -> CacheVersion?
    
    init(fetcher: @escaping (_ context: ContextType, _ completion: @escaping FetchCompletionHandler) -> (),
         equalityChecker: @escaping (ContextType, ContextType) -> Bool,
         resolver: @escaping (_ oldVersion: CacheVersion?, _ newVersion: CacheVersion?) -> CacheVersion? = { $1 }) {
        self.fetcher = fetcher
        self.equalityChecker = equalityChecker
        self.resolver = resolver
    }
    
    private func fetchAndReturn(for context: ContextType, completion: @escaping CompletionHandler) {
        fetcher(context) { [weak self] newValue in
            guard let `self` = self else { completion(nil, nil); return }
            
            let oldVersion = self.curVersion
            let newVersion: CacheVersion?
            
            if let value = newValue {
                newVersion = Version(context: context, value: value)
            } else {
                newVersion = nil
            }
            
            self.curVersion = self.resolver(oldVersion, newVersion)
            completion(oldVersion, newVersion)
        }
    }
    
    func get(for context: ContextType, completion: @escaping CompletionHandler) {
        if let version = curVersion, equalityChecker(context, version.context) {
            completion(version, version)
        } else {
            fetchAndReturn(for: context, completion: completion)
        }
    }
}

/// Testing
let dump: [String: [String]] = [
    "ios": ["Ashik", "Dulal", "Muzahid"],
    "android": ["Sourav"]
]
let cache = SingleCache<String, [String]>(
    fetcher: { (context, completion) in
        print("fetching ...")
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
            completion(dump[context])
        })
    },
    equalityChecker: { (oldStr, newStr) -> Bool in
        return oldStr == newStr
})

cache.get(for: "ios") {
    print($1?.value)
    
    cache.get(for: "ios") {
        print($1?.value)
        
        cache.get(for: "android") {
            print($0?.value, $1?.value)
            
            print(cache.curVersion)
            
            cache.curVersion = Version(context: "backend", value: ["rinku", "masum"])
            cache.get(for: "backend") {
                print($0, $1)
            }
        }
    }
}