In my ARC iOS app I am running a for loop that ends up with a large memory allocation overhead. I want to somehow end my for loop with minimal/no extra memory allocated. In this instance I am using the SSKeychain library which lets me fetch things from a keychain. I usually just use autorelease pools and get my memory removed properly but here I don't know what is wrong because I end up with 70 mb + of memory allocated at the end of the loop. I have been told that I should start/end a run loop to properly deal with this. Thoughts?
for (int i = 0; i < 10000; ++i) {
#autoreleasepool {
NSError * error2 = nil;
SSKeychainQuery* query2 = [[SSKeychainQuery alloc] init];
query2.service = #"Eko";
query2.account = #"loginPINForAccountID-2";
query2.password = nil;
[query2 fetch:&error2];
}
}
What are you using to measure memory usage?
Results of a very simple test...
Running in the simulator, measure only resident memory before and after.
Without autoreleasepool...
Started with 27254784, ended with 30212096, used 2957312
With autoreleasepool...
Started with 27316224, ended with 27443200, used 126976
Obviously, the autoreleasepool is preventing memory from growing too bad, and I don't see anything close to 70MB being used under any circumstance.
You should run instruments and get some good readings on the behavior.
Here is the code I hacked and ran...
The memchecker
static NSUInteger available_memory(void) {
NSUInteger result = 0;
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size) == KERN_SUCCESS) {
result = info.resident_size;
}
return result;
}
And the code...
#define USE_AUTORELEASE_POOL 1
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
dispatch_async(dispatch_get_main_queue(), ^{
NSUInteger beginMemory = available_memory();
for (int i = 0; i < 10000; ++i) {
#ifdef USE_AUTORELEASE_POOL
#autoreleasepool
#endif
{
NSError * error2 = nil;
SSKeychainQuery* query2 = [[SSKeychainQuery alloc] init];
query2.service = #"Eko";
query2.account = #"loginPINForAccountID-2";
query2.password = nil;
[query2 fetch:&error2];
}
}
NSUInteger endMemory = available_memory();
NSLog(#"Started with %u, ended with %u, used %u", beginMemory, endMemory, endMemory-beginMemory);
});
return YES;
}
Related
I'm having trouble understanding how #autoreleasepool work. Consider the following example in which I am creating an AVAssetReader for an audiofile. To make the memory impact matter, I repeated this step 1000 times.
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
void memoryTest() {
NSURL *url = [[NSURL alloc] initWithString:#"path-to-mp3-file"];
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset error:NULL];
}
int main(int argc, const char * argv[]) {
// Breakpoint here (A)
#autoreleasepool {
for(int i = 0; i < 1000; i++) {
memoryTest();
}
}
// Breakpoint here (B)
return 0;
}
Before and after the loop at breakpoint A and breakpoint B I took a look at the memory usage of my application in the Xcode Debug Navigation. At point A my App consumes about 1.5MB of memory. At point B it is about 80MB. Interestingly the memory usage at B drops to about 4MB when I put the autoreleasepool inside the loop like so:
for(int i = 0; i < 1000; i++) {
#autoreleasepool { memoryTest(); }
}
Why does the position matter? (The breakpoint - in both cases - is outside of the autoreleasepool!) In either case the consumed memory at point B is proportional to the number of loops. Am I missing something else here to free up its memory?
I thought of the memory chart in the navigator to be delayed, but adding usleep just before the breakpoint does not change anything. If I change memoryTest() to
void memoryTest() {
NSURL *url = [NSURL URLWithString:#"path-to-mp3-file"];
}
the position of #autoreleasepool does not matter. Isn't that weird?
I am using Xcode 6, OS X SDK 10.10 with ARC enabled.
Temporary memory is being created and autorelease'ed by the code inside the loop by your code and the API calls it makes. This memory is not released until the pool is drained. The usual point it is is drained is in the run loop. But your loop does not allow the run loop to execute so by placing the #autoreleasepool inside the loop the pool can drain as your code runs.
I am writing a small program for printing to the console every few seconds.
The objective is to call a function on each of ten objects in an array every N seconds, where n is a class variable. I was wondering how I could incorporate such a timer loop into my code. Here is what I have. I would ver much appreciate a response. Thanks.
ALHuman.m
static NSInteger barkInterval = 3;
#implementation ALHuman
-(void)setMyDog:(ALDog *)dog{
myDog = dog;
}
-(ALDog *)getMyDog{
return myDog;
}
+(NSInteger)returnBarkInterval {
return barkInterval;
}
-(void)createDog{
ALDog *aDog = [[ALDog alloc]init];
char dogName [40] = " ";
NSLog(#"Please enter a name for %s's dog",[self name]);
scanf("%s",dogName);
[aDog setName:dogName];
char barkSound [40] = "";
NSLog(#"Please enter a bark sound for dog: %s",[aDog name]);
scanf("%s",barkSound);
[myDog setBarkSound:barkSound];
[myDog setCanBark:YES];
[self setMyDog:aDog];
}
-(void)callDog:(NSInteger)numberOfResponses {
NSLog(#"%s",[[self getMyDog] name]);
[[self getMyDog] bark:numberOfResponses];
}
-(NSInteger)getRandomNumberBetween:(NSInteger)from to:(NSInteger)to {
return (NSInteger)from + arc4random() % (to-from+1);
}
-(void)timerFireMethod:(NSTimer *)timer {
[self callDog:[self getRandomNumberBetween:1 to:5]];
}
#end
main.m
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSMutableArray *people = [[NSMutableArray alloc]init];
for (int i = 0; i < 10; i++) { // I didn't want to create a class method for creating these humans and their dogs, because it is unneccessary.
ALHuman *person = [[ALHuman alloc]init];
// NameGenerator *name = [[NameGenerator alloc]init]; I need to work on implementing this
[person setName:"Bob"];
[person createDog];
[person setHeight:[person getRandomNumberBetween:5 to:8]];
[people addObject:person];
}
NSLog(#"%#",people);
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; //Here is where I am having trouble
for(ALHuman *human in people){
[NSTimer timerWithTimeInterval:[ALHuman returnBarkInterval] target:human selector:#selector(timerFireMethod:) userInfo:NULL repeats:YES];
}
return 0;
}
}
First, +[NSTimer timerWithTimeInterval:...] creates and returns a new timer object, but you're ignoring the return value. So, those timer objects are just lost and useless. You either want to manually schedule them into the run loop or use +scheduledTimerWithTimeInterval:... instead.
Second, you allow execution to flow to the return statement, which exits the main() function. When that happens, the process is terminated. It doesn't bother waiting for any timers to fire, or anything else for that matter.
If you want to wait and allow those timers to fire, you need to manually run the run loop (since you're not in the main thread of an application, which would run the run loop for you). You can invoke [myRunLoop run], [myRunLoop runUntilDate:someDate], or build a loop around an invocation of [myRunLoop runMode:someMode beforeDate:someDate]. It depends on under what circumstances you want the program to exit, if ever.
Use this simple runloop controller class.
(untested):
int main(int argc, const char * argv[])
{
#autoreleasepool {
RunLoopController *runLoopController = [RunLoopController new];
[runLoopController register];
NSMutableArray *people = [NSMutableArray new];
NSMutableArray *timers = [NSMutableArray new];
for (int i = 0; i < 10; i++) {
ALHuman *person = [[ALHuman alloc]init];
// NameGenerator *name = [[NameGenerator alloc]init]; I need to work on implementing this
[person setName:"Bob"];
[person createDog];
[person setHeight:[person getRandomNumberBetween:5 to:8]];
[people addObject:person];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:[ALHuman returnBarkInterval]
target:person
selector:#selector(timerFireMethod:)
userInfo:nil
repeats:YES];
[timers addObject:timer];
}
NSLog(#"%#",people);
while ([runLoopController run])
;
[runLoopController deregister];
}
}
However the issue you face is how to terminate the program when you are finished. You can either install a signal handler, or use some other metric to determine the program has finished, and then call [[RunLoopController mainRunLoopController] terminate].
The RunLoopController uses a simple signalling mechanism (a MACH port) in order to know that the runloop must terminate. Other usage examples exist on the github repo in the above link.
I have a questions about blocks in Objective-C.
For example I have this code:
__block int count = 0;
void (^someFunction)(void) = ^(void){
count = 4;
};
count +=2;
What would be the proper way to write the same piece of code so the count will become 6, not 2 ?!
Thank you!
I should probably show the actual code because my previous question was blurry.
EDIT:
__block CMTime lastTime = CMTimeMake(-1, 1);
__block int count = 0;
[_imageGenerator generateCGImagesAsynchronouslyForTimes:stops
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,
AVAssetImageGeneratorResult result, NSError *error)
{
if (result == AVAssetImageGeneratorSucceeded)
{
NSImage *myImage = [[NSImage alloc] initWithCGImage:image size:(NSSize){50.0,50.0}];
[arrOfImages addObject:myImage];
}
if (result == AVAssetImageGeneratorFailed)
{
NSLog(#"Failed with error: %#", [error localizedDescription]);
}
if (result == AVAssetImageGeneratorCancelled)
{
NSLog(#"Canceled");
}
if (arrOfImages.count > 5)
{
NSLog(#"here");
}
count++;
}];
int f = count+1;
after 10 iterations count is 0...why?!?!
You are not executing the block (calling a block someFunctionmight be a misleading thing)
__block int count = 0;
void (^someBlock)(void) = ^{
count = 4;
};
someBlock();
count +=2;
Call block like this:
someFunction();
So that would be:
__block int count = 0;
void (^someFunction)(void) = ^(void){
count = 4;
};
// call block
someFunction();
count +=2;
Look at the name of the method you are calling; generateCGImagesAsynchronouslyForTimes: completionHandler:.
Asynchronously means that it executes in a different thread (likely via a queue and, as #newaccount points, it may likely be re-scheduled for future execution on the current queue/thread) and the method returns immediately. Thus, when you set f=count+1;, the completion block hasn't even been executed yet because none of the image loads in background threads have been completed.
You need to make a call from that completion block back to your code that needs to respond to the completion. i.e.
^() {
....
dispatch_async(dispatch_get_main_queue(), ^{[self heyManAnImageLoadedDude];});
....
}
Strangest thing I've seen yet.. NSLog is failing within a method based on something bizarre. Here is the code:
-(void) testBoard {
BoardManager* bm = [BoardManager manager];
Board* board = [bm boardForName:#"fourteen by eight"];
NSLog(#"%#", board);
NSLog(#"%#", board.coords);
}
in Board.m :
-(NSArray*) coords {
if(!_coords.count && _definition) {
NSArray* c = [Board decode:_definition];
[self setCoords:c];
}
return _coords;
}
+(NSArray*) decode:(NSString*)encodedCoords {
NSMutableArray* coords = [NSMutableArray array];
NSArray* tokens = [encodedCoords componentsSeparatedByString:#","];
int i = 0;
NSString* dimStr = [tokens objectAtIndex:i++];
int width = [dimStr substringToIndex:2].intValue;
int height = [dimStr substringWithRange:NSMakeRange(2, 2)].intValue;
int depth = [dimStr substringFromIndex:4].intValue;
NSLog(#"w=%d h=%d d=%d", width, height, depth);
NSString* b128;
NSString* b2;
for(int z=0; z<depth; z++) {
for(int y=0; y<height; y++) {
b128 = [tokens objectAtIndex:i++];
NSLog(#"[%#]", b128);
b2 = [Board base128to2:b128];
NSLog(#"b2=%#",b2);
for(int x=0; x<b2.length; x++) {
if([b2 characterAtIndex:b2.length-1-x] == '1') {
Coord* coord = [Coord x:width-1-x y:height-1-y z:z];
[coords addObject:coord];
}
}
}
}
return coords;
}
Now what happens is, none of the NSLog statements within decode: or methods called from decode: will log to the console. However, NSLog before and after calling decode: work, and the rest of the code around the NSLog's executes fine. I found that I could get all the NSLog statements to work simply by commenting out [coords addObject:coord];. This statement appears after NSLog statements that it is affecting. I also found I could get the NSLog's to work by rearranging a few lines in testBoard: like this:
-(void) testBoard
{
BoardManager* bm = [BoardManager manager];
Board* board = [bm boardForName:#"fourteen by eight"];
NSLog(#"%#", board);
NSString* def = board.definition;
NSArray* coords = [Board decode:def];
NSLog(#"%#", coords);
}
This seems quite bizarre..an xcode Loch Ness monster surfacing?!
Have you tried running in the debugger? What happens when you get to that if statement
if(!_coords.count && _definition)
Are you sure _coords.count is 0 and _definition is not nil
In my experience, NSLog macro expansions can fail. Most of the time it's obvious why, sometimes not.
Just do something like this:
id objToLog = /* whatever */;
NSLog(#"%#", objToLog);
If this approach works, you can go ahead with your development and maybe you figure the problem out later.
my first question on Stackoverflow.
Let me start with a bit of code. It's a bit repetitive so I'm going to cut out the parts I repeat for different arrays (feel free to ask for the others). However, please ignore the code in preference to answering the Qs at the bottom. Firstly: thank you to answerers in advance. Secondly: the freeing of data.
#implementation ES1Renderer
GLfloat **helixVertices;
GLushort **helixIndices;
GLubyte **helixColors;
- (void)freeEverything
{
if (helixVertices != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixVertices[i]);
free(helixVertices);
}
if (helixIndices != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixIndices[i]);
free(helixIndices);
}
if (helixColors != NULL)
{
for (int i=0; i < alphasToFree / 30 + 1; i++)
free(helixColors[i]);
free(helixColors);
}
}
(I will get to the calling of this in a moment). Now for where I malloc() the arrays.
- (void)askForVertexInformation
{
int nrows = self.helper.numberOfAtoms / 300;
int mrows = [self.helper.bonds count] / 300;
int alphaCarbonRows = [self.helper.alphaCarbons count] / 30;
helixVertices = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
helixIndices = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
helixColors = malloc(alphaCarbonRows * sizeof(GLfloat *) + 1);
for (int i=0; i < alphaCarbonRows + 1; i++)
{
helixVertices[i] = malloc(sizeof(helixVertices) * HELIX_VERTEX_COUNT * 3 * 33);
helixIndices[i] = malloc(sizeof(helixIndices) * HELIX_INDEX_COUNT * 2 * 3 * 33);
helixColors[i] = malloc(sizeof(helixColors) * HELIX_VERTEX_COUNT * 4 * 33);
}
[self.helper recolourVerticesInAtomRange:NSMakeRange(0, [self.helper.alphaCarbons count]) withColouringType:CMolColouringTypeCartoonBlue forMasterColorArray:helixColors forNumberOfVertices:HELIX_VERTEX_COUNT difference:30];
self.atomsToFree = self.helper.numberOfAtoms;
self.bondsToFree = [self.helper.bonds count];
self.alphasToFree = [self.helper.alphaCarbons count];
}
Finally, the bit which calls everything (this is a separate class.)
- (void)loadPDB:(NSString *)pdbToLoad
{
if (!self.loading)
{
[self performSelectorOnMainThread:#selector(stopAnimation) withObject:nil waitUntilDone:YES];
[self.renderer freeEverything];
[renderer release];
ES1Renderer *newRenderer = [[ES1Renderer alloc] init];
renderer = [newRenderer retain];
[self performSelectorOnMainThread:#selector(stopAnimation) withObject:nil waitUntilDone:YES]; // need to stop the new renderer animating too!
[self.renderer setDelegate:self];
[self.renderer setupCamera];
self.renderer.pdb = nil;
[renderer resizeFromLayer:(CAEAGLLayer*)self.layer];
[newRenderer release];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(setup:) object:pdbToLoad];
[self.queue addOperation:invocationOperation];
[invocationOperation release];
}
}
- (void)setup:(NSString *)pdbToLoad
{
self.loading = YES;
[helper release];
[renderer.helper release];
PDBHelper *aHelper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
helper = [aHelper retain];
renderer.helper = [aHelper retain];
[aHelper release];
if (!resized)
{
[self.helper resizeVertices:11];
resized = YES;
}
self.renderer.helper = self.helper;
[self.helper setUpAtoms];
[self.helper setUpBonds];
if (self.helper.numberOfAtoms > 0)
[self.renderer askForVertexInformation];
else
{
// LOG ME PLEASE.
}
[self performSelectorOnMainThread:#selector(removeProgressBar) withObject:nil waitUntilDone:YES];
[self performSelectorOnMainThread:#selector(startAnimation) withObject:nil waitUntilDone:YES];
self.renderer.pdb = pdbToLoad;
self.loading = NO;
}
What I'm doing here is loading a molecule from a PDB file into memory and displaying it on an OpenGL view window. The second time I load a molecule (which will run loadPDB: above) I get the Giant Triangle Syndrome and Related Effects... I will see large triangles over my molecule.
However, I am releasing and reallocating my PDBHelper and ES1Renderer every time I load a new molecule. Hence I was wondering:
1. whether the helixVertices, helixIndices and helixColors which I have declared as class-wide variables are actually re-used in this instance. Do they point to the same objects?
2. Should I be setting all my variables to NULL after freeing? I plan to do this anyway, to pick up any bugs by getting a segfault, but haven't got round to incorporating it.
3. Am I even right to malloc() a class variable? Is there a better way of achieving this? I have no other known way of giving this information to the renderer otherwise.
I can't answer your general questions. There's too much stuff in there. However, this caught my eye:
[helper release];
[renderer.helper release];
PDBHelper *aHelper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
helper = [aHelper retain];
renderer.helper = [aHelper retain];
[aHelper release];
I think this stuff possibly leaks. It doesn't make sense anyway.
If renderer.helper is a retain or copy property, do not release it. It already has code that releases old values when it is assigned new values. Also do not retain objects you assign to it.
You have alloc'd aHelper, so there's no need to retain it again. The above code should be rewritten something like:
[helper release];
helper = [[PDBHelper alloc] initWithContentsOfFile:pdbToLoad];
renderer.helper = helper;
Also, I think your helix malloced arrays should probably be instance variables. As things stand, if you have more than one ES1Renderer, they are sharing those variables.