10

我正在使用 NSCache 在我的应用程序中实现缓存。我想为其添加过期时间,以便它会在一段时间后获取新数据。有哪些选择,最好的方法是什么?

我应该在访问缓存时查看时间戳并使其无效吗?缓存是否应该通过使用固定间隔计时器自动使自己失效?

4

2 回答 2

10

缓存是否应该通过使用固定间隔计时器自动使自己失效?

这将是一个糟糕的解决方案,因为您可能会在计时器触发前几秒钟添加一些内容。到期时间应基于特定项目的年龄。(当然,可以使用计时器有条件地使项目无效;请参阅对此答案的评论。)

这是一个例子。我考虑过子类化NSCache,但决定使用组合更简单。

界面

//
//  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

执行

//
//  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

笔记

  • 假设您使用的是 ARC。
  • 我没有实现setObject:forKey:cost:,因为 NSCache 文档都告诉你不要使用它
  • 我使用 @try/@catch 块,因为从技术上讲,您可以将对象添加到不响应expiringCacheItemDate. 我考虑过使用respondsToSelector:它,但你可以添加一个不响应的对象,因为 NSCache 需要id而不是NSObject.

示例代码

#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
    }
}
于 2013-10-23T22:49:25.797 回答
1

另一种解决方案是在设置对象时设置过期时间,并与对象的过期时间进行比较。

例如:

用法

#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
     ];

}

界面

#import <Foundation/Foundation.h>

@interface PTCache : NSCache

+ (PTCache *) sharedInstance;

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

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

@end

执行

#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
于 2016-01-07T05:36:12.383 回答