/** ------------------------------------------------------------------------------------- * * Class: Cache * Version: 1.1 * * Author: Justin S. Giboney * * \brief This class is used to store objects in memory so that we don't have as many * calls to the database, and so that we don't duplicate objects. * * This class has an array that holds other objects. The reason for using this rather * than the standard memory is because it allows us to set time out periods, and allows * us constant connections to objects. This constant connection makes it so that the * objects are not collected with the garbage. Another reason for having this class is * so that we do not have to hit the database all the time for information. This allows * us to get the objects from our cache rather than the database. This will save time * and effort. * * To use this class you must have an object and a unique identifier. Using values for * an auto increment from a DBMS will not work. We need to be able to identify each * object with an id. * * * Changelog * 2008 Apr 02 Version 1.1 Updated the class with NSAutoreleasePool actions. So that * it doesn't throw warnings. * * 2008 Feb 22 Version 1.0 Created this Cache Class. Wrote some documentation. * 2008 Feb 25-27 Added all of the methods and functionality. * * * Copyright (c) 2008 Justin S. Giboney * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is furnished * to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * -----------------------------------------------------------------------------------*/ #import "Cache.h" @implementation Cache /* This variable sets the clock for our thread that goes through and cleans the array. * It is set to go off every 15 minutes. This is used by the usleep command. The usleep command * uses microseconds. 1,000,000 microseconds = 1 second. */ const static int DEFAULT_SLEEP_TIME = 15 * 60 * 1000000; // 15 minutes x 60 seconds x 1,000,000 microseconds /* This variable sets the timeout limit. If an object is over 30 minutes old it will be removed from the * array. */ const static NSTimeInterval DEFAULT_TIMOUT = 30 * 60; // 30 minutes x 60 seconds /* The array is an array of arrays where the inner arrays are set up in the following manner. * ObjectID (key), Object, TimeStamp */ static NSMutableArray *THE_ARRAY = nil; /* This is to lock the array in the put and touch methods to not allow updates while the removal * process is happening. */ static _Bool arrayLocked = 0; /* This is for the singleton method */ static Cache *THE_INSTANCE = nil; /** This method is the implementation of the singleton design pattern. It will go out and * get the instance of the Cache class. We do not want more than one instance of this * class out at a time. If we did we could have some major issues. */ + (Cache *) getInstance { //NSLog(@"getting instance\n"); @synchronized(self) { if (THE_INSTANCE == nil) { [[self alloc] init]; } } return THE_INSTANCE; } /** This method makes sure that new objects can not be instantiated by calling * the [[alloc] init] method more than once. */ + (id) allocWithZone: (NSZone *) zone { @synchronized(self) { if (THE_INSTANCE == nil) { THE_INSTANCE = [super allocWithZone:zone]; // Here we create the array object THE_ARRAY = [[NSMutableArray alloc] init]; NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init]; // Here we start the run method. It is a new thread and will run every default timout [NSThread detachNewThreadSelector:@selector(run) toTarget: self withObject: nil]; [tempPool release]; return THE_INSTANCE; } } return nil; } /** This methods checks the dictionay for an object. It requires an id of an object. If the * object is in the array, it will return that object. If it isn't, the method will * return nil. */ - (id) get: (NSString *) theID { int i, items; NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init]; // get the count of the array items = [THE_ARRAY count]; // loop through all the items in the copied Array for (i = 0; i < items; ++i){ NSMutableArray *singleItemArray = [THE_ARRAY objectAtIndex: i]; // get the id of the item NSString *itemID = [singleItemArray objectAtIndex: 0]; // compare the two ids if ([theID isEqualToString: itemID] == YES) { // touch the object [self touch: theID]; return [singleItemArray objectAtIndex: 1]; } } [tempPool release]; return nil; } /** This method puts an object into the array. It requires and id (so that we an go * back and get it later using the get method) and the object. */ - (void) put: (NSString *) theID with: (id) theObject { // wait for the array to be unlocked do { } while (arrayLocked == 1); arrayLocked = 1; NSAutoreleasePool *tempPool2 = [[NSAutoreleasePool alloc] init]; // get the timestamp NSDate *now = [NSDate date]; // create an array NSMutableArray *itemArray = [NSMutableArray arrayWithObjects: theID, theObject, now, nil]; int i, items; // get the count of the copied array items = [THE_ARRAY count]; // loop through all the items in the copied Array for (i = 0; i < items; i = i + 1){ NSMutableArray *singleItemArray = [THE_ARRAY objectAtIndex: i]; // get the id of the item NSString *itemID = [singleItemArray objectAtIndex: 0]; // compare the two ids if ([theID isEqualToString: itemID] == YES) { [THE_ARRAY removeObjectAtIndex: i]; break; } } // add the item array to the main array [THE_ARRAY addObject: itemArray]; [tempPool2 release]; arrayLocked = 0; } /** This method touches, or resets the timestamp, of an object. Since the Cache has a timer * any time that we read an object from the Cache, we want to touch it and reset its time * stamp. */ - (void) touch: (NSString *) theID { // wait for the array to be unlocked do { } while (arrayLocked == 1); // get the timestamp NSDate *now = [NSDate date]; int i, items; arrayLocked = 1; // get the count of the array items = [THE_ARRAY count]; // loop through all the items in the copied Array for (i = 0; i < items; i = i + 1){ NSMutableArray *singleItemArray = [THE_ARRAY objectAtIndex: i]; // get the id of the item NSString *itemID = [singleItemArray objectAtIndex: 0]; // compare the two ids if ([theID isEqualToString: itemID] == YES) { [singleItemArray replaceObjectAtIndex: 2 withObject: now]; [THE_ARRAY replaceObjectAtIndex: i withObject: singleItemArray]; break; } } arrayLocked = 0; } /** This method is our thread that runs every default timer amount. This is what goes * through and removes from the array, objects that are passed the time. */ + (void) run { // This will keep the loop running forever, or until the program gets shut down bool keepRunning = true; do { // wait for the array to be unlocked do { } while (arrayLocked == 1); NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init]; // lock the array arrayLocked = 1; NSDate *now = [NSDate date]; NSDate *cutoffTime = [now addTimeInterval:-DEFAULT_TIMOUT]; NSMutableArray *arrayCopy = [self getTheArray]; int i, items; // get the count of the copied array items = [arrayCopy count]; // loop through all the items in the copied Array for (i = 0; i < items; i = i + 1){ // get the array inside at the index NSMutableArray *singleItemArray = [arrayCopy objectAtIndex: i]; // get the timestamp of the item NSDate *itemTime = [singleItemArray objectAtIndex: 2]; // compare the two times if ([cutoffTime compare: itemTime] == (NSOrderedDescending)) { // get the id of the item NSString *itemID = [singleItemArray objectAtIndex: 0]; // remove the item [self removeArrayItem: itemID]; } } // unlock the array arrayLocked = 0; [tempPool release]; // sleep the specified time usleep(DEFAULT_SLEEP_TIME); } while (keepRunning == true); } /** This method should only be accessed by the run method. It will basically create a copy of * the array, so that it can compare it with the times without being interuppted by what is * going on with the rest of the program. * * I do see a problem here. If an object gets touched between the time that the array gets * copied and before the run method hits that spot, and the time before it gets touched is * passed the allowed time, the object will be released from the array when it really shouldn't. * Hopefully this won't occur often. Either way it won't cause any problems with the program. * It just won't be as fast as it could in those situations. */ + (NSMutableArray *) getTheArray { return THE_ARRAY; } /** This method can be called from outside and it removes an item.*/ - (void) removeItem: (NSString *) theID { int i, items; items = [THE_ARRAY count]; // 30 Apr 08, I have been getting an error after about 30 minutes of this running that // says that there is less in the array than there should be, so I am surrounding it with // with this try catch to try to prevent that error. @try { // loop through all the items in the Main Array for (i = 0; i < items; ++i){ // get the array inside at the index NSMutableArray *singleItemArray = [THE_ARRAY objectAtIndex: i]; // get the id of the item NSString *itemID = [singleItemArray objectAtIndex: 0]; // compare the two ids and remove the one if they are the same if ([theID isEqualToString: itemID] == YES) { [THE_ARRAY removeObjectIdenticalTo: singleItemArray]; break; } } } @catch (NSException e){ NSLog(@"error deleting an item from the cache"); } } /** This method should only be called by the run method. It is designed to remove an item * from the array. */ + (void) removeArrayItem: (NSString *) theID { int i, items; items = [THE_ARRAY count]; // loop through all the items in the Main Array for (i = 0; i < items; ++i){ // get the array inside at the index NSMutableArray *singleItemArray = [THE_ARRAY objectAtIndex: i]; // get the id of the item NSString *itemID = [singleItemArray objectAtIndex: 0]; // compare the two ids and remove the one if they are the same if ([theID isEqualToString: itemID] == YES) { [THE_ARRAY removeObjectIdenticalTo: singleItemArray]; break; } } } /** This method sees if a key is in the array */ - (bool) containsKey: (NSString *) theID { int i, items; items = [THE_ARRAY count]; for ( i = 0; i < items; ++i ){ // get the array inside at the index NSMutableArray *singleItemArray = [THE_ARRAY objectAtIndex: i]; // get the id of the item NSString *itemID = [singleItemArray objectAtIndex: 0]; // compare the two ids and remove the one if they are the same if ([theID isEqualToString: itemID] == YES) { [self touch: theID]; return true; } } return false; } /* The following methods were taken from Apple's website to help with the Singleton * implementation. http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/chapter_3_section_10.html * 25 Feb 2008 */ - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (unsigned)retainCount { return UINT_MAX; //denotes an object that cannot be released } - (void)release { //do nothing } - (id)autorelease { return self; } @end