Potential leaks in iOS project - ios

Hello i have this function, that gets dummy data from file in project:
Issues are shown in those lines:
NSString *path = [[NSBundle mainBundle] pathForResource: #"StatisticsDataJSON" ofType: #"TXT"]; - potential leak of an object.
NSMutableDictionary *statisticsResponse = [jsonParser objectWithString:data]; - potential leak of an object stored into 'jsonParser'
for (id key in statisticsResponse) { - potential leak of an object stored into 'statisticArray'
for (int i = 0; i < statsForDate.count; i++) { - potential leak of an object stored into 's'
if (self.statistics==nil)
{
self.statistics = [[NSMutableDictionary alloc]init];
NSString *path = [[NSBundle mainBundle] pathForResource: #"StatisticsDataJSON" ofType: #"TXT"];
NSError *error = nil;
NSString *data = [NSString stringWithContentsOfFile: path
encoding: NSUTF8StringEncoding
error: &error];
//NSLog(#"%#",data);
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSMutableDictionary *statisticsResponse = [jsonParser objectWithString:data];
for (id key in statisticsResponse) {
NSArray *statsForDate = [statisticsResponse objectForKey:key];
NSMutableArray *statisticArray = [[NSMutableArray alloc]init];
for (int i = 0; i < statsForDate.count; i++) {
Statistic *s = [[Statistic alloc]init];
s.locationId = [[statsForDate objectAtIndex:i] objectForKey:#"locationId"];
int value =[[[statsForDate objectAtIndex:i] objectForKey:#"visits"] integerValue];
s.visits = value;
value =[[[statsForDate objectAtIndex:i] objectForKey:#"totalUsers"] integerValue];
s.totalUsers = value;
value= [[[statsForDate objectAtIndex:i] objectForKey:#"uploads"] integerValue];
s.uploads = value;
value = [[[statsForDate objectAtIndex:i] objectForKey:#"downloads"] integerValue];
s.downloads = value;
value = [[[statsForDate objectAtIndex:i] objectForKey:#"apps"] integerValue];
s.apps = value;
[statisticArray addObject:s];
}
[self.statistics setObject:statisticArray forKey:key];
};
}
I have found that autorelease in ststisticsResponse - resolves the problem:
NSMutableDictionary *statisticsResponse = [[jsonParser objectWithString:data]autorelease];
But then something fails in SBJsonStreamParserAccumulator.m in dealoc function.
What is the problem?

Note that the warnings about potential leaks come on the line after the potential leak, because that's the first point at which the referenced object is technically "leaked". So your current fixes are probably over-releasing and causing crashes.
The first statement in your question actually refers to a leak in this line, immediately before:
self.statistics = [[NSMutableDictionary alloc]init];
You've got no further reference to that allocated dictionary, and it's a retained property, so you have a leak.
self.statistics = [[[NSMutableDictionary alloc]init] autorelease];
Will fix that one. The next one, you have to release the jsonParser when you've finished with it (after the parsing is done):
[jsonParser release];
I'm not going to go through all of them but you should get the idea. Basically you need to read the memory management guide, or update to ARC.
Pay attention to the variable names in the warnings. They tell you where the leak is.

Related

Trying to create an array of dictionaries, keep getting the same dictionary repeated in the array

I'm trying to use NSData to pull information out of a text file, and then load it into a dictionary.
First I create a string of the text file, and load each record into an array.
Then I break apart the each record into individual data elements.
The problem I'm having is that when the dictionary is fully populated, I then use addObject to load it into the array, which it does do successfully. The problem is that when the next loop creates a new dictionary, the same dictionary gets loaded into the array, and I end up an array of all the same dictionaries, instead of multiple different dictionary objects.
I'm guessing there is some simple mistake I'm making that is causing this error. Any help would be appreciated.
NSString *clientListFile = [NSURL URLWithString: #"/textfile"];
NSData *clientListDataFile = [NSData dataWithContentsOfFile:clientListFile];
NSString *clientListString = [[NSString alloc]initWithBytes:[clientListDataFile bytes] length:[clientListDataFile length] encoding:NSUTF8StringEncoding];
NSString *returnDelimiter = #"\n";
NSString *commaDelimiter = #",";
NSString *exclamationDelimiter = #"!";
NSArray *keysAndObjects = [[NSArray alloc]init];
NSMutableDictionary *clientList = [[NSMutableDictionary alloc]init];
NSMutableArray *clientListOfDictionaries = [[NSMutableArray alloc]init];
NSArray *sentenceArray = [clientListString componentsSeparatedByString:returnDelimiter];
for (int i = 0; i < [sentenceArray count]; i=i+1) {
[clientList removeAllObjects]; //to start with a fresh dictionary for the next iteration
NSString *recordSentence = [sentenceArray objectAtIndex:i];
NSArray *attributes = [recordSentence componentsSeparatedByString:commaDelimiter];
for (int j = 0; j < [attributes count]; j = j+1) {
NSString *pairsOfItems = [attributes objectAtIndex:j];
//a small arry, of only two objects, the first is the key, the second is the object
keysAndObjects = [pairsOfItems componentsSeparatedByString:exclamationDelimiter];
[clientList setObject:[keysAndObjects lastObject] forKey:[keysAndObjects firstObject]];
}
[clientListOfDictionaries addObject:clientList];
}
When I used NSLog to see what's in the dictionary, I mulitple objects of the same dictionary repeated, even though up earlier in the iteration, I can see that the code is creating separate and unique dictionaries.
Instead of this line
[clientListOfDictionaries addObject:clientList];
you can have
[clientListOfDictionaries addObject:[[NSArray alloc] initWithArray:clientList];
That way you will be adding new arrays to clientListOfDictionaries instead of the same one.
Move this line:
NSMutableDictionary *clientList = [[NSMutableDictionary alloc]init];
to just after the first for loop line and then delete the line:
[clientList removeAllObjects];
It's important to create a new dictionary for each iteration.
You should also delete the following line:
NSArray *keysAndObjects = [[NSArray alloc]init];
and change:
keysAndObjects = [pairsOfItems componentsSeparatedByString:exclamationDelimiter];
to:
NSArray *keysAndObjects = [pairsOfItems componentsSeparatedByString:exclamationDelimiter];
You are allocated and initialising your clientList dictionary outside of the for loop, so you only have one dictionary, which you are storing in your array multiple times. Adding the dictionary to the array does not copy it, it merely adds a pointer to the object.
you need to move
NSMutableDictionary *clientList = [[NSMutableDictionary alloc]init];
inside your first for loop in place of
[clientList removeAllObjects];
Also, componentsSeparatedByString: returns an NSArray, so you don't need to allocate and initialise one. You can simply define the variable -
NSArray *keysAndObjects;
Because you're using the same clientList variable for each iteration of the loop. You need to create a whole new dictionary object each time.
Try this modified code:
NSData *clientListDataFile = [NSData dataWithContentsOfFile:clientListFile];
NSString *clientListString = [[NSString alloc]initWithBytes:[clientListDataFile bytes] length:[clientListDataFile length] encoding:NSUTF8StringEncoding];
NSString *returnDelimiter = #"\n";
NSString *commaDelimiter = #",";
NSString *exclamationDelimiter = #"!";
NSArray *keysAndObjects = nil;
NSMutableArray *clientListOfDictionaries = [[NSMutableArray alloc] init];
NSArray *sentenceArray = [clientListString componentsSeparatedByString:returnDelimiter];
for (NSUInteger i = 0; i < [sentenceArray count]; ++i) {
NSMutableDictionary *clientList = [[NSMutableDictionary alloc] init]; //to start with a fresh dictionary for the next iteration
NSString *recordSentence = [sentenceArray objectAtIndex:i];
NSArray *attributes = [recordSentence componentsSeparatedByString:commaDelimiter];
for (NSUInteger j = 0; j < [attributes count]; ++j) {
NSString *pairsOfItems = [attributes objectAtIndex:j];
//a small arry, of only two objects, the first is the key, the second is the object
keysAndObjects = [pairsOfItems componentsSeparatedByString:exclamationDelimiter];
[clientList setObject:[keysAndObjects lastObject] forKey:[keysAndObjects firstObject]];
}
[clientListOfDictionaries addObject:clientList];
}
An alternate option, though likely less efficient, is to to change the line:
[clientListOfDictionaries addObject:clientList];
to
[clientListOfDictionaries addObject:[clientList copy]];
That lets you keep using the same clientList variable, since you're adding a copy of it to the clientListOfDictionaries array. I just point that out because it might help you understand what's going on.
Also, note that I changed this line for you:
NSArray *keysAndObjects = [[NSArray alloc]init];
to
NSArray *keysAndObjects = nil;
Because it's just a pointer that is set by your call to componentsSeparatedByString, you don't need to allocate an array for it. That array will just vanish in your first iteration of the loop.
Should be added the new dictionary to array. Otherwise it will not add to an array. Every object in array have same dictionary mapping. So it will give you the same dictionary value. Create new dictionary for every object and add to array.
for (int i = 0; i < [sentenceArray count]; i=i+1) {
NSMutableDictionary *clientList = [[NSMutableDictionary alloc]init];
NSString *recordSentence = [sentenceArray objectAtIndex:i];
NSArray *attributes = [recordSentence componentsSeparatedByString:commaDelimiter];
for (int j = 0; j < [attributes count]; j = j+1) {
NSString *pairsOfItems = [attributes objectAtIndex:j];
//a small arry, of only two objects, the first is the key, the second is the object
NSArray *keysAndObjects = [pairsOfItems componentsSeparatedByString:exclamationDelimiter];
[clientList setObject:[keysAndObjects lastObject] forKey:[keysAndObjects firstObject]];
}
[clientListOfDictionaries addObject:clientList];
}

make retain count in ARC

I am using an external library in my project which is being build in an ARC environment. As per the library the socket object gets deallocated only when the retain count=0. As far as I know its not liable to use retain count in ARC but I am forced to remove all the reference of the socket object which is not possible in my project. How can I resolve this issue? A gist of code issue is below:
-(void)callConnect{
for(int i = 0; i<[userArray count];i++){
[self connect:(NSString*)[userArray objectAtIndex:i]];
}
}
-(void)connect:(NSString *)username{
RTMPCLient *socket = [[RTMPClient alloc] init];
BroadCastClient *stream = [[BroadCastClient alloc] initWithClient:socket];
NSMutableDictionary *stream = [NSMutableDictionary dictionaryWithObject:stream forKey:username];
}
-(void)disconnect{
for(int i = 0; i<[userArray count];i++){
[stream objectForKey:[NSString stringWithFormat:#"%#",[userArray objectAtIndex:i]]] = nil; //error on this line
BroadCastClient *tempStream = [stream objectForKey:[userArray objectAtIndex:i]];
tempStream = nil;
}
}
I am trying to make the stream object nil which gives an error. Cannot save it another variable as it increases the references of socket object.By making the tempStream nil doesn't affect the original instance created.
I want to remove the reference of socket object from stream in the disconnect method. How can I do so?
ARC will put the invisible release message in your code (in connect), but the array will have strong reference on them, so they will stay in memory. All you have to do in disconnect remove all the objects from your collection ([stream removeAllObjects] and [userArray removeAllObjects]) and the collection will release them.
UPDATE:
By following your code I see the following:
In this code you are creating an instance of BroadCastClient and adding it to NSDictionnary (stream), but NSDictionary has no reference to it, so it will be deallocated after the method call
-(void)callConnect{
for(int i = 0; i<[userArray count];i++){
[self connect:(NSString*)[userArray objectAtIndex:i]];
}
}
-(void)connect:(NSString *)username{
RTMPCLient *socket = [[RTMPClient alloc] init];
BroadCastClient *stream = [[BroadCastClient alloc] initWithClient:socket];
NSMutableDictionary *stream = [NSMutableDictionary dictionaryWithObject:stream forKey:username];
}
Now here the disconnect stream dictionary (I don't know what is this object, because in your code I don't see any creating or adding to it) the object BroadCastClient is retained by the dictionary, so just removing this object from the dictionary will free it from memory (assuming you have no other strong reference to it)
-(void)disconnect{
for(int i = 0; i<[userArray count];i++){
[stream objectForKey:[NSString stringWithFormat:#"%#",[userArray objectAtIndex:i]]] = nil; //error on this line
BroadCastClient *tempStream = [stream objectForKey:[userArray objectAtIndex:i]];
tempStream = nil;
}
}
I would recommend some refactoring for your code, but before that please have some time to read this guid: https://developer.apple.com/library/mac/documentation/cocoa/conceptual/memorymgmt/Articles/mmPractical.html
IN ARC, you have to just make the objects to nil to maintain RC. So you can do it in the following way.
-(void)disconnect{
socket = nil;
stream = nil;
stream = nil;
}
-(void)connect:(NSString *)username{
if (socket != nil )
socket = nil;
RTMPCLient *socket = [[RTMPClient alloc] init];
if (stream != nil )
stream = nil;
BroadCastClient *stream = [[BroadCastClient alloc] initWithClient:socket];
NSMutableDictionary *stream = [NSMutableDictionary dictionaryWithObject:stream forKey:username]; // Make it using alloc...then you must use nil only
}
It looks like stream is an instance variable of type NSMutableDictionary *. So if you want to remove the references in your stream dictionary, you could do it like this:
- (void)disconnect {
for (int i = 0; i<[userArray count]; i++) {
[stream removeObjectForKey:[userArray objectAtIndex:i]];
}
}
// Alternative version using Fast Enumeration:
- (void)disconnect {
for (id key in userArray) {
[stream removeObjectForKey:key];
}
}
But if all you want to do is remove all references from stream, simply do:
- (void)disconnect {
[stream removeAllObjects];
}

iOS - App crash without error during loop process

during a loop process, my App crash without error. The array count is equal to 175260. With profiler I don't have leaks, so I don't know why the App exit, maybe the CPU usage 100% during a lot of time?
Thank you for your help.
Just this code following crash the App :
for(unsigned int i = 0; i <14;i++)
{
if(findSensor[i]==YES)
{
for(unsigned int j = 1; j <[array count];j++)
{
#autoreleasepool {
if([[[[array objectAtIndex:j] componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
//Code here
}
}
}
}
}
The full code is :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = [NSString stringWithFormat:#"%#/%#",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase];
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:fileName];
NSFileHandle *output = [NSFileHandle fileHandleForReadingAtPath:[NSString stringWithFormat:#"%#/%#10",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase]];
if(output == nil)
{
NSManagedObjectContext *context = [self managedObjectContext];
_recordlocal = [NSEntityDescription insertNewObjectForEntityForName:#"RECORD" inManagedObjectContext:context];
_recordlocal.date = [ibNavSettings interfaceSettings].selectedFileToDataBase;
NSData *inputData = [NSData dataWithData:[fh readDataToEndOfFile]];
NSString *inputString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding];
NSArray *array = [[NSArray alloc] initWithArray:[inputString componentsSeparatedByString:#"\n"]];
for(unsigned int i = 0; i <14;i++)
{
if(findSensor[i]==YES)
{
[[NSFileManager defaultManager] createFileAtPath:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)] contents:nil attributes:nil];
NSMutableString *saveString = [[NSMutableString alloc] init];
int count = 0;
for(unsigned int j = 1; j <[array count];j++)
{
#autoreleasepool {
if([[[[array objectAtIndex:j] componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
[saveString appendString:[array objectAtIndex:j]];
[saveString appendString:#"\n"];
if(i == 0)
count++;
progress++;
pourcent = progress/total;
load = pourcent*100;
if(load%5==0)
[self performSelectorInBackground:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
}
}
}
[saveString writeToFile:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)] atomically:YES encoding:NSUTF8StringEncoding error:nil];
if(i == 0)
_recordlocal.count = [[NSNumber alloc] initWithInt:(count/50)];
}
}
_recordlocal.load = [[NSNumber alloc] initWithBool:YES];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Core data error %#, %#", error, [error userInfo]);
abort();
}
I would guess that your app is crashing without a readable exception because it is running out of available RAM, especially since you indicated that it is running through a large number of iterations.
For a test, I would recommend doing what Rikkles suggests with the autorelease pool. In addition, since the value of i (and as a result the comparison string) rarely changes, I would create that string outside the j loop as well. This would avoid the creation of a lot of extra strings laying around.
Beyond that, since it appears that you are looking for a string at the beginning of a string that is delimited by a semicolon, I would recommend instead of doing componentsSeparatedByString and then examining element zero that you use the NSString method hasPrefix to check for the condition you are looking for.
Here is an example:
for(unsigned int i = 0; i <14;i++)
{
NSString *searchString = [NSString stringWithFormat:#"%d;", 10*(i+1)];
if(findSensor[i]==YES)
{
for(unsigned int j = 1; j <[array count];j++)
{
if([[array objectAtIndex:j] hasPrefix:searchString])
{
//Code here
}
}
}
}
(I hope this compiles and runs, if it doesn't it should require more than minor tweaks. I am away from my Mac right now.)
If this doesn't help, then something going on inside //Code here must be the culprit.
Why are you creating [array count] autoreleasepools? What's the point of creating so many of them? It could crash because of that. Put the #autoreleasepool outside the for loop.
The only reason I could think that you would do that is if you create so many transient objects inside each iteration of the for loop that you'd want to get rid of them as soon as you got out of the iteration. But there are other ways to do that, including reusing those objects within each iteration.
First suggestion
Just use fast enumeration for the inner loop, you aren't actually using the index 'j' for anything
https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/Enumeration.html
Second suggestion
Put some NSLog's in place, it will slow everything down, but you need to figure out what point you are failing at. That will help point everyone in the right direction.
Third suggestion
Actually use NSError objects and output their value if an error is thrown:
NSError *writeError = nil;
[saveString writeToFile:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)]
atomically:YES
encoding:NSUTF8StringEncoding
error:&writeError];
if(error != nil) NSLog(#"error writing file: %#", [[writeError userInfo]description]);
Fourth suggestion
You appear to try to be updating the UI from a background thread. This will not work or will cause a crash. UI code can only be called from a main thread. So dont do this:
[self performSelectorInBackground:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
If you are already on a background thread this will probably crash because you are creating threads on threads on threads. You instead would want to call:
[self performSelectorOnMainThread:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
Fifth suggestion
You may be going over the maximum length for NSString (it's big but I did it once on accident before). You should probably just be appending the file on each iteration of the loop instead, so you don't have an ever growing NSMutableString:
NSString *path = [NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)]
NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:filePath];
NSData *newLine = [#"\n" dataUsingEncoding:NSUTF8StringEncoding];
for(NSString *rowString in array)
{
if([[[rowString componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
NSData *stringData = [rowString dataUsingEncoding:NSUTF8StringEncoding];
[fh truncateFileAtOffset:[fh seekToEndOfFile]];
[fh writeData:stringData];
[fh truncateFileAtOffset:[fh seekToEndOfFile]];
[fh writeData:newLine];
if(i == 0)
count++;
progress++;
pourcent = progress/total;
load = pourcent*100;
if(load%5==0)
[self performSelectorOnMainThread:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
}
}
}
And this has the added benefit of helping you ditch the autoreleasepools
This was invalid
If your array does in fact have 175260 rows, that is probably your issue. You are looping using unsigned int as your index var. Unsigned ints in c only have a max value of 65535. Use an unsigned long int, max 4294967295.

csv to core data method error 2012

I have read a few posts and found a method. I adapted it to my app, however at [self saveContext]; I get a bad instruction error.
Please let me know why, and if this method makes sense. Also I am using \r because in the csv each item is in its own row.
Thank you in advance
-(void)addData{
NSString *paths = [[NSBundle mainBundle] resourcePath];
NSString *bundlePath = [paths stringByAppendingPathComponent:#"coredatainfo.csv"];
NSString *dataFile = [[NSString alloc] initWithContentsOfFile:bundlePath];
NSArray *dataRows = [dataFile componentsSeparatedByString:#"\r"];
[dataFile release];
FirstCDitem *myItem;
for (int i = 0 ; i < [dataRows count] ; i++)
{
NSArray *dataElements = [[dataRows objectAtIndex:i] componentsSeparatedByString:#","];
NSLog(#"Added: %d %#",i,dataElements);
myItem = (FirstCDitem *)[NSEntityDescription insertNewObjectForEntityForName:#"FirstCDitem" inManagedObjectContext:[self managedObjectContext]];
[FirstCDitem setTitle:[dataElements objectAtIndex:i]];
[self saveContext];
}
}
There are two problems in this code line:
[FirstCDitem setTitle:[dataElements objectAtIndex:i]];
i is the current row number, so it probably makes no sense to use it as index to the dataElements array. Perhaps you want the first element in the row?
setTitle must sent to the instance myItem, not to the class FirstCDItem.
So the line should look like this:
[myItem setTitle:[dataElements objectAtIndex:0]];

iOS Dictionary causes memory leak

Why would the following implementation of the Dictionary cause a memory leak? See the screenshot below as well. Practically all of the leaks there are from this method.
- (void) setLocation:(NSString *) location:(NSString *) turnPage {
NSLog(#"Start setLocation");
//---get the path to the property list file---
NSString *localPlistFileNameConf = [[self documentsPath] stringByAppendingPathComponent:#"Config.plist"];
NSMutableDictionary *copyOfDict;
//---if the property list file can be found---
if ([[NSFileManager defaultManager] fileExistsAtPath:localPlistFileNameConf]) {
//---load the content of the property list file into a NSDictionary object---
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:localPlistFileNameConf];
//---make a mutable copy of the dictionary object---
copyOfDict = [dict mutableCopy];
[dict release];
}
else {
//---load the property list from the Resources folder---
NSString *pListPath = [[NSBundle mainBundle] pathForResource:#"Config" ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:pListPath];
//---make a mutable copy of the dictionary object---
copyOfDict = [dict mutableCopy];
[dict release];
}
location = [self checkLocationValidity:location:turnPage];
[copyOfDict setValue:location forKey:#"Location"];
[self writeConfigToFile:copyOfDict];
NSLog(#"End setLocation");
}
You're not releasing copyOfDict anywhere. You own any object created with a method that starts with copy, so you need to release those objects. It's probably misreporting the source as the original dictionary due to a bit of trickery in the NSDictionary class cluster for efficiency reasons. Try running analyse over your code, it should point these things out to you.

Resources