1

I have an NSDictionary which I cached. I need to implement a time-based setObject with timestamp. NSCache Class doesn't have a setExpiry. Any help would be appreciated.

This is the extension I have so far:

import Foundation

extension NSCache {

class var sharedInstance : NSCache {
    struct Static {
        static let instance : NSCache = NSCache()
    }
    return Static.instance
 }
}

I found NSCache Extension at http://nshipster.com/nscache/ . Any easy way to implement with an expiry timestamp?

extension NSCache {
subscript(key: AnyObject) -> AnyObject? {
    get {
        return objectForKey(key)
    }
    set {
        if let value: AnyObject = newValue {
            setObject(value, forKey: key)
        } else {
            removeObjectForKey(key)
        }
    }
 }
}
Tal Zion
  • 6,308
  • 3
  • 50
  • 73
  • 1
    A little off-topic, but if you're trying to create a singleton by what you have in the extension right now, you should check out [this article] (http://krakendev.io/blog/the-right-way-to-write-a-singleton). You can create a singleton in one line, like this: `static let sharedInstace = NSCache()`. – Dominik Hadl Nov 08 '15 at 14:42
  • I have a JSON, which I need to cache it with expiry timestamp of 1HR. Do you think my approach is sufficient? Thanks.. – Tal Zion Nov 08 '15 at 14:45

2 Answers2

4

Here is the basic approach.

PS: I haven't tested this code and I wrote it in the text editor. It may require some tweaks depending on your requirements :)

import Foundation

protocol Cacheable: class {
    var expiresAt : NSDate { get set }
}

class CacheableItem : Cacheable {
    var expiresAt = NSDate()
}

extension NSCache {

    subscript(key: AnyObject) -> Cacheable? {
        get {
            if let obj = objectForKey(key) as? Cacheable {
                 var now = NSDate();
                if  now.isGreaterThanDate(obj.expiresAt) {
                    removeObjectForKey(key)
                }
            }

            return objectForKey(key) as? Cacheable
        }
        set {
            if let value = newValue {
                setObject(value, forKey: key)
            } else {
                removeObjectForKey(key)
            }
        }
    }
}

extension NSDate
{
    func isGreaterThanDate(dateToCompare : NSDate) -> Bool
    {
        var isGreater = false

        if self.compare(dateToCompare) == NSComparisonResult.OrderedDescending {
            isGreater = true
        }

        return isGreater
    }
}

Based on this Stack Overflow answer.

Community
  • 1
  • 1
ProblemSlover
  • 2,538
  • 2
  • 23
  • 33
  • Great thanks! Will test it and see how to works.. Cheers :) – Tal Zion Nov 08 '15 at 14:35
  • @TalZion I just made an edit to the answer, so the code actually compiles. – Dominik Hadl Nov 08 '15 at 14:37
  • @TalZion I've edit my answer. correct logical error in comparing dates. Hope it works for you :) – ProblemSlover Nov 08 '15 at 14:54
  • Thanks very much for your help :)!. I tried to test it, how would I work with it? When I setObject, how would I add the expiry date timestamp? – Tal Zion Nov 08 '15 at 14:55
  • You can assign the expiration date before adding to the cache like var myCacheableItem = new CacheableItem() => myCacheableItem.expiresAt = myCacheableItem.expiresAt.dateByAddingTimeInterval(3600.0) => cache.set(value: myCacheableItem, key: "image.png") – ProblemSlover Nov 08 '15 at 15:18
2

You can also use a timer to empty the queue:

private let ExpiringCacheObjectKey = "..."
private let ExpiringCacheDefaultTimeout: NSTimeInterval = 60

class ExpiringCache : NSCache {

    /// Add item to queue and manually set timeout
    ///
    /// - parameter obj: Object to be saved
    /// - parameter key: Key of object to be saved
    /// - parameter timeout: In how many seconds should the item be removed

    func setObject(obj: AnyObject, forKey key: AnyObject, timeout: NSTimeInterval) {
        super.setObject(obj, forKey: key)
        NSTimer.scheduledTimerWithTimeInterval(timeout, target: self, selector: "timerExpires:", userInfo: [ExpiringCacheObjectKey : key], repeats: false)
    }

    // Override default `setObject` to use some default timeout interval

    override func setObject(obj: AnyObject, forKey key: AnyObject) {
        setObject(obj, forKey: key, timeout: ExpiringCacheDefaultTimeout)
    }

    // method to remove item from cache

    func timerExpires(timer: NSTimer) {
        removeObjectForKey(timer.userInfo![ExpiringCacheObjectKey] as! String)
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hey @Rob, I get a potential error, can you please help? [Screenshot] (https://www.dropbox.com/s/d9wocj6dzex380j/Screenshot%202015-11-08%2016.09.24.png?dl=0) This is what I think should be the syntax removeObjectForKey(timer.userInfo![ExpiringCacheObjectKey]) ? – Tal Zion Nov 08 '15 at 16:12
  • 2
    Do not add a timer for every key in your cache, you should add an expiration date to the key and check it when you retrieve the object. – Ruud Visser Apr 29 '16 at 20:03
  • But if you don't retrieve the object, then the cache won't be purged like the OP wanted. You could, though, have a single repeating timer that iterates through all of the objects in the cache and determine which, if any, need to be purged. Also, if you're doing something like that, I'd pursue a generic that captures the "last retrieved" time rather than embedding it in the key. Frankly, I think the entire time-based purging idea is over-engineered and I'd just stick with `countLimit` and `totalCostLimit`. – Rob Apr 29 '16 at 20:17