How to use data from main.m in a subclass - objective-c

I am doing homework from the Big Nerd Ranch Guide book. In the problem, I have two arrays in main.m. One is from a subclass and the other from a superclass. BNRItem is the superclass. BNRContainer is the subclass. In the implementation files I have description string which says what the NSLog should say. I need to take the data from the superclass used in main.m's array and use it in the subclass so that the subclass array can work. is there an easy answer?
In main.m I have the following:
// main.m
// RandomItems
#import <Foundation/Foundation.h>
#import "BNRItem.h"
#import "BNRContainer.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSMutableArray *items = [[NSMutableArray alloc] init];
for (int i = 0; i < 3; i++) {
BNRItem *item = [BNRItem randomItem];
[items addObject:item];
}
for (BNRItem *item in items) {
NSLog(#"%#", item);
}
NSMutableArray *containers = [[NSMutableArray alloc] init];
for (int i = 0; i < 3; i++) {
BNRContainer *container = [BNRContainer randomContainer];
[containers addObject:container];
}
for (BNRContainer *container in containers) {
NSLog(#"%#", container);
}
items = nil;
containers = nil;
}
return 0;
}
In BNRItem.h:
// BNRItem.h
// RandomItems
//
// Created by Meghan on 3/19/14.
// Copyright (c) 2014 Meghan. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface BNRItem : NSObject
{
NSString *_itemName;
NSString *_serialNumber;
int _valueInDollars;
NSDate *_dateCreated;
}
+ (instancetype)randomItem;
//Designated initializer for BNRItem
- (instancetype)initWithItemName:(NSString *)name
valueInDollars:(int)value
serialNumber:(NSString *)sNumber;
-(instancetype)initWithItemName:(NSString *)name;
- (NSString *)description;
- (void)setItemName:(NSString *)str;
-( NSString *)itemName;
- (void)setSerialNumber:(NSString *)str;
- (NSString *)serialNumber;
- (void)setValueInDollars:(int)v;
- (int)valueInDollars;
- (NSDate *)dateCreated;
#end
in BNRContainer.h:
// BNRContainer.h
// RandomItems
//
// Created by Meghan on 3/20/14.
// Copyright (c) 2014 Meghan. All rights reserved.
//
#import "BNRItem.h"
#interface BNRContainer : BNRItem
{
NSString *_containerName;
int _containerItemsSum;
int _containerValue;
int _totalContainerValue;
}
- (NSString *)description;
+ (instancetype)randomContainer;
//Designated initializer for BNRContainer
- (instancetype)initWithContainerName:(NSString *)name
containerItemsSum:(int)iSum
containerValue:(int)value
totalContainerValue:(int)tvalue;
- (instancetype)initWithContainerName:(NSString *)name;
- (instancetype)init;
- (void)setContainerName:(NSString *)str;
- (NSString *)containerName;
- (void)setContainerItemsSum:(int)v;
- (int)containerItemsSum;
- (void)setContainerValue:(int)v;
- (int)containerValue;
- (void)setTotalContainerValue:(int)v;
- (int)totalContainerValue;
#end
The variable containerItemsSum in BNRContainer should be the sum of valueInDollars of several items and the value is generated in main.m when it is looped through.
// BNRItem.m
// RandomItems
//
// Created by Meghan on 3/19/14.
// Copyright (c) 2014 Meghan. All rights reserved.
//
#import "BNRItem.h"
#implementation BNRItem
- (instancetype)initWithItemName:(NSString *)name
valueInDollars:(int)value
serialNumber:(NSString *)sNumber
{
self = [super init];
if (self) {
//Give the instance variables initial values
_itemName = name;
_serialNumber = sNumber;
_valueInDollars = value;
//Set dateCreated to the current date and time
_dateCreated = [[NSDate alloc]init];
}
return self;
}
-(instancetype)initWithItemName:(NSString *)name
{
return [self initWithItemName:name
valueInDollars:0
serialNumber:#""];
}
- (instancetype)init
{
return [self initWithItemName:#"Item"];
}
+ (instancetype)randomItem
{
NSArray *randomAdjectiveList = #[#"Fluffy", #"Rusty", #"Shiny"];
NSArray *randomNounList = #[#"Bear", #"Spork", #"Mac"];
NSInteger adjectiveIndex = arc4random() % [randomAdjectiveList count];
NSInteger nounIndex = arc4random() % [randomNounList count];
NSString *randomName = [NSString stringWithFormat:#"%# %#",
randomAdjectiveList[adjectiveIndex],
randomNounList[nounIndex]];
int randomValue = arc4random() % 100;
NSString *randomSerialNumber = [NSString stringWithFormat:#"%c%c%c%c%c",
'0' + arc4random() % 10,
'A' + arc4random() % 26,
'0' + arc4random() % 10,
'A' + arc4random() % 26,
'0' + arc4random() % 10];
BNRItem *newItem = [[self alloc] initWithItemName:randomName
valueInDollars:randomValue
serialNumber:randomSerialNumber];
return newItem;
}
- (void)setItemName:(NSString *)str
{
_itemName = str;
}
- (NSString *)itemName
{
return _itemName;
}
- (void)setSerialNumber:(NSString *)str
{
_serialNumber = str;
}
- (NSString *)serialNumber
{
return _serialNumber;
}
- (void)setValueInDollars:(int)v
{
_valueInDollars = v;
}
- (int)valueInDollars
{
return _valueInDollars;
}
- (NSDate *)dateCreated
{
return _dateCreated;
}
- (NSString *)description
{
NSString *descriptionString =
[[NSString alloc] initWithFormat:#"%# (%#): Worth $%d, recorded on %#",
self.itemName,
self.serialNumber,
self.valueInDollars,
self.dateCreated];
return descriptionString;
}
#end

for (BNRItem *item in items) {
NSLog(#"%#", item);
}
Here, you're passing through each BNRItem that you've created. This is your opportunity to collect information about the items.
Create a local variable to store the total, and add to it each item you read:
int dollarTotal = 0;
for (BNRItem *item in items) {
NSLog(#"%#", item);
dollarTotal += [item valueInDollars];
}
NSLog(#"Total value in dollars is %d",dollarTotal);
You'd then use this value to set the total for the container, although this seems completely backward. A BNRContainer should (though this may be a later point in the tutorial you are doing) be able to derive this total from its own set of contained items, using a loop similar to the one above. I don't see why you are creating three containers in a loop either. The example doesn't make a great deal of sense.

Related

Unexpected result of property `copy` attribute

I've implemented NSCopying for own class and when I use this class as a property with copy attribute I'm expecting that it should use [... copy]/[... copyWithZone:] method. But it returns a reference to the same object.
But if I use copy attribute for NSString it works or when I directly call copy method.
My question why copy attribute is not working for own class that supports NSCopying protocol?
#interface A: NSObject<NSCopying>
#property(nonatomic, strong) NSNumber *num;
#end
#implementation A
- (instancetype) init {
if(self = [super init]) {
_num = #0;
}
return self;
}
- (instancetype)copyWithZone:(NSZone *)zone {
A *newA = [A new];
newA.num = [self.num copyWithZone:zone];
return newA;
}
#end
#interface B: NSObject
#property(nonatomic, copy) NSString *str;
#property(nonatomic, copy) A *objA;
#end
#implementation B
- (instancetype) init {
if(self = [super init]) {
_objA = [A new];
_str = #"0";
}
return self;
}
#end
int main(int argc, const char * argv[]) {
B *objB = [B new];
A *newA1 = objB.objA.copy;
newA1.num = #1;
NSLog(#"%# %#", newA1.num, objB.objA.num);
A *newA = objB.objA;
NSString *newStr = objB.str;
newA.num = #1;
newStr = #"1";
NSLog(#"%# %#", newA.num, objB.objA.num);
NSLog(#"%# %#", newStr, objB.str);
return 0;
}
Output:
1 0
1 1
1 0
Expected output:
1 0
1 0
1 0

Objective-C Coding Style - Categories instead of Marks?

Is there a downside to dividing implementation of Objective-C classes into categories, for code organization reasons. Instead of using the conventional #pragma mark - SectionTitle way?
Below I've included contrasting samples of a portion of a single implementation file.
Category Approach
#implementation Gallery
+ (NSArray*)titles
{
return #[#"St. Augustine", #"Roanoke", #"Jamestown", #"Santa Fe"];
}
#end
#implementation Gallery (Overrides)
- (NSString*)description
{
return self.title;
}
- (NSString*)debugDescription
{
return [NSString stringWithFormat:#"%# - %u items",
self.title, (unsigned int)[self.items count]];
}
#end
#implementation Gallery (Debug)
+ (instancetype) randomGalleryWithTitle:(NSString*)title;
{
Gallery *gallery = [[Gallery alloc] init];
gallery.title = title;
gallery.iconImageName = title;
NSMutableArray *items = [NSMutableArray array];
for (int i = 0; i < 20; ++i) {
if(rand() % 2 == 0) {
ArtObject *randomArtObject = [ArtObject randomArtObject];
randomArtObject.galleryTitle = gallery.title;
[items addObject:randomArtObject];
} else {
Story *randomStory = [Story randomStory];
randomStory.galleryTitle = gallery.title;
[items addObject:randomStory];
}
}
gallery.items = items;
return gallery;
}
#end
Conventional Approach
#implementation Gallery
+ (NSArray*)titles
{
return #[#"St. Augustine", #"Roanoke", #"Jamestown", #"Santa Fe"];
}
#end
#pragma mark - Overrides
- (NSString*)description
{
return self.title;
}
- (NSString*)debugDescription
{
return [NSString stringWithFormat:#"%# - %u items",
self.title, (unsigned int)[self.items count]];
}
#end
#pragma mark - Debug
+ (instancetype) randomGalleryWithTitle:(NSString*)title;
{
Gallery *gallery = [[Gallery alloc] init];
gallery.title = title;
gallery.iconImageName = title;
NSMutableArray *items = [NSMutableArray array];
for (int i = 0; i < 20; ++i) {
if(rand() % 2 == 0) {
ArtObject *randomArtObject = [ArtObject randomArtObject];
randomArtObject.galleryTitle = gallery.title;
[items addObject:randomArtObject];
} else {
Story *randomStory = [Story randomStory];
randomStory.galleryTitle = gallery.title;
[items addObject:randomStory];
}
}
gallery.items = items;
return gallery;
}
#end
From "Customizing Existing Classes"
in the "Programming with Objective-C" guide:
At runtime, there’s no difference between a method added by a category
and one that is implemented by the original class.
So you can choose whatever you find more intuitive for managing your code. There will
be no difference at runtime.

Is it possible to use a wildcard in KVC?

I'm trying to use wildcard in KVC like this.
Is it possible?
Or Is there other ways to use a wildcard to indicate a member variable?
#interface MyClass : NSObject
#property(nonatomic, retain) NSNumber *test1;
#property(nonatomic, retain) NSNumber *test2;
#end
#implementation MyClass{
NSNumber * test1;
NSNumber * test2;
}
#synthesize test1;
#synthesize test2;
#end
using wildcard
MyClass *testClass = [[[MyClass alloc] init] autorelease];
testClass.test1 = #50;
NSLog(#"test value : %#", [testClass valueForKey:#"*1"]);
For detail codes.
A real reason i wanted is to indicate a member variable of instance by value of integer or nsnumber type.
If possible, it is easier to set values and read values of any instance.
For example of property part copy.
MyClass *testClass = [[[MyClass alloc] init] autorelease];
testClass.year_1 = #2012;
testClass.quarter_2 = #3;
testClass.month_3 = #8;
testClass.day_4 = #20;
testClass.week_5 = #4;
// copy propertys to other instance.
// Normal way
MyClass *testClassCopy = [[[MyClass alloc] init] autorelease];
testClassCopy.year_1 = testClass.year_1;
testClassCopy.quarter_2 = testClass.quarter_2;
testClassCopy.month_3 = testClass.month_3;
testClassCopy.day_4 = testClass.day_4;
// copy propertys by using wildcard
for (int j = 0; j < 4; j++) {
NSString *indicate = [NSString stringWithFormat:#"*%#", [NSNumber numberWithInteger:j + 1]];
NSNumber *sourceProperty = [testClass valueForKey:indicate];
[testClassCopy setValue:sourceProperty forKey:indicate];
}
I'll raise your wildcards by adding Regex, and by using categories:
To read about how regex works with this, please read the NSRegularExpression Class Reference.
Features:
Uses regex, for matching of a wide variety of keys
Uses a category that works on any instance
Caches key lists per class
Full KVC support (not just properties, but accessor methods & iVars too!)
Integrates flawlessly with current KVC methods (only uses the regex if the key wasn't found, improving performance)
Subclassing doesn't mess it up, like #JamesWebster's solution
Doesn't needlessly pollute the list of keys with NSObject's methods
Returns a NSDictionary of matched keys & values
Cons:
Uses regex, which is slower and more complex to understand
Slow initial lookup for a class (must iterate through all methods & iVars)
Automatically overwrites the -valueForUndefinedKey: method, so it's possible that this could break some existing code (move it to it's own method to fix).
Currently doesn't support setting of values (by design, that's a whole other bag of cats).
Can have duplicate keyPaths in the result (not the biggest of issues, but stems from the fact that KVC matching is complex, and I have to implement all of the rules)
Uses NSRegularExpression, which is only available in iOS 4 and later (not the largest of issues).
Version History:
1.0: Initial Release
So, here is the code:
NSObject+KVCRegex.h:
//
// NSObject+KVCRegex.h
// TestProj
//
// Created by Richard Ross on 8/20/12.
// Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface NSObject (KVCRegex)
// custom implemenation
-(id) valueForUndefinedKey:(NSString *)key;
#end
NSObject+KVCRegex.m:
//
// NSObject+KVCRegex.m
// TestProj
//
// Created by Richard Ross on 8/20/12.
// Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//
#import "NSObject+KVCRegex.h"
#import <objc/runtime.h>
#implementation NSObject (KVCRegex)
static NSSet *keyPathsForClass(Class cls)
{
NSMutableSet *keys = [NSMutableSet set];
do
{
if (cls == [NSObject class])
{
// nothing good can come from trying to use KVC on NSObject methods
break;
}
unsigned count = 0;
Method *methods = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i++) {
// make sure that the method returns a value
const char *methodName = sel_getName(method_getName(methods[i]));
char returnType[64];
method_getReturnType(methods[i], returnType, 64);
if (strcmp(returnType, "v") == 0)
continue;
// make sure that the method takes no args (except for self & _cmd)
if (method_getNumberOfArguments(methods[i]) == 2)
{
// add a duplicate entry for ones matching 'is'
if (strstr(methodName, "is") == methodName)
{
char *newStr = strdup(methodName + 2);
newStr[0] = tolower(newStr[0]);
[keys addObject:[NSString stringWithUTF8String:newStr]];
free(newStr);
}
[keys addObject:[NSString stringWithUTF8String:methodName]];
}
}
free(methods);
// now copy iVars
count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (int i = 0; i < count; i++)
{
const char *ivarName = ivar_getName(ivars[i]);
if (strstr(ivarName, "_") == ivarName)
[keys addObject:[NSString stringWithUTF8String:ivarName + 1]]; // iVar name starting with _<key>
[keys addObject:[NSString stringWithUTF8String:ivarName]];
}
free(ivars);
} while ((cls = [cls superclass]));
return [NSSet setWithSet:keys];
}
// returns a dictionary based on 'key' as a regex
-(id) valueForUndefinedKey:(NSString *)key
{
// lookup for later use
static NSMutableDictionary *keyClassPairs;
if (!keyClassPairs)
keyClassPairs = [NSMutableDictionary dictionary];
if (!keyClassPairs[[self class]])
{
keyClassPairs[(id<NSCopying>)[self class]] = keyPathsForClass([self class]);
}
NSSet *keyPaths = keyClassPairs[[self class]];
// assume 'key' is a regex
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:key options:0 error:nil];
NSMutableArray *matches = [NSMutableArray array];
for (NSString *keyPath in keyPaths)
{
NSRange matchRange = [regex rangeOfFirstMatchInString:keyPath options:0 range:(NSRange) { 0, keyPath.length }];
if (matchRange.length == keyPath.length)
{
// we have a match
[matches addObject:keyPath];
}
}
if (matches.count)
return [self dictionaryWithValuesForKeys:matches];
else
[NSException raise:NSUndefinedKeyException format:#"Could not find a key that matches the regex in %#", key];
return nil;
}
#end
Example:
#interface MyObject : NSObject
{
#public
int normalIvar;
id _underscoreIvar;
}
#property id someProp;
#property BOOL isProperty;
#property int nativeProp;
-(void) notAKey;
-(id) aKey;
#end
#implementation MyObject
#synthesize someProp, isProperty, nativeProp;
-(void) notAKey
{
NSLog(#"Not a key!");
}
-(id) aKey
{
return #"Value";
}
#end
int main()
{
#autoreleasepool {
MyObject *obj = [MyObject new];
obj.someProp = #"a property";
obj.nativeProp = 15;
obj.isProperty = YES;
obj->normalIvar = 172;
obj->_underscoreIvar = #"Ivar";
NSString *regex = #"[a|s].*"; // match a key starting with 'a' or 's', then matching anything else after
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ aKey = 'Value', someProp = 'a property' }"
regex = #"_.*"; // match a key starting with '_', and then match anything else after
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ _underscoreIvar = 'Ivar' }"
regex = #".*"; // match any key declared for this object
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ "_underscoreIvar" = Ivar; aKey = Value; isProperty = 1; nativeProp = 15; normalIvar = 172; property = 1; someProp = "a property"; underscoreIvar = Ivar; }"
regex = #"(?i)[A-J].*"; // match (case insensitive) a key starting with A - J
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ aKey = value; isProperty = 1; }"
}
}
Though I couldn't find a way to support wildcards using the syntax you were attempting. I found this roundabout method using the Objective-C runtime!
First we get all of the properties of the class you'd like to use
#import <objc/runtime.h>
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([MyClass class], &outCount);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:outCount];
for (int i = 0; i < outCount; i++)
{
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if(propName)
{
NSString *propertyName = [NSString stringWithUTF8String:propName];
[array addObject:propertyName];
}
}
free(properties);
Then filter out the ones you actually want
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF ENDSWITH '1'"];
[array filterUsingPredicate:predicate];
Then actually use them
for (NSString *key in array)
NSLog(#"%#", [testClass valueForKey:key]);

"Missing context for method declaration" for overridden description method

Am getting a "Missing context for method declaration" for my overridden description method. Can you tell what's wrong with the code?
#import <Foundation/Foundation.h>
#import "BNRItem.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
// Create a mutable array object, store its address in items variable
NSMutableArray *items = [[NSMutableArray alloc]init];
BNRItem *p = [[BNRItem alloc]init];
NSLog(#"%# %# %# %d", [p itemName], [p dateCreated], [p serialNumber], [p valueInDollars]);
// This creates a new NSString, "Red Sofa" and gives it to the BNRItem
[p setItemName:#"Red Sofa"];
// This creates a new NSString, "A1B2C" and gives it to the BNRItem
[p setSerialNumber:#"A1B2C"];
// We send the value 100 to be used as the valueInDollars of this BNRItem
[p setValueInDollars:100];
// Destroy the array pointed to by items
items = nil;
}
return 0;
}
-(NSString *)description // Missing context for method declaration
{
NSString *descriptionString =
[[NSString alloc]initWithFormat:#"%# (%#): Worth $%d, recorded on %#",
itemName;
serialNumber;
valueInDollars;
dateCreated];
return descriptionString;
}
BNRItem.m
#import "BNRItem.h"
#implementation BNRItem
-(void)setItemName:(NSString *)str {
itemName = str;
}
-(NSString *)itemName {
return itemName;
}
-(void)setSerialNumber:(NSString *)str {
serialNumber = str;
}
-(NSString *)serialNumber {
return serialNumber;
}
-(void)setValueInDollars:(int)i {
valueInDollars = i;
}
-(int)valueInDollars {
return valueInDollars;
}
-(NSDate *)dateCreated {
return dateCreated;
}
-(NSString *)description
{
NSString *descriptionString =
[[NSString alloc]initWithFormat:#"%# (%#): Worth $%d, recorded on %#",
itemName,
serialNumber; // Expected "]"
valueInDollars, // Expression result unused
dateCreated]; //Extraneous "]" before ";"
return descriptionString;
}
#end
Your method appears to be free floating inside main.m. An instance method needs to be placed inside the implementation section of a class. (between #implementation and #end).
My guess is that you should move that code into BNRItem.m.
If you have something like a char at the veryfirst beginning of your Header oder .m file, its also likely that this error accures.
^//
// EMServices.m
// MyController
//
// Created by EMart on 09.01.14.
// Copyright (c) 2014 EMart. All rights reserved.
//

Cache Expiration Implementation using NSCache

I am using NSCache to implement caching in my app. I want to add expiration to it so it will obtain new data after some time. What are the options and what's the best approach?
Should I look at the timestamp when the cache is accessed and invalidate it then? Should the cache automatically invalidate itself by using a fixed interval timer?
Should the cache automatically invalidate itself by using a fixed
interval timer?
This would be a bad solution, because you might add something seconds before the timer fires. The expiry should be based on the specific item's age. (It would, of course, be possible to conditionally invalidate items using a timer; see the comments on this answer.)
Here's an example. I thought about subclassing NSCache, but decided it was simpler to use composition.
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
}
}
Another solution would be to set an expire time when setting an object and compare against the expire time for an object.
For example:
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