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];
}
Related
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.
I'm using the following piece of code to store elements in array dynamically and retrieve it later
for (int i = 0; i< [companyNames count]; i++){
testimonialsArray = [[NSMutableArray alloc]init];
testimonialsComplementedArray = [[NSMutableArray alloc]init];
[testimonialsArray addObject:companyNames[i] ];
[testimonialsComplementedArray addObject:texts[i]];
NSLog(#"Compliments %#",testimonialsComplementedArray);
}
But it prints only the last copy that had been added to the array. How to retrieve all elements?
Following are the company names,
"General Marketing Company",
"United Enterprises, Chennai",
"Hari Match Industries"
allocate the testimonialsArray = [[NSMutableArray alloc]init]; in above the for loop if you initilize in inside the loop every time the memory will be initilize, so final value only you get on output in finally , do like
testimonialsArray = [[NSMutableArray alloc]init];
testimonialsComplementedArray = [[NSMutableArray alloc]init]
for (int i = 0; i< [companyNames count]; i++){
[testimonialsArray addObject:companyNames[i] ];
[testimonialsComplementedArray addObject:texts[i]];
}
NSLog(#"Compliments %#",testimonialsComplementedArray);
testimonialsArray = [[NSMutableArray alloc]init]; // have to init out side the loop
testimonialsComplementedArray = [[NSMutableArray alloc]init]; // have to init out side the loop
for (int i = 0; i< [companyNames count]; i++){
[testimonialsArray addObject:companyNames[i] ];
[testimonialsComplementedArray addObject:texts[i]];
}
NSLog(#"Compliments %#",testimonialsComplementedArray);
You are allocating mutable array every time in for loop because of that its initializes array and add object in that everytime.Always its better to alloc any object in ViewDidLoad method.
Try like this.
testimonialsArray = [[NSMutableArray alloc]init];
testimonialsComplementedArray = [[NSMutableArray alloc]init];
for (int i = 0; i< [companyNames count]; i++){
[testimonialsArray addObject:companyNames[i] ];
[testimonialsComplementedArray addObject:texts[i]];
}
NSLog(#"Compliments %#",testimonialsComplementedArray);
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];
}
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: ]
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.