I have my sqlite execute query code as below. Instruments catches a memory leak in the NSDictionary alloc and release lines (inside the while loop). Can someone point out what is wrong in those alloc/release ?
- (NSMutableArray *)executeQuery:(NSString *)sql arguments:(NSArray *)args {
sqlite3_stmt *sqlStmt;
if (![self prepareSql:sql inStatament:(&sqlStmt)])
return nil;
int i = 0;
int queryParamCount = sqlite3_bind_parameter_count(sqlStmt);
while (i++ < queryParamCount)
[self bindObject:[args objectAtIndex:(i - 1)] toColumn:i inStatament:sqlStmt];
NSMutableArray *arrayList = [[NSMutableArray alloc]init]; // instrument marks leak 0.1 % on this line
NSUInteger rcnt= arrayList.retainCount;
int columnCount = sqlite3_column_count(sqlStmt);
while ([self hasData:sqlStmt]) {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; // instrument marks leak 13% on this line
for (i = 0; i < columnCount; ++i) {
id columnName = [self columnName:sqlStmt columnIndex:i];
id columnData = [self columnData:sqlStmt columnIndex:i];
[dictionary setObject:columnData forKey:columnName];
}
[arrayList addObject:dictionary];
[dictionary release];// instrument marks leak 86.9 % on this line
}
sqlite3_finalize(sqlStmt);
rcnt=arrayList.retainCount;
return arrayList ;
}
Any help/pointer is very much appreciated. Have been struggling with this for few days.
If you are not using ARC then I suggest you change your return line for:
return [arrayList autorelease];
Otherwise you are probably leaking the complete arrayList.
Related
I have checked same question in StackOverflow
but I can't understand how it's related in my scenario? Please check my code
UITextField *textFieldData=(UITextField *)textField;
NSMutableDictionary *dictFields = [[NSMutableDictionary alloc] init];
[dictFields setObject:[[dictCustomFieldGroups valueForKey:#"name"] objectAtIndex:textFieldData.tag] forKey:#"fieldname"];
[dictFields setObject:textFieldData.text forKey:#"fieldvalue"];
[dictFields setObject:[NSString stringWithFormat:#"%ld",(long)textFieldData.tag] forKey:#"tagvalue"];
if ([arrFields count] > 0) {
for (int i = 0; i< [arrFields count]; i++) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
dict = [arrFields objectAtIndex:i];
if ([[dict valueForKey:#"tagvalue"] isEqualToString:[NSString stringWithFormat:#"%ld",(long)textFieldData.tag]]) {
[arrFields replaceObjectAtIndex:i withObject:dictFields];
break;
} else {
[arrFields addObject:dictFields];
break;
}
}
} else {
[arrFields addObject:dictFields];
}
I am getting this warning in my for loop.
You have written break in both if and else condition. Thus, the loop breaks, after running once - irrespective of your condition being correct or false.
Additionally as pointed out in the comment, you should not modify the array while it is being used in a loop - it might work in this case, given you are replacing, however, it can lead to crashes etc, if not very careful. Infact, in else condition you are adding extra elements, which is definitely going to disturb the iteration.
I was told that #autoreleasepool block in loop can reduce the peak of memory usage until I do a test. The test device is iPhone 6s with iOS 11.4.1.
my code:
#implementation BigMemObj{
NSMutableArray *_mutArr;
}
-(instancetype)init{
if(self = [super init]){
_mutArr = [[NSMutableArray alloc] initWithCapacity:1024*1024*30];
for(int i = 0; i < 1024*1024*30; i++){
[_mutArr addObject:#(i)];
}
}
return self;
}
- (void)viewDidLoad {
NSMutableArray *arr = [[NSMutableArray alloc] init];
for(int i = 0 ; i < 10000; i++){
#autoreleasepool {
BigMemObj *mem = [[BigMemObj alloc] init];
}
}
}
- (void)viewDidLoad {
NSMutableArray *arr = [[NSMutableArray alloc] init];
for(int i = 0 ; i < 10000; i++){
BigMemObj *mem = [[BigMemObj alloc] init];
}
}
I run both test 34 seconds, in test 1, the highest memory usage is 458M, but in the test 2 the highest memory usage is 362M. and both test have a triangle shape.
with #autoreleaspool block
without #autoreleaspool block
Did the autoreleasepool implementation changed? or the compiler dose some optimization?
Thank You!
It all looks normal actually. The growth you are seeing is this part:
_mutArr = [[NSMutableArray alloc] initWithCapacity:1024*1024*30];
for(int i = 0; i < 1024*1024*30; i++){
[_mutArr addObject:#(i)];
}
So here you are adding your numbers to an array _mutArr and you are adding 1024*1024*30 of them. When this loop finishes the _mutArr is valid and full, it retains all of those numbers. This would not even be changed by adding another autorelease pool within this loop because your array will not let those numbers be released.
Now after this constructor is being called you have
#autoreleasepool {
BigMemObj *mem = [[BigMemObj alloc] init];
}
so autorelease pool will be drained in this moment releasing all the numbers inside BigMemObj instance mem and your memory is back to normal.
You might expect that your application should keep growing in memory without the call to #autoreleasepool. But there is no change at all if you remove that call. The reason for that is that none of your code uses autorelease pool at all. What your code translates to (non-ARC) is:
NSMutableArray *arr = [[NSMutableArray alloc] init];
for(int i = 0 ; i < 10000; i++){
#autoreleasepool {
BigMemObj *mem = [[BigMemObj alloc] init];
[mem release];
}
}
[arr release];
But you would need your autoreleasepool only if it was
NSMutableArray *arr = [[NSMutableArray alloc] init];
for(int i = 0 ; i < 10000; i++){
#autoreleasepool {
BigMemObj *mem = [[[BigMemObj alloc] init] autorelease];
}
}
[arr release];
To have a situation where autorelease pool is needed:
NSMutableArray *allBigValues = [[NSMutableArray alloc] init];
NSString *path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"profile.png"];
for(int i = 0; i<100000; i++){
#autoreleasepool {
[allBigValues addObject:[UIImage imageWithContentsOfFile:path]];
[allBigValues removeAllObjects];
}
}
If in this code you remove the autorelease pool it will grow in memory until the loop ends. The reason for this is because imageWithContentsOfFile is using autorelease pool and all the images produced by this method will only be released after the pool is drained. Since the main pool will not be released inside the loop we need to create another one.
Bottom line this code works nicely but as soon as you remove the #autoreleasepool part it will start growing in memory and probably crash you application.
Note 1: You need to add image profile.png into your app for this code to work (just drag it among source files, not the assets).
Note 2: We use "drain" where it comes to pools because this used to be the name of the method you needed to call when you wanted for the pool to remove it's objects. This is how it used to be:
for(int i = 0; i<100000; i++){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[allBigValues addObject:[UIImage imageWithContentsOfFile:path]];
[allBigValues removeAllObjects];
[pool drain];
}
Basically when you call
[[NSMutableDictionary alloc] init];
Apple will create a dictionary of some default size. What is that default size?
Interestingly, on iOS at least it appears that Apple does the same thing for init as if it were initWithCapacity:0. I ran the following code under Instruments:
int max=1000000;
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:max];
for(int i=0; i < max; i++) {
[array addObject:[[NSMutableDictionary alloc] init]];
}
if(true) return array; // Don't let the compiler remove the ref
Next I did something very similar but with 0 capacity explicitly specified:
int max=1000000;
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:max];
for(int i=0; i < max; i++) {
[array addObject:[[NSMutableDictionary alloc] initWithCapacity:0]];
}
if(true) return array; // Don't let the compiler remove the ref
Both of these ran with a max consumption of 55.3 MB on my iOS 9 device. Then I tried using initWithCapacity:1 when creating the dictionaries:
int max=1000000;
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:max];
for(int i=0; i < max; i++) {
[array addObject:[[NSMutableDictionary alloc] initWithCapacity:1]];
}
if(true) return array; // Don't let the compiler remove the ref
In that case my max consumption was 116.4 MB.
As other commenters have noted, this may vary from OS to OS and even from version to version. Don't rely on it, but that's one way to tell what NSMutableDictionary init is doing.
So, I'm kind of new to Obj-c, and I have a strange crash happening with the following code :
- (NSMutableArray*)followNonBlackPixels:(int)startX withY:(int)startY
{
NSMutableArray* result;
NSMutableArray* adjacents = [self getAdjacents:startX withY:startY];
int r = 0;
int i = 0;
int tempX;
int tempY;
int max = [adjacents count];
CGPoint tempPoint;
while(i < max)
{
int tempX = (int)[[adjacents objectAtIndex:i] CGPointValue].x;
int tempY = (int)[[adjacents objectAtIndex:i] CGPointValue].y;
result = [self getAdjacents:tempX withY:tempY];
for(r = 0; r < [result count]; r++)
{
tempPoint = [[result objectAtIndex:r] CGPointValue];
//[adjacents addObject:[NSValue valueWithCGPoint:CGPointMake(tempPoint.x, tempPoint.y)]];
}
i++;
max = [adjacents count];
}
return adjacents;
}
This code runs fine, but as soon as I uncomment the line where I add an object to the "adjacents" NSMutableArray, then the program crashes.
The signature of the getAdjacents method is as follows:
- (NSMutableArray*)getAdjacents:(int)startX withY:(int)startY;
I'm developing a Cordova plugin under Windows so I do not have any debug info to provide... But maybe my mistake would be clear to an experienced obj-c developer ?
Thanks a lot for you help !
If you change your code to the following,
NSMutableArray *adjacents = [[NSMutableArray alloc] initWithArray:[[self getAdjacents:startX withY:startY] mutableCopy]];
I'm a total noob and need help with a method I'm writing.
The method creates a deck of cards (using NSMutableArray). I'm first experimenting with loading the array with numbers 1-13 randomly (each number appearing once).
When I run a simple test program to print the values in the array, I get a "Build Successful" but an error once the program starts. The error says "[__NSArrayM insertObject:atIndex:]: object cannot be nil".
Once I understand what I'm doing wrong, I can then expand on the method properly. Thanks!
NB: This is my first post. Is this type of question ok?
- (void) createDeck {
int r;
BOOL same;
deck = [[NSMutableArray alloc]init];
NSNumber *randNum;// = nil;
randNum = [[NSNumber alloc]init];
[randNum initWithInt: (arc4random()%13)+1];
[deck addObject: randNum]; // First card added to deck
same = FALSE;
while (!same) {
for (int i=1; i<13; i++) {
same = FALSE;
for (r=0; r<=i; r++) {
[randNum initWithInt: (arc4random()%13)+1];
if ([deck objectAtIndex:r] == [deck objectAtIndex:i]) {
same = TRUE;
}
[deck addObject: randNum]; // Next card added to deck
}
}
}
}
you cannot re-init randNum:
NSNumber *randNum;// = nil;
randNum = [[NSNumber alloc]init];
[randNum initWithInt: (arc4random()%13)+1];
and the third line is missing the assignment anyway. just do:
NSNumber *randNum = [[NSNumber alloc] initWithInt:(arc4random()%13)+1];
and put that inside the inner for loop, like this:
BOOL same = FALSE;
NSMutableArray *deck = [[NSMutableArray alloc] initWithCapacity:13];
[deck addObject:[[NSNumber alloc] initWithInt:(arc4random()%13)+1]]; // First card added to deck
while (!same) {
for (int i = 1; i < 13; i++) {
same = FALSE;
for (int r = 0; r <= i; r++) {
NSNumber *randNum = [randNum initWithInt:(arc4random()%13)+1]; // modern ObjC will assign a register for this outside the while loop, but restrict the variable's scope to the inner-most loop
if ([deck objectAtIndex:r] == [deck objectAtIndex:i])
same = TRUE;
[deck addObject: randNum]; // Next card added to deck
}
}
Note that I haven't thought through the logic of what you are trying to do here, I've only attempted to resolve the NULL object reference. The error was referring to the first line [deck addObject: randNum] outside of the loop.
Try to use this line of code where you using NSNumber
NSNumber * randNum = [NSNumber numberWithInt: (arc4random%13)+1];
Instead of
[[NSNumber alloc] initWithInt: ]