Why does the position of #autoreleasepool matter? - objective-c

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.

Related

How to create/end run loop to properly deallocate memory?

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;
}

How to use a run loop with NSTimer in Objective-C

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.

Why is this autorelease error occurring with ARC enabled?

I wrote a small CLI program to delete specific Safari cookies for me. Functionally it's fine, but it's throwing up warnings about objects being "autoreleased with no pool in place". My project has ARC enabled, hence why I don't have any autorelease pools.
Here's my code:
// NSLog replacement from http://stackoverflow.com/a/3487392/1376063
void IFPrint (NSString *format, ...) {
va_list args;
va_start(args, format);
fputs([[[NSString alloc] initWithFormat:format arguments:args] UTF8String], stdout);
fputs("\n", stdout);
va_end(args);
}
int main(int argc, const char * argv[])
{
NSString *urlSearchString;
if (argc > 1) {
urlSearchString = [[NSString alloc] initWithUTF8String:argv[1]];
}
else {
IFPrint(#"No URL provided, quitting.");
return 1;
}
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSString *filterString = [[NSString alloc] initWithFormat:#"domain ENDSWITH '%#'", urlSearchString];
NSPredicate *filter = [NSPredicate predicateWithFormat:filterString];
NSArray *matchedCookies = [cookieStorage.cookies filteredArrayUsingPredicate:filter];
for (int i = 0; i < matchedCookies.count; i++) {
[cookieStorage deleteCookie:[matchedCookies objectAtIndex:i]];
}
IFPrint(#"Removed %li cookies", matchedCookies.count);
return 0;
}
The message I get is:
objc[15502]: Object 0x107b2bf00 of class NSThread autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
Which appears in the Xcode debugger or when running the release binary directly (slight digression: shouldn't these messages be stripped out of the "release" build?). The line that causes it seems to be:
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
Similarly, if I run it without passing an argument, I get a similar message:
objc[15630]: Object 0x100114ed0 of class __NSCFString autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
objc[15630]: Object 0x100114f80 of class __NSCFData autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
Which appears to come from the IFPrint function I'm using (however this doesn't show up when I use the IFPrint when I provide a proper argument).
I'm a bit out of my depth here, can anyone show me where (and how) I've gone wrong?
ARC still requires an autorelease pool. Methods like [NSPredicate predicateWithFormat:filterString] continue to release an autoreleased object (though you no longer need to concern yourself all that much since ARC handles it). Furthermore the internal implementation of any library method you call may create arbitrarily many autoreleased objects while running.
You should wrap your code in an autorelease pool via the #autoreleasepool mechanism.
Wrap the entire body of main with #autoreleasepool like so:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
// your code
}
}
All you need to do is add an autoreleasepool in your main.
int main(int argc, const char * argv[])
{
#autoreleasepool
{
//Your code
}
}

memory is growing in audio buffer code

I have a code that we use many times with our apps, its a class that take the buffer samples and process it ,then send back notification to the main class.
The code is c and objective-c.
It works just great, but there is a memory growing which i can see in instruments-allocations tool. the "overall bytes" is keep growing, in 100k a second. becuase of some parts of the code that i know who they are .
this is the callback function, with the line that makes problems.
it happens many times a second.
I also dont really understand where to put my *pool :
static OSStatus recordingCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
AudioBuffer buffer;
buffer.mNumberChannels = 1;
buffer.mDataByteSize = inNumberFrames * 2;
//NSLog(#"%ld",inNumberFrames);
buffer.mData = malloc( inNumberFrames * 2 );
// Put buffer in a AudioBufferList
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0] = buffer;
// block A
OSStatus status;
status = AudioUnitRender(audioUnit,
ioActionFlags,
inTimeStamp,
inBusNumber,
inNumberFrames,
&bufferList);
//end block A
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int16_t *q = (int16_t *)(&bufferList)->mBuffers[0].mData;
int16_t average ;
for(int i=0; i < inNumberFrames; i++)
{
average=q[i];
if(average>100) // lineB
reducer++;
//blockC
if(reducer==150 )
{
average= preSignal + alpha*(average-preSignal);
//NSLog(#"average:%d",average);
//call scene
[dict setObject:[NSNumber numberWithInt:average] forKey:#"amp" ] ;
[[NSNotificationCenter defaultCenter] postNotificationName:#"DigitalArrived" object:nil userInfo:dict];
reducer=0;
preSignal=average;
}
//end blockC
}
free(buffer.mData);
[pool release];
return noErr;
}
OK:
ignore blockC for a second.
removing blockA and lineB solve it all.
removing only one of them- leaks.
i just cant undetstand what is growing here .
Just a guess, but allocating a new NSAutoreleasePool inside of your recording callback function (which is a super time-critical function) is probably a bad idea.
Actually, why are you doing this here at all? Shouldn't you just have one pool for the entire app, in your main.m? This is probably causing some of your leaks.
You should not do anything the requires memory allocation inside an Audio Unit render callback. The real-time requirements are too tight for using generic Objective C.
Since you should not allocate a pool, or any other memory, inside an audio unit callback, you should not use any Objective C methods that potentially or actually create any objects, such as dictionary modifications or notification creation. You may have to drop back to using plain C inside the render callback (set a flag), and do your Objective C messaging outside the render callback in another thread (after polling the flag(s) in a timer callback, for instance).

Why unloaded class is still accessible in Cocoa?

I loaded a class dynamically with [NSBundle load]. And unloaded it dynamically with [NSBundle unload]. Anyway it looks the class is still alive after unloading.
My code is:
// In separated bundle.
#implementation EEExampleBundle
+ (void)test
{
NSLog(#"TTTTT");
}
#end
// In executable file.
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
#autoreleasepool
{
id EEExampleBundle = nil;
#autoreleasepool
{
NSString* path = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"EEExampleBundle.framework"];
NSBundle* sampleBundle = [NSBundle bundleWithPath:path];
[sampleBundle load];
EEExampleBundle = (id)[sampleBundle classNamed:#"EEExampleBundle"];
[EEExampleBundle test];
BOOL r = [sampleBundle unload];
NSLog(#"unload result = %d", r);
}
[EEExampleBundle test];
}
return 0;
}
The output is:
2011-09-25 01:08:52.713 driver[2248:707] TTTTT
2011-09-25 01:08:52.714 driver[2248:707] unload result = 1
2011-09-25 01:08:52.716 driver[2248:707] TTTTT
Why the class code is still working? Is this normal? Or should I do any extra step to unload the code completely?
P.S
I'm not using ARC. I turned it off explicitly.
(more of a comment than an answer, nevertheless:) That's due to the inner #autoreleasepool block, no? You won't be able to create a new instance from your bundle, but you do keep the ones already created alive (else, that'd generate fancy bugs).