Cache Expiration Implementation using NSCache - cocoa-touch

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

Related

How to use data from main.m in a subclass

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.

Archiving objects within objects in objective-c

Thankyou for reading,
PS: I am a beginner so I am not too good at this unfortunetaly, but any help would be very appreciated
So basically I want to archive a big array which contains Account objects, which they themselves contain:
1.a username in form of a NSString,
2.an encrypted password array filled with NSNumbers, and
3.a data array filled with service data objects.
The service data objects have the following:
encrypted serviceType (NSArray filled with NSNumbers) (whatever service the username and password is for)
encrypted username (NSArray filled with NSNumbers)
encrypted password (NSArray filled with NSNumbers)
Now weirdly when trying to archive and save this, I get two errors. One time it won't let me add service data objects to the data array in the Account class anymore, with the following error message (or at least they dont show up in the NSTableView I have, however it does say they exsist):
[<ServiceData 0x60000023bfa0> valueForUndefinedKey:]: this class is not key value
coding-compliant for the key service.
and two, when I try to login in the the username and password from the Account class, it retrieves the username and the first couple and last couple NSNumbers of my password correctly, but the rest of the NSNumbers for the password are in the trillions or something, so I'm wondering what is going wrong, any help would be greatly appreciated.
Here is the code for my instance variables, how I used the NSKeyedArchiver and unarchiver, and how I went about saving and loading the files. Again, please help me, I have been stuck on this for a while and this is kind-of my last resort. I have no idea what is happening!
ACCOUNT CLASS:
H file:
#interface Account : NSObject <NSCoding>
{
#private
NSString *username;
NSMutableArray *password;
NSMutableArray *accData;
}
#property NSString *username;
#property NSArray *password;
FULL M file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if(self)
{
username = [aDecoder decodeObjectForKey:#"username"];
password = [aDecoder decodeObjectForKey:#"password"];
accData = [aDecoder decodeObjectForKey:#"data"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:password forKey:#"password"];
[aCoder encodeObject:accData forKey:#"data"];
}
SERVICEDATA CLASS:
H file:
#interface ServiceData : NSObject <NSCoding>
{
#private
NSArray* serviceData;
NSArray* usernameData;
NSArray* passwordData;
}
#property NSArray* serviceData;
#property NSArray* usernameData;
#property NSArray* passwordData;
M file:
#import "Account.h"
#import "Crypt.h"
NSMutableArray *accounts;
NSInteger accountNumber = -1;
#implementation Account
#synthesize username;
#synthesize password;
- (id)initWithUsername:(NSString *)name withPassword:(NSMutableArray *)key
{
self = [super init];
if (self)
{
username = name;
password = key;
accData = [[NSMutableArray alloc]init];
}
return self;
}
/*
setters and getters
*/
-(NSString*)getUsername;
{
return username;
}
-(NSArray*)getPassword;
{
return password;
}
-(void)changePassword:(NSMutableArray*)newPassword;
{
NSInteger sizeOldPass = [password count];
NSInteger sizeNewPass = [newPassword count];
int changeXObjects = (int)(sizeNewPass - sizeOldPass);
int changeSize = abs(changeXObjects);
//adjusts size differences
if (changeXObjects < 0)
{
for(int i = 0; i < changeSize; i++)
{
[password removeLastObject];
}
}
else if (changeXObjects > 0)
{
for(int i = 0; i < changeSize; i++)
{
NSNumber *value = [NSNumber numberWithInt:0];
[password addObject:value];
}
}
//change password
NSInteger sizePass = [password count];
for (int k = 0; k < sizePass; k++)
{
[password replaceObjectAtIndex:k withObject:newPassword[k]];
}
}
-(NSMutableArray*)getAccData;
{
return accData;
}
-(void)setAccData:(NSMutableArray*)input
{
[input setArray: accData];
}
+(NSMutableArray*)getAccounts
{
return accounts;
}
+(NSInteger)getAccountNumber
{
return accountNumber;
}
+(void)setAccounts:(id)accs
{
accounts = accs;
}
+(void)setAccountNumber:(NSInteger)number
{
accountNumber = number;
}
/*
other methods
*/
+(void)addAccount:(id)acc
{
[accounts addObject:acc];
}
+(void)deleteAccount:(NSInteger)index
{
[accounts removeObjectAtIndex:index];
}
-(void)addAccData:(id)input
{
[accData addObject:input];
}
-(void)deleteAccDataAt:(NSInteger)index
{
[accData removeObjectAtIndex:index];
}
+(bool)checkPassword:(NSString*)passwordIn accountNumber:(NSInteger)index
{
NSMutableArray *passwordInputCrypt = [Crypt encrypt:passwordIn];
NSMutableArray *passwordCrypt = [accounts[index] getPassword];
NSInteger lengthPassword = [passwordInputCrypt count];
bool correctPassword = true;
if([passwordCrypt count] == [passwordInputCrypt count])
{
for(int i = 0; i < lengthPassword; i++)
{
if(passwordCrypt[i]!=passwordInputCrypt[i])
correctPassword = false;
}
}
else
{
correctPassword = false;
}
if(correctPassword == true)
{
return true;
}
return false;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if(self)
{
username = [aDecoder decodeObjectForKey:#"username"];
password = [aDecoder decodeObjectForKey:#"password"];
accData = [aDecoder decodeObjectForKey:#"data"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:password forKey:#"password"];
[aCoder encodeObject:accData forKey:#"data"];
}
#end
LOADING FILE(filePath is given):
NSData *data = [[NSFileManager defaultManager] contentsAtPath:filePath];
if(data != nil)
{
NSArray *arrayFromData = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSMutableArray *initArray = [NSMutableArray arrayWithArray:arrayFromData];
[Account setAccounts:initArray];
}
else
{
NSMutableArray *accountsInit = [[NSMutableArray alloc] init];
[Account setAccounts:accountsInit];
}
SAVING FILE:
NSArray *accounts = [Account getAccounts];
NSString *filePath = [AppController getFilePath];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:accounts];
[data writeToFile:filePath atomically:YES];
A few things:
You should not be archiving password data to disk (even if you are encrypting it). That's what the keychain is for. Have a look at SSKeychain for a good wrapper class.
The Key-value coding error you are getting suggests you are trying to reference your serviceData array as just "service" somewhere. Check your valueForKey and setValueForKey statements.
Can you post the rest of the Account class? That method setAccounts looks like it might be relevant.
Also is there a reason you are using Keyed Archiving instead of Core Data?

NSMutableDictionary crashes with "mutating message sent to immutable object"

I have a class that has a NSMutableDictionary as a property:
#interface Alibi : NSObject <NSCopying>
#property (nonatomic, copy) NSMutableDictionary * alibiDetails;
#end
With the following constructor:
- (Alibi *)init
{
self = [super init];
_alibiDetails = [NSMutableDictionary dictionary];
return self;
}
and copy method:
- (Alibi *)copyWithZone:(NSZone *)zone
{
Alibi *theCopy = [[Alibi alloc] init];
theCopy.alibiDetails = [self.alibiDetails mutableCopy];
return theCopy;
}
When I try to call setObject:ForKey: I get a runtime error mutating method sent to immutable object.
I have the Alibi object declared in the view controller as #property (copy, nonatomic) Alibi * theAlibi; and I initialize it with self.theAlibi = [[Alibi alloc] init]; in viewDidLoad.
The line which crashes is:
NSString * recipient;
recipient = #"Boss";
[self.theAlibi.alibiDetails setObject:recipient forKey:#"Recipient"];
Please let me know what I am doing wrong here. I am coding for iOS 5 on iPhone.
You have a 'copy' property, which means exactly that - your NSMutableDictionary will get the -copy method called and return a regular NSDictionary before being assigned to the synthesized instance variable. This thread provides some information on some of your options as to solving this.
For the sake of completing this thread I will include my revised Alibi class below, this works as I require it to. If anyone notices any memory leaks or other issues, that would be appreciated.
#implementation Alibi
NSMutableDictionary *_details;
- (Alibi *)init
{
self = [super init];
_details = [NSMutableDictionary dictionary];
return self;
}
- (NSMutableDictionary *)copyDetails
{
return [_details mutableCopy];
}
- (NSMutableDictionary *)setDetails:(NSMutableDictionary *)value
{
_details = value;
return value;
}
- (void)addDetail:(id)value forKey:(id)key
{
[_details setObject:value forKey:key];
}
- (id)getDetailForKey:(id)key
{
return [_details objectForKey:key];
}
- (Alibi *)copyWithZone:(NSZone *)zone
{
Alibi *theCopy = [[Alibi alloc] init];
theCopy.serverId = [self.serverId copyWithZone:zone];
theCopy.user = [self.user copyWithZone:zone];
theCopy.startTime = [self.startTime copyWithZone:zone];
theCopy.endTime = [self.endTime copyWithZone:zone];
[theCopy setDetails:[self copyDetails]];
return theCopy;
}
#end

Custom single KeyValuePair class vs NSMutableDictionary

I came into a situation where I had to write a loop with a good amount if iterations and in this loop I had a NSData object that I had to associate with a key. This lead me to search for a simple objective-c _KeyValuePair_ class but coulnt not find one so I wrote my own. Now I'm curious to see if there is any benefit over just using an NSMutableDictinoary holding just 1 key and value. After trying both throughout my project I can't tell much difference on the App UI side or with Instruments Time Profiler.
So my questions are:
Could a single kvpair class be more efficient than a NSMutableDictionary
Does a NSMutableDict allocate any larger amount of space by default then this does
Is there actually a standard single key value pair class that I just missed
Some code:
for (int i = 0, count = [photoUrls count]; i < count; ++i) {
// Example usage of the kvp class
NSMutableDictionary *imageRequest = [[NSMutableDictionary alloc] init];
JHKeyValuePair *kvPair = [[JHKeyValuePair alloc] initWithKey:#"DAILY" andValue:[NSNumber numberWithInt:i];
[imageRequest setObject:self forKey:#"delegate"];
[imageRequest setObject:kvPair forKey:#"userInfo"];
[kvPair release];
[imageRequest setObject:[dailySpecialData objectForKey:#"IMAGE_URL"] forKey:#"url"];
[imageDownloader addDownloadRequestToQueue:imageRequest];
[imageRequest release];
}
JHKeyValuePair.h
#interface JHKeyValuePair : NSObject {
id key;
id value;
}
#property (nonatomic, retain) id key;
#property (nonatomic, retain) id value;
- (id)initWithKey:(id)aKey andValue:(id)aValue;
#end
JHKeyValuePair.m
#import "JHKeyValuePair.h"
#implementation JHKeyValuePair
#synthesize key;
#synthesize value;
- (id)initWithKey:(id)aKey andValue:(id)aValue {
if ((self = [super init])) {
key = [aKey retain];
value = [aValue retain];
}
return self;
}
- (void)dealloc {
[key release], key = nil;
[value release], value = nil;
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone {
JHKeyValuePair *copy = [[JHKeyValuePair allocWithZone:zone] init];
[copy setKey:self.key];
[copy setValue:self.value];
return copy;
}
- (BOOL)isEqual:(id)anObject {
BOOL ret;
if (self == anObject) {
ret = YES;
} else if (![anObject isKindOfClass:[JHKeyValuePair class]]) {
ret = NO;
} else {
ret = [key isEqual:((JHKeyValuePair *)anObject).key] && [value isEqual:((JHKeyValuePair *)anObject).value];
}
return ret;
}
#end
Edit to fix the initial explanation. Seems I got sidetracked mid-sentance and never came back to finish it.
If you really want to get speed you are doing a lot of unnecessary retain releases that probably aren't necessary every time you set your key/values. If you use a struct and some basic c code you can achieve something a little quicker but you sacrifice the simple and consistent memory management you get from doing it the objective c way.
typedef struct {
id key;
id value;
} Pair;
BOOL isEqual(Pair a, Pair b); //...
// You will need to clean up after yourself though:
void PairRelease(Pair p) {
[p.key release];
[p.value release];
}

iOS - Storing groups of UILabels into a NSMutableArray

I'm creating UILabels dynamically in a for each loop. Every loop that is run creates 1-4 UILabels.
What I want is that I put these UILabels into my NSMutableArray and being able later to easy retrieve the data.
My original thought was to put these UILabels into a NSDictionary and use [dictGroupLabels setValue:uiLabel1 forKey:#"uiLabel1"] and then [dictGroupLabels setValue:uiLabel2 forKey:#"uiLabel2"] and so on. And then put this dictionary into my NSMutableArray for each loop. Later on I could access the values like UILabel *label = [[myArray objectAtIndex:0] valueForKey:#"uiLabel1"] BUT that unfortunately doesn't work since UILabels don't conform to the NSCopying protocol.
So with this in mind how would you solve this?
this question provided more information on what you are trying to accomplish. Since you know for a fact, the possible set of labels you are trying to create in each case, I would highly recommend using mutable dictionaries instead of arrays.
To illustrate, given the following hypothetical class definition:
#interface MyClass: NSObject {
NSMutableDictionary * _labelDict;
}
#property (nonatomic, retain) NSMutableDictionary * labelDict;
- ( void )methodA;
- ( void )methodB;
- (NSMutableDictionary *) labelsForRunLoop: (NSUInteger) loopIdx;
#end
You would have the following, hypothetical, class implementation:
#implementation MyClass
#synthesize labelDict = _labelDict;
- ( id ) init {
if( ( self = [ super init ] ) ) {
[self setLabelDict: [NSMutableDictionary dictionaryWithCapacity: 8]];
}
}
- ( void ) dealloc {
[ self.labelDict release ];
[ super dealloc ];
}
- ( void ) methodA {
for(NSUInteger i = 0; i < some index; i++) {
[self.labelDict setObject: [self labelsForRunLoop: i] forKey: [NSString stringWithFormat: #"%d", i]];
}
}
- ( void ) methodB {
// Locate the label you need to work with. Example based on this crude pseudo code
NSMutableDictionary * subDict = (NSMutableDictionary *) [self.labelDict objectForKey: #"0"];
UILabel * theLabel = (UILabel * ) [subDict objectForKey: #"UILabel.Z"];
theLabel.text = #"Label 1";
}
- (NSMutableDictionary *) labelsForRunLoop: (NSUInteger) loopIdx {
NSMutableDictionary * dictionary = [NSMutableDictionary dictionaryWithCapacity: 4] ;
[dictionary setObject: create-w-label forKey: #"UILabel.W"];
[dictionary setObject: create-x-label forKey: #"UILabel.X"];
[dictionary setObject: create-y-label forKey: #"UILabel.Y"];
[dictionary setObject: create-z-label forKey: #"UILabel.Z"];
return [dictionary retain];
}
#end
This is basically pseudo code and will not successfully compile. However it will serve as a good starting point. You probably want to store each label dictionary under some key that makes sense, instead of just using the loop's index. Hope this helps.
They don’t need to adhere to NSCopying to be added to an array. It sounds like you just need to do something like this:
NSMutableArray *mainArray = [NSMutableArray array];
for(int i = 0; i < 5; i++)
{
NSMutableArray *subArray = [[NSMutableArray alloc] initWithCapacity:5];
for(int j = 0; j < 4; j++)
{
UILabel *label = [[UILabel alloc] init];
// etc.
[subArray addObject:label];
[label release];
}
[mainArray addObject:subArray];
[subArray release];
}
// then, to get one of the labels:
UILabel *someSpecificLabel = [[mainArray objectAtIndex:2] objectAtIndex:1];