Does NSCache persist across ViewControllers? - ios

Its my first time using NSCache and I know that it doesn't persist on app lunch.
But does it persist across ViewControllers?... Meaning... If I set the cache object on ViewController A and then I move to ViewController B can I still access it?
My question is related to an issue that I am having in my code. I am on ViewController B and I set the cache object. Then move to ViewController B and try to retrieve that object but is never found.
Is that normal or there is a problem in my code??? My Views are quite inexpensive so I see no reason why it would be dropping the cache object
ViewController A (Using Cache)
- (void) searchDone:(NSDictionary*)response {
NSString * str = input.text;
str = [str stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString* cachedKey = [str stringByReplacingOccurrencesOfString:#"#" withString:#""];
cachedKey = [cachedKey lowercaseString];
//
// Cache
//
NSCache * cache = [[NSCache alloc]init];
NSDictionary* chachedData = [cache objectForKey:cachedKey];
// Check for a cached version of this
if ( chachedData ) {
NSLog(#"There is a cache");
NSTimeInterval timeDifferenceBetweenDates = [chachedData[#"time"] timeIntervalSinceNow];
CGFloat time = fabsf(timeDifferenceBetweenDates);
if ( time < sysTraditionalSearchCacheTime ) {
NSLog(#"using cache");
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:input.text,#"input",chachedData,#"response",nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"closeSearch"
object:nil
userInfo:dictionary];
return;
}
[cache removeObjectForKey:cachedKey];
}
ViewController B (Cache Setter)
- (void) notificationCloseSearch:(NSNotification*) notification {
input.text = [[notification userInfo] valueForKey:#"input"];
NSDictionary* response = [[notification userInfo] valueForKey:#"response"];
NSString * str = input.text;
str = [str stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString* cachedKey = [str stringByReplacingOccurrencesOfString:#"#" withString:#""];
cachedKey = [cachedKey lowercaseString];
//
// Cache
//
NSCache * cache = [[NSCache alloc]init];
NSDictionary* chachedData = [cache objectForKey:cachedKey];
// Check for a cached version of this
if ( chachedData ) {
NSTimeInterval timeDifferenceBetweenDates = [chachedData[#"time"] timeIntervalSinceNow];
CGFloat time = fabsf(timeDifferenceBetweenDates);
if ( time >= sysTraditionalSearchCacheTime ) {
[cache removeObjectForKey:cachedKey];
}
} else { // if there is no cache then set one
NSLog(#"setting cache key %#",cachedKey);
NSDate* now = [NSDate date];
NSMutableDictionary* newResopnse = [[NSMutableDictionary alloc]initWithDictionary:response];
[newResopnse setObject:now forKey:#"time"];
response = [[NSDictionary alloc] initWithDictionary:newResopnse];
[cache setObject:response forKey:cachedKey];
NSLog(#"cached %#",[cache objectForKey:cachedKey]);
}
}

NSCache is a some kind of NSMutableDictionary.
The difference is that when NSCache detects excessive memory pressure it will release some of it key-value pairs.
In ViewControllerA you create NSCache object
NSCache * cache = [[NSCache alloc]init];
And in ViewControllerB you create one more NSCache object again
NSCache * cache = [[NSCache alloc]init];
So it will be two different objects with two different set of key-value pairs.
If you need some kind of storage you can write singleton class which will contain one NSCache object.

Related

NSCoding - saving array to file or nsdefaults

Afternoon all,
Working on my first iphone app.
I am trying to save an array of an array either to file or nsuserdefaults.
Data is like this...
MainArray (contains 3 below arrays)
Array1 (contains 3 strings)
Array2 (contains 3 strings)
Array3 (contains 3 strings)
So far I've been reading about saving things to nsuserdefaults, and saving to file. Not sure which is the right way or benefits of either but I start trying saving to file.
below is my custom object to save information.
#implementation UserSettingsClass
+ (instancetype)sharedUserData{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self loadInstance];
//sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.arrayUserSettings forKey:#"someArray"];
[encoder encodeObject:self.userDescription forKey:#"testDesc"];
}
- (id)initWithCoder:(NSCoder *)decoder{
if ((self = [super init])){
self.userDescription = [decoder decodeObjectForKey:#"testDesc"];
self.arrayUserSettings = [decoder decodeObjectForKey:#"someArray"];
}
return self;
}
+(NSString *)filePath{
static NSString *filePath = nil;
if (!filePath){
filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:#"gamedata"];
}
return filePath;
}
+(instancetype)loadInstance{
NSData *decodedData = [NSData dataWithContentsOfFile:[UserSettingsClass filePath]];
if (decodedData){
UserSettingsClass *gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
return [[UserSettingsClass alloc] init];
}
-(void)save{
NSData *encodedData = [NSKeyedArchiver archivedDataWithRootObject:self];
[encodedData writeToFile:[UserSettingsClass filePath] atomically:YES];
}
and here is the main class where I am trying to use it.
//initialize variables
//_userArray = [[NSMutableArray alloc] init];
//_userDescription = [[NSString stringWithFormat:#"testDescription"] init];
//_userLoginID = [[NSString stringWithFormat:#"testLogin"] init];
//_userPW = [[NSString stringWithFormat:#"testPassword"] init];
// [_userArray addObject:[UserSettingsClass sharedUserData].userDescription];
//[_userArray addObject:_userLoginID];
//[[UserSettingsClass sharedUserData].arrayUserSettings addObject: [UserSettingsClass sharedUserData].userDescription];
NSMutableArray *tempArray = [UserSettingsClass sharedUserData].arrayUserSettings;
//[_userArray addObject:_userPW];
//save data to shared singleton class
//[[UserSettingsClass sharedUserData].arrayUserSettings addObject:_userArray];
//NSMutableArray *tempArray = [[NSMutableArray alloc] init];
//tempArray = [UserSettingsClass sharedUserData].arrayUserSettings;
//[UserSettingsClass sharedUserData].highScore = 10;
//int i = [UserSettingsClass sharedUserData].highScore;
//[UserSettingsClass sharedUserData].userDescription = #"hello";
NSString *temp2 = [UserSettingsClass sharedUserData].userDescription;
I am able to save the single string, but I must be doing something wrong.
The single string I saved was just to see if I can get it working. My goal is to save the main array to file (or nsuserdefaults), which contain about 3 objects (array)... and each of those arrays contains 3 strings each.
any I doing something blatantly wrong?
You are trying to hard.
If what you want to save is just NSArrays and NSStrings to you do not need so add an NSCoding, these types already conform to NSCoding. Just Archive to a file or "shudder" save to NSUserDefaults "/shudder".
It is really better to create a Data Model class and use NSArchiver to save and restore from a file in the Documents directory.

See if NSDictionary key is in another NSArray

Database (
{
to = (NSString *)
from = (NSString *)
subject = (NSString *)
uid = int
body = (NSString *)
}, { ...
)
Downloaded (
{
to = (NSString *)
from = (NSString *)
subject = (NSString *)
uid = int
body = (null)
}, { ...
)
I immediately pull and load an NSArray of about 200 NSDictionay objects from my Database into my UITableView, then I download an NSArray of the same structured NSDictionary but without a body.
Q: How do I go through all 200 Downloaded NSDictionary to see if it isn't already in my Database NSArray by matching the key: "uid"?
This should do the trick:
NSArray *arrayOfNew = [arrayDownload filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"NOT (uid IN %#)", [arrayDataBase valueForKey:#"uid"]];
Tested with this sample data, if someone want to test it:
NSDictionary *dictionary0 = #{#"to":#"0",#"from":#"0",#"uid":#(0), #"body":#"0"};
NSDictionary *dictionary1 = #{#"to":#"1",#"from":#"1",#"uid":#(1), #"body":#"0"};
NSDictionary *dictionary2 = #{#"to":#"2",#"from":#"2",#"uid":#(2), #"body":#"0"};
NSDictionary *dictionary3 = #{#"to":#"3",#"from":#"3",#"uid":#(3), #"body":#"0"};
NSDictionary *dictionary4 = #{#"to":#"4",#"from":#"4",#"uid":#(2)};
NSDictionary *dictionary5 = #{#"to":#"5",#"from":#"5",#"uid":#(5)};
NSArray *arrayDataBase = #[dictionary0, dictionary1, dictionary2, dictionary3];
NSArray *arrayDownload = #[dictionary4, dictionary5];
//So the dictionary4 shouldn't be kept, and dictionary5 should be kept.
NSArray *arrayNew = [arrayDownload filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"NOT (uid IN %#)", [arrayDataBase valueForKey:#"uid"]]];
NSLog(#"arrayNew: %#", arrayNew);
Output:
arrayNew: (
{
from = 5;
to = 5;
uid = 5;
}
With this code you can iterate in two arrays called "Downloaded" and "Database" and check if their uid match. I'm not sure if you're looking for a more elegant solution.
for (NSDictionary *dictDownloaded in Downloaded) {
for (NSDictionary *dictDatabase in Database) {
if ([dictDownloaded objectForKey:#"uid"] == [dictDatabase objectForKey:#"uid"]) {
NSLog(#"Object with uid: %d is in database", [[dictDownloaded objectForKey:#"uid"] intValue]);
}
}
}
Beware of nested loops :)
If you use Arturo's example (which works!) and download 1000 messages, you will have a potential of O(n*m) = 1000*200 = 200.000 "calculation steps"
Larme's attempt is pretty elegant (I like predicates!) but it's hard to predict the time it will use for execution, because it's all encapsulated within NSPredicate.
So another attempt, based on Larme's example data, would be to use a dictionary with the uid as the key for fast lookup.
NSMutableDictionary *databaseLookupDictionary = [[NSMutableDictionary alloc]init];
databaseLookupDictionary[#(0)] = #{#"to":#"0",#"from":#"0",#"uid":#(0), #"body":#"0"};
databaseLookupDictionary[#(1)] = #{#"to":#"1",#"from":#"1",#"uid":#(1), #"body":#"0"};
databaseLookupDictionary[#(2)] = #{#"to":#"2",#"from":#"2",#"uid":#(2), #"body":#"0"};
databaseLookupDictionary[#(3)] = #{#"to":#"3",#"from":#"3",#"uid":#(3), #"body":#"0"};
/* your download code */
// example data
NSMutableArray *downloadedData = [[NSMutableArray alloc]init];
[downloadedData addObject: #{#"to":#"0",#"from":#"0",#"uid":#(3)}];
[downloadedData addObject: #{#"to":#"0",#"from":#"0",#"uid":#(4)}];
for(NSDictionary *downloadDataDict in downloadedData)
{
// will be executed for message #4
if(![databaseLookupDictionary.allKeys containsObject:downloadDataDict[#"uid"]])
{
NSLog(#"Unknown message data found: %#", downloadDataDict);
}
}
This runs in linear time (O(n)*O(1)) so you should be fine with the performance. But keep in mind: if your message database count grows, you should think about searching directly in CoreData.

JSON with Dictionary - nested objects to convert to strings and display

I came across few posts here related to what I am doing but I am working with some nested objects that I want to extract.
This is a sample of my returned data - https://gist.github.com/ryancoughlin/8043604
I have this in my header so far :
#import "TideModel.h"
#protocol TideModel
#end
#implementation TideModel
-(id)initWithDict:(NSDictionary *)json {
self = [super init];
if(self) {
self.maxheight = [dictionary valueForKeyPath:#"tide.tideSummaryStats.minheight"];
self.minheight = [dictionary valueForKeyPath:#"tide.tideSummaryStats.maxheight"];
self.tideSite = [dictionary valueForKeyPath:#"tide.tideInfo.tideSite"];
}
return self;
}
#end
I have declared a property for each string and i am accessing it accordingly.
But what I have above doesn't work, maybe because it wont know what to drill in to correct?... Or will it?
tide.tideSummaryStats returns an array.
tide.tideInfo returns an array.
So you can't do -valueForKeyPath: all the way.
Also, this is incorrect: [dictionary valueForKeyPath:...];
it should be : [json valueForKeyPath:...];
because json is the name of the NSDictionary variable passed (not dictionary)
Try this (not sure):
-(id)initWithDict:(NSDictionary *)json {
self = [super init];
if(self) {
NSArray *arrOfTideSummaryStats = [json valueForKeyPath:#"tide.tideSummaryStats"];
NSDictionary *dctOfTideSummaryStats = [arrOfTideSummaryStats objectAtIndex:0];
//since self.maxheight and self.minheight are NSString objects and
//the original keys "minheight" & "maxheight" return float values, do:
self.maxheight = [NSString stringWithFormat:#"%f", [dctOfTideSummaryStats valueForKey: #"maxheight"]];
self.minheight = [NSString stringWithFormat:#"%f", [dctOfTideSummaryStats valueForKey: #"minheight"]];
/*============================================================*/
NSArray *arrOfTideInfo = [json valueForKeyPath:#"tide.tideInfo"];
NSDictionary *dctOfTideInfo = [arrOfTideInfo objectAtIndex:0];
self.tideSite = [dctOfTideInfo valueForKey:#"tideSite"];
}
return self;
}
Similar Questions:
How to parsing JSON object in iPhone SDK (XCode) using JSON-Framework
Getting array elements with valueForKeyPath
Keypath for first element in embedded NSArray
Recently had to create a app that worked with a remote RESTful server that returned JSON data and was then deserialised into an object for graphing.
I used unirest for the requests and responses and then deserialised the returned JSON into an object. Below is an extract of the code where "hourlySalesFigures" within dictionary "jsonResponseAsDictionary" was a JSON collection of 24 figures which I put into an array. Please note the function is a lot larger but I removed anything which I thought was distracting.
- (PBSSales*) deserializeJsonPacket2:(NSDictionary*)jsonResponseAsDictionary withCalenderType:(NSString *)calendarViewType
{
PBSSales *pbsData = [[PBSSales alloc] init];
if(jsonResponseAsDictionary != nil)
{
// Process the hourly sales figures if the day request and returned is related to Daily figures
if([calendarViewType isEqualToString:#"Day"]){
NSArray *hourlyFiguresFromJson = [jsonResponseAsDictionary objectForKey:#"hourlySalesFigures"];
PBSDataDaySales *tmpDataDay = [[PBSDataDaySales alloc] init];
NSMutableArray *hSalesFigures = [tmpDataDay hourlySalesFigures];
for(NSInteger i = 0; i < [hourlyFiguresFromJson count]; i++){
hSalesFigures[i] = hourlyFiguresFromJson[i];
}
[[pbsData dataDay] setHourlySalesFigures:hSalesFigures];
[pbsData setCalViewType:#"Day"];
}
}
return pbsData;
}

NSThread Causing memory Leaks in iPhone

I am uploading images chunk wise, in a background thread, each chunk will be size of 512kb,to the best of my knowledge,i have taken care of memory leaks using release,nsautoreleasepool.
Below is the code for uploading images chunkwise.
- (void)FetchDataFromDB : (NSNumber *) isOffline
{
#autoreleasepool {
#try {
NSLog(#"FetchDatafromDB");
isThreadStarted = YES;
VYukaDBFunctions *vdb = [VYukaDBFunctions getInstance];
NSMutableArray *fileNames = [vdb GetFileNames:[isOffline integerValue]];
for(int j=0 ; j<[fileNames count] ; j++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString * filename = fileNames [j] ;
int _outgoingMsgId = [[vdb SelectMsgId:filename] intValue];
int _totalchunk =[[vdb SelectTotalChunk:filename]intValue];
int currentChunk = [vdb GetCurrentChunk:filename];
for( int i=currentChunk ; i <= _totalchunk ; i++)
{
NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
NSString *AsyncRequest = [[NSString alloc] init];
AsyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
if(![AsyncRequest isEqual:#""])
{
BOOL status = [self UploadChunkWise :AsyncRequest : 1 : i : vdb : filename : _outgoingMsgId];
// AsyncRequest = NULL;
// [AsyncRequest release];
if(status){
if(i==_totalchunk)
{
NSLog(#"Deleting from medialist , FileName :%#", filename);
[vdb DeleteFromMediaList : filename];
}
}
else{
[vdb DeleteFromMediaList : filename];
break;
}
}
[innerPool drain];
}
[pool drain];
}
[fileNames removeAllObjects];
// [fileNames release];
//recurssive call to check any pending uploads..
if([[vdb GetFileNames:[isOffline integerValue]] count] > 0)
{
NSLog(#"Calling Recursively..");
[self FetchDataFromDB:[isOffline integerValue]];
}
}
#catch (NSException *exception) {
NSLog(#"Exception caught on Uploading from FetchDataFromDB:%#", exception);
}
#finally {
}
}
NSLog(#"thread quit ");
isThreadStarted = NO;
[NSThread exit];
}
-(BOOL) UploadChunkWise :(NSString *) AsyncRequest : (int) count : (int)currentChunk : (VYukaDBFunctions * ) vdb : (NSString *) currentFileName : (int) outgoingMsgId
{
NSHTTPURLResponse *response ;
NSError *error;
//Yes, http
NSMutableURLRequest *httpRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"Url goes here"]];
NSData* data = [AsyncRequest dataUsingEncoding:NSUTF8StringEncoding];
[httpRequest setHTTPMethod:#"POST"];
[httpRequest setHTTPBody:data];
[httpRequest setValue:#"application/xml" forHTTPHeaderField:#"Content-Type"];
NSData *returnedData = [NSURLConnection sendSynchronousRequest: httpRequest returningResponse:&response error:&error] ;
NSString *result= [[NSString alloc] initWithData:returnedData encoding:NSASCIIStringEncoding];
[httpRequest release];
returnedData= NULL;
[returnedData release];
data = NULL;
[data release];
if ([result rangeOfString:#"success"].location != NSNotFound )
{
NSLog(#" success");
[vdb DeleteCurrentChunkFromOutgoingTable:currentChunk : outgoingMsgId];
[result release];
return YES ;
}
else if ([result rangeOfString:#"fail"].location != NSNotFound )
{
[result release];
if (count < 3) {
return [self UploadChunkWise :AsyncRequest : count+1 : currentChunk: vdb : currentFileName : outgoingMsgId ];
}
else
{
NSLog(#" failed");
[vdb DeleteAllCurrentFileChunksFromOutgoingTable:currentFileName];
return NO ;
}
}
return NO;
}
I am starting thread as below
[NSThread detachNewThreadSelector:#selector(FetchDataFromDB:) toTarget:self withObject:[NSNumber numberWithInt:0]];
The problem is after uploading 9 to 12 chunks, i am getting memory error. i am getting 4 to 5 times memory warning and after that app crashes.in console i am getting memory warning first at app delegate class, followed by 4 classes which are extending UIViewController. why i am getting warning at app delegate, and other classes which is of type UIViewController.Why i have to release object of other class if the separate thread is giving me memory error? what i am doing wrong here? I cannot use ARC, as i have integrated this with old code, which is not using ARC, i tried enabling ARC class wise, but it dint work. Can any one help me to find out if there is any memory leaks in this code. Suggestions are welcomed and appreciated.Thanks in advance..
Two things- first, I see this:
NSString *AsyncRequest = [[NSString alloc] init];
AsyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
This should be consolidated to this:
NSString *asyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
You instead are creating a new instance, then immediately either generating or referencing another instance.
Second:
Your code is very hard to read and doesn't follow the Objective-C smalltalk conventions.
Variable names should begin with a lowercase letter. Method names should also start with lowercase letters. Class names and functions should begin with capital letters. It makes it difficult to read because I and many others have been trained to see capital letters and think CLASS NAME instead of POSSIBLE VARIABLE NAME. Just FYI
Finally, some of your methods take multiple parameters, like the one above. You really should add a prefix to each parameter so that it's easy to understand what the parameter is for. This:
[vdb SelectAsyncRequest: PARAMETER : PARAMETER];
would look much better if it was :
[vdb selectAsyncRequestForParameter: PARAMETER withOtherParameter:OTHERPARAM];
EDIT: I also don't think you need so many autorelease pools. The entire thing is wrapped in a big autorelease pool already.
EDIT2: I also see a lot of release calls that aren't necessary. In your UploadChunkWise method you are calling release on *data and *returnedData which are both already implicitly autoreleased. Methods that return objects to you will already have ownership given up and "handed over" to you. Essentially, those methods will do this:
NSData *data = [[NSData alloc] init];
return [data autorelease];
When you get it, if you want to keep it you will have to retain it yourself, otherwise it will be destroyed at the return of your method.
However, it is correct for you to call release on the NSString *result instance you created with -init.

iOS Memory Management & NSString Initialisation

Still learning iOS development with ObjectiveC and iOS, and trying to realy understand memory management! Appreciate any advise on the snippet below, eg:
1) Analyser says there are potential memory leaks, but can't solve them?
2) Should I keep alloc and init the NSStrings in the for loop and when appended to?
Thanks
- (NSString *) lookUpCharNameForID: (NSString *) inCharID
{
debugPrint ("TRACE", [[#"Lookup Char Name for = " stringByAppendingString: inCharID] UTF8String]);
NSString *tempName = [[NSString alloc] initWithFormat: #""];
if (![inCharID isEqualToString: #""])
{
// Potentially lookup multiple values
//
NSString *newName = [[NSString alloc] initWithFormat: #""];
NSArray *idList = [inCharID componentsSeparatedByString: #","];
for (NSString *nextID in idList)
{
NSLog( #"Lookup %i : %#", [idList count], nextID);
newName = [[NSString alloc] initWithFormat: #"C%#", nextID];
// Append strings
if ([tempName isEqualToString: #""])
tempName = [[NSString alloc] initWithFormat: #"%#", newName];
else
tempName = [[NSString alloc] initWithFormat: #"%#+%#", tempName, newName];
}
[newName release];
}
return [tempName autorelease];
}
You don't need any of the calls to alloc, release, or autorelease. Instead, use [NSString stringWithFormat:] to create instances of NSString that you don't own, and therefore don't need to manage. Also, consider using NSMutableString to simplify your code a bit, for example along the lines of the following (untested) version:
- (NSString *) lookUpCharNameForID: (NSString *) inCharID
{
NSMutableString *tempName = nil;
if (![inCharID isEqualToString: #""])
{
NSArray *idList = [inCharID componentsSeparatedByString: #","];
for (NSString *nextID in idList)
{
[tempName appendString:#"+"]; // Does nothing if tempName is nil.
if (tempName == nil)
tempName = [NSMutableString string];
[tempName appendFormat:#"C%#", nextID];
}
}
return tempName;
}
You have 2 alloc initWithFormat for tempName. One before the loop and one within the loop.
Use ARC (Automatic Reference Counting) for new projects. For older projects it may be easy to convert them, if not ARC can be disabled on a file-by-file basis where necessary.
Using a mutable string, autoreleased convience methods and a little rerfactoring:
- (NSString *) lookUpCharNameForID: (NSString *) inCharID
{
NSMutableString *tempName = [NSMutableArray array];
if (inCharID.length)
{
NSArray *idList = [inCharID componentsSeparatedByString: #","];
for (NSString *nextID in idList)
{
if (tempName.length == 0)
[tempName appendFormat: #"%#C", nextID];
else
[tempName appendFormat: #"+%#C", nextID];
}
}
return tempName;
}

Resources