Реализация срока действия кэша с использованием NSCache

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

Стоит ли смотреть на временную метку при доступе к кешу и делать ее недействительной? Должен ли кеш автоматически аннулировать себя, используя таймер с фиксированным интервалом?

Ответы на вопрос(2)

Решение Вопроса

Should the cache automatically invalidate itself by using a fixed interval timer?

Это было бы плохим решением, потому что вы могли бы добавить что-то за секунды до срабатывания таймера. Срок действия должен быть основан на возрасте конкретного предмета. (Конечно, было бы возможноconditionally сделать недействительными элементы с помощью таймера; см. комментарии к этому ответу.)

Вот пример. Я думал о подклассеNSCache, но решил, что использовать композицию проще.

Interface
//
//  ExpiringCache.h
//
//  Created by Aaron Brager on 10/23/13.

#import <Foundation/Foundation.h>

@protocol ExpiringCacheItem <NSObject>

@property (nonatomic, strong) NSDate *expiringCacheItemDate;

@end

@interface ExpiringCache : NSObject

@property (nonatomic, strong) NSCache *cache;
@property (nonatomic, assign) NSTimeInterval expiryTimeInterval;

- (id)objectForKey:(id)key;
- (void)setObject:(NSObject <ExpiringCacheItem> *)obj forKey:(id)key;

@end
Implementation
//
//  ExpiringCache.m
//
//  Created by Aaron Brager on 10/23/13.

#import "ExpiringCache.h"

@implementation ExpiringCache

- (instancetype) init {
    self = [super init];

    if (self) {
        self.cache = [[NSCache alloc] init];
        self.expiryTimeInterval = 3600;  // default 1 hour
    }

    return self;
}

- (id)objectForKey:(id)key {
    @try {
        NSObject <ExpiringCacheItem> *object = [self.cache objectForKey:key];

        if (object) {
            NSTimeInterval timeSinceCache = fabs([object.expiringCacheItemDate timeIntervalSinceNow]);
            if (timeSinceCache > self.expiryTimeInterval) {
                [self.cache removeObjectForKey:key];
                return nil;
            }
        }

        return object;
    }

    @catch (NSException *exception) {
        return nil;
    }
}

- (void)setObject:(NSObject <ExpiringCacheItem> *)obj forKey:(id)key {
    obj.expiringCacheItemDate = [NSDate date];
    [self.cache setObject:obj forKey:key];
}

@end
Notes Assumes you're using ARC. I didn't implement setObject:forKey:cost: since the NSCache documentation all but tells you not to use it. I use a @try/@catch block, since technically you could add an object to the cache that doesn't respond to expiringCacheItemDate. I thought about using respondsToSelector: for this, but you could add an object that doesn't respond to that too, since NSCache takes id and not NSObject. Sample code
#import "ExpiringCache.h"

@property (nonatomic, strong) ExpiringCache *accountsCache;

- (void) doSomething {
    if (!self.accountsCache) {
        self.accountsCache = [[ExpiringCache alloc] init];
        self.accountsCache.expiryTimeInterval = 7200; // 2 hours
    }

    // add an object to the cache
    [self.accountsCache setObject:newObj forKey:@"some key"];

    // get an object
    NSObject *cachedObj = [self.accountsCache objectForKey:@"some key"];
    if (!cachedObj) {
        // create a new one, this one is expired or we've never gotten it
    }
}
 17 мая 2016 г., 10:06
это не похоже на политику выселения. Ресурсы выселяются, если срок их действия истек, только при попытках доступа. Экземпляры могут жить вечно, даже если срок их действия истек.
 17 мая 2016 г., 16:25
@RaduSimionescu Правда. Вы можете добавить таймер для периодического удаления объектов с истекшим сроком, если это необходимо. (Или один таймер на каждое время истечения, если вы хотите быть еще строже.) Однако, так какNSCache будет изгонять объекты, когда существует нехватка памяти, возможно, будет нормально оставить их на неопределенное время.

Другое решение состоит в том, чтобы установить время истечения при установке объекта и сравнить со временем истечения для объекта.

Например:

Usage

#import "PTCache.h"

NSInteger const PROFILE_CACHE_EXPIRE = 3600;

- (void) cacheSomething: (id) obj
                 forKey: (NSString*) key
{
     [PTCache sharedInstance] setObject: obj
                                 forKey: key
                                 expire: PROFILE_CACHE_EXPIRE
     ];

}

Interface

#import <Foundation/Foundation.h>

@interface PTCache : NSCache

+ (PTCache *) sharedInstance;

- (void) setObject: (id) obj
            forKey: (NSString *) key
            expire: (NSInteger) seconds;

- (id) objectForKey: (NSString *) key;

@end

Implementation

#import "PTCache.h"

@implementation PTCache
{
    NSMutableArray * expireKeys;
}


+ (PTCache *) sharedInstance
{
    static dispatch_once_t predicate = 0;
    __strong static id sharedObject = nil;
    dispatch_once(&predicate, ^{
        sharedObject = [[self alloc] init];
    });

    return sharedObject;
}

- (id) init
{
    if ( self = [super init])
    {
        expireKeys = [[NSMutableArray alloc] init];
    }

    return self;
}

/**
 * Get Object
 *
 * @param NSString * key
 * @return id obj
 *
 **/

- (id) objectForKey: (NSString *) key
{
    id obj = [super objectForKey: key];

    if( obj == nil)
    {
        return nil;
    }

    BOOL expired = [self hasExpired: key];

    if( expired)
    {
        [super removeObjectForKey: key];
        return nil;
    }

    return obj;
}

/**
 * Set Object
 *
 * @param id obj
 * @param NSString * key
 * @param NSInteger seconds
 *
 */
- (void) setObject: (id) obj
            forKey: (NSString *) key
            expire: (NSInteger) seconds
{
    [super setObject: obj forKey: key];

    [self updateExpireKey: key expire: seconds];
}


/**
 * Update Expire Time for Key and Seconds to Expire
 *
 * @param NSString * key
 * @param NSInteger seconds
 *
 **/
- (void) updateExpireKey: (NSString *) key
                  expire: (NSInteger) seconds
    __block NSInteger index = -1;

    [expireKeys enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
        if([obj[@"key"] isEqualToString: key])
        {
            index = idx;
            *stop = YES;
            return;
        }
    }];

    NSNumber * expires = [NSNumber numberWithFloat: ([[NSDate date] timeIntervalSince1970] + seconds)];

    if( index > -1)
    {
        [[expireKeys objectAtIndex: index] setObject: expires forKey: key];
    }
    else
    {
        NSMutableDictionary * element = [[NSMutableDictionary alloc] init];
        [element setObject: key forKey: @"key"];
        [element setObject: expires forKey: @"expire"];

        [expireKeys addObject: element];
    }

}

/**
 * Has Expired for Key
 *
 **/
- (BOOL) hasExpired: (NSString *) key
{
    NSNumber * expiredObj = [self getExpireTime: key];

    NSDate * current = [NSDate date];

    NSDate * expireDate = [NSDate dateWithTimeIntervalSince1970: [expiredObj doubleValue]];

    return [current compare: expireDate] == NSOrderedDescending;
}

/**
 * Get Expire Time
 *
 * @param NSString * key
 * @param NSInteger
 *
 **/

- (NSNumber *) getExpireTime: (NSString *) key
{
    __block NSNumber * expire = nil;

    [expireKeys enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
        if([obj[@"key"] isEqualToString: key])
        {
            expire = obj[@"expire"];
            *stop = YES;
            return;
        }
    }];

    return expire;
}



@end

Ваш ответ на вопрос