Memory Leak using ASIHTTPRequest appendPostData with ARC - ios

I've been having a memory leak in a upload speedtest function that has been recently converted to ARC. I believe I've adhered to the memory management guidelines for ARC.
The issue seems to be with the chuck of random data I create for the upload test. Its memory doesn't seem to get freed.
Here is where I create the upload data and ASIHTTPRequest object:
ASIHTTPRequest *request0 = [ASIHTTPRequest requestWithURL:uploadTestURL];
__weak ASIHTTPRequest *request = request0;
NSData *uploadData ;
if ([speedTier isEqualToString:#"Wifi"]) {
uploadData = [self createRandomNSDataOfSize:1000000];
}else
{
uploadData = [self createRandomNSDataOfSize:4000000];
}
[request appendPostData:uploadData];
The function that actually creates the data is:
NSMutableData* theData = [NSMutableData dataWithCapacity:size];
for( unsigned int i = 0 ; i < size/4 ; ++i )
{
u_int32_t randomBits = arc4random();
[theData appendBytes:(void*)&randomBits length:4];
}
return theData;
I then proceed to set up the block for setBytesSentBlock, where I manage the graphics for the upload and moment of termination of upload. Some of the code is below:
[request0 setBytesSentBlock:^(unsigned long long size, unsigned long long total) {
double timeDiffereceFromStart = [[NSDate date] timeIntervalSinceDate:start];
if (totalUploadSize == 0)
{
start=[NSDate date];
totalUploadSize = [request.postBody length];
return;
}
if(startPosition == 0 && timeDiffereceFromStart >= 1)//[request totalBytesSent] > 20000)
{
startPosition = [request totalBytesSent];
start=[NSDate date];
return;
}
I've just posted some of the code, but wanted to show where I used the variable 'request' within the block. I'm pretty sure I've fixed the circular retain issue here, but I wanted to make sure there wasn't some other problem.
On other thing I should note - I've put a break point within the ASIHTTPRequest dealloc function. All of the objects of this type that I create hit the dealloc breakpoint. So they are all being freed properly. But I don't understand why the memory usage keeps going up when it hits the upload function.
Thanks!

I've figured out the issue, and it was a retain cycle which involved the parent class of the class from which I posted the code. Because this part of the system isn't wasn't written by me, I missed it. I ended up fixing the warnings that point out retain cycles when using blocks, and the memory leaks were gone.

Related

Crash when accessing NSNumber from array

I am facing a strange crash where an instance of NSNumber seems to be deallocated although it persists in array. I have created a system to download multiple files from remote server and have a block to indicate progress (an average progress really). And the computation of the progress produces a crash. The crash is not consistent and happens "usually" at can occur at any point. [_NSProgressFractionTuple floatValue]: unrecognized selector sent to instance 0x17042ab80 leads me to believe the NSNumber is somehow deallocated and I fail to see how this is possible.
To give the full method code:
- (void)downloadFilesFromURLs:(NSArray<NSString *> *)urlPaths withProgress:(void (^)(float progress))progressBlock completion:(void (^)(NSError *error))completionBlock {
NSMutableArray *toLoad = [[NSMutableArray alloc] init];
for(NSString *path in urlPaths) {
if([self fileForURL:path] == nil) {
[toLoad addObject:path];
}
}
NSInteger itemsToLoad = toLoad.count;
if(itemsToLoad <= 0) {
if(completionBlock) {
completionBlock(nil);
}
return;
}
// Set progresses to zero
__block NSMutableArray *progresses = [[NSMutableArray alloc] init];
for(int i=0; i<itemsToLoad; i++) [progresses addObject:[[NSNumber alloc] initWithFloat:.0f]];
__block NSInteger requestsOut = itemsToLoad;
__block NSError *latestError = nil;
for(int i=0; i<itemsToLoad; i++) {
NSInteger index = i;
[self downloadFileFromURL:toLoad[index] named:nil withProgress:^(float progress) {
progresses[index] = [[NSNumber alloc] initWithFloat:progress];
if(progressBlock) {
float overallProgress = .0f;
for(NSNumber *number in [progresses copy]) {
overallProgress += number.floatValue;
}
progressBlock(overallProgress/itemsToLoad);
}
} completion:^(NSString *filePath, NSError *error) {
if(error) latestError = error;
requestsOut -= 1;
if(requestsOut <= 0) {
if(completionBlock) {
completionBlock(latestError);
}
}
}];
}
}
Code explanation:
So this method accepts an array of URLs. It then checks if some of the files were already downloaded and creates a new array which only contains URLs that need to be downloaded. If all files exist or no URLs are provided then the completion is called and the operation breaks.
Next I create a mutable array and fill it with NSNumber instances all having a zero value. I remember how many requests will be made and I create a placeholder for an error. I iterate through all the URLs and initialize requests where each will report a progress and completion and both of these are on a main thread.
So in progress block I access the array of progresses to assign the new values through indexing. I then compute an average progress and report overall progress to an input block.
The request completion decreases the number of requests counter and when that one falls to zero the input completion is called.
The situation:
It all works as expected, the whole procedure is correct. The given values are all valid and all the files are there on the server and are accessible. When the app does not crash it all works as it should.
But when it crashes it crashes in
for(NSNumber *number in [progresses copy]) {
overallProgress += number.floatValue;
}
and the crash is random but in any case the number.floatValue seems to be accessing a memory that it shouldn't.
I now have a solution where I replaced the progresses array with pure C pointer float *progresses = malloc(sizeof(float)*itemsToLoad); which is freed on completion. It seems to work but still, what am I missing here? What could be the cause of array with NSNumbers not working here?
Some additional info:
Memory is OK, this is writing directly into files and even if it didn't the overall file size is relatively small
Disk space is OK
I was using #(progress) syntax but changed it to explicit allocation in hopes of removing the issue
progresses does not need __block, I added it just in case
Completion does not get called before all the progresses get called and even if it did I see no reason to crash the app
Thank you!
NSMutableArray is not thread safe. So even though there is no explicit memory management issue, if NSMutableArray is accessed at the same time by two different thread bad things can happen. I believe that dispatching the withProgress block in a serial queue would solve the issue.

NSMutableArray can not release objects after invoke removeAllObjects

I'm a new guy for developing ios app.
#property (copy, nonatomic) NSMutableArray* dataBufferArray;
Following code run in a callback function.which invoked frequently.
[analyzer.dataBufferArray addObject:[NSNumber numberWithInteger:thisFrame]];
[analyzer.dataBufferArray removeAllObjects];
Code run in ARC.
I found the memory always growing! Finally IOS exit my application cause by huge memory consume.
My question is: why removeAllObjects can not release the memory?
How to resolve it?
post more code
static int analyze(SAMPLE *inputBuffer,
unsigned long framesPerBuffer,
AudioSignalAnalyzer *analyzer) {
SAMPLE *pSample = inputBuffer;
for (long i = 0; i SAMPLE_RATE){
NSArray* unitSampleArray = [analyzer.dataBufferArray subarrayWithRange:NSMakeRange(0, SAMPLE_RATE - 1)];
[analyzer.dataBufferArray removeObjectsInRange:NSMakeRange(0, SAMPLE_RATE - 1)];
//use thread to process
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:analyzer
selector:#selector(decodeSound:)
object:unitSampleArray];
[analyzer.queue addOperation: operation];
}
// for protect
if (analyzer.dataBufferArray.count > SAMPLE_RATE * 12){
NSLog(#"sample in data buffer so big, need clear");
[analyzer.dataBufferArray removeAllObjects];
}
return 0;
}
As you can see, analyze function is callback by AudioQueueNewInput.
I used NSMutableArray add object NSNumber, and I always 'removeObjectsInRange:' them. I use instruments to check the memory, it always growing!
You are using ARC based project there is no need to release memory, it will automatically managed. Your problem is somewhere else please post some more code.

Completion blocks in for loop

Currently I am trying to do some async and concurrent tasks, and I am using Azures blob to upload all the images, however the concern is that, for every blob I need to get a SASURL and then upload the images. Also the another side towards it is that I want to have all the operations of the images completed to be uploaded, and hence send a final upload to the database. Although I can send the operation to the database earlier, without having the confirmation of the images completed, but I just wanted to make sure, that the operation does gets completed.
Below is the code for the SASURL block.
- (void)storageServiceBlob:(NSArray*)images
{
StorageService *storageService = [StorageService getInstance];
NSLog(#"%#",[storageService containers]);
NSLog(#"%#",[storageService blobs]);
for (int i = 0; i < [images count]; i++) {
NSString *file_name = [images objectAtIndex:i];
NSString *result = [self imageName:file_name];
NSLog(#"Final: %#", result);
[storageService getSasUrlForNewBlob:result forContainer:#"misccontainer" withCompletion:^(NSString *sasUrl) {
NSLog(#"%#",sasUrl);
[self postBlobWithUrl:sasUrl Image:[images objectAtIndex:i]];
}];
}
}
I want to use gcd in group somehow to determine that after all the completion blocks is called in a group, it executes Post method. Is there anyway to do this in gcd?
There are many ways you could do this. Here's one:
- (void)storageServiceBlob:(NSArray *)imageFilenames
{
StorageService *storageService = [StorageService getInstance];
__block NSMutableSet *remainingImageFilenames = [NSMutableSet setWithArray:imageFilenames];
for (NSString *imageFilename in imageFilenames) {
NSString *imageName = [self imageNameForImageFilename:imageFilename];
[storageService getSasUrlForNewBlob:imageName forContainer:#"misccontainer" withCompletion:^(NSString *sasUrl) {
[self postBlobWithUrl:sasUrl imageFilename:imageFileName];
[remainingImageFilenames removeObject:imageFilename];
if ([remainingImageFilenames count] == 0) {
// you're done, do your thing
}
}];
}
}
A few tips:
Be careful with your naming. There seems to be some ambiguity there.
Generally, idiomatic method name parameters start with a lower-case letter, e.g. myMethodWithThis:andThat:, not MyMethodWithThis:AndThat:.
Fast enumeration, e.g. for (id obj in array) is your friend. Learn and use it.
You can shortcut [array objectAtIndex:1] as array[1].
If you have access to the queue that the requests are going in then you can issue a barrier block.
When you have an async queue a barrier block will sit and wait to be executed until all of the blocks issued before it have run.
If you don't have access to the queue then your best bet is to keep a count.

iOS [ARC] app not releasing memory

My application, during a process I've written, is rising in memory and seems to not be releasing it.
The first thing I'd like to mention is that the basic outline of what I've written is this:
- Request a url (fetching data using NSData -initWithContentsOfURL)
- Parse NSData into NSArray of NSDictionarys using NSJSONSerialization +JSONObjectWithStream
- Loop through decoded NSArray inserting/updating/deleting records in a sqlite database using the FMDB framework with the data decoded
The application does the above, however it does it in a loop for an undetermined period of time, in which the application displays a "Loading" HUD. I thought it may be worth mentioning this, although I find it insignificant how many times it does this process, as that shouldn't affect the memory usage if it were releasing properly. If I am wrong here, please advise me.
My code works fine, well, it does what it's intended to do. However, when I profile the application code, the memory seems to just keep rising. It does drop in segments throughout, but overall it keeps rising (IE doesn't release in full what it previously used).
I have, as previously stated, profiled the application with Allocations, Leaks, VM Tracker, and used Trace Highlights.
Trace Highlights: Shows that the memory usage is gradually going up, but dropping some memory (not all) meaning if the process is running for long enough the memory will reach high usage and terminate.
Allocations: Seems OK. The allocations has spikes but always comes back down to where it started. I took heapshots and they always drop down leaving maximum 500-700kb per segment (left for about 10 minutes)
VM Tracker: Proves to show that memory consistently rises, and is not releasing full memory (as discovered in trace highlights). Resident seems to get really high
Leaks: No leaks found in application
Here's some screenshots of Allocations/VM Tracker running:
It's worth noting that I have in fact tried:
- Adding autoreleasepools
- "force releasing" by assigning each properties; such as NSURLs, NSRequests, etc; to nil
My questions:
- Should I be doing something special to release the memory?
- How could I further debug this issue?
- How best can I find out what's wrong from the data Instruments gives me?
---- EDIT: ----
Here's the code that sends the url request to fetch the data.:
- (void) requestAndParse : (NSString *)url
{
NSURL *theURL;
ASIHTTPRequest *request;
NSData *collectedData;
NSError *error;
#try {
// File cache the NSData
theURL = [[NSURL alloc] initWithString: url];
request = [ASIHTTPRequest requestWithURL: theURL];
[request setDownloadDestinationPath: [[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"]];
[request startSynchronous];
[request waitUntilFinished];
collectedData = [[NSData alloc] initWithContentsOfFile:[[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"]];
if ([collectedData length] > 0) {
records = [NSJSONSerialization JSONObjectWithData:collectedData options:NSJSONReadingMutableContainers error:&error];
}
}
#catch (NSException *exception) {
// Failed
NSLog(#"Parse error: %#", error);
}
#finally {
// DB updates with the records here
...
// remove file
[[NSFileManager defaultManager] removeItemAtPath:[[NSHomeDirectory() stringByAppendingPathComponent:#"Documents"] stringByAppendingString:#"/cachefile.txt"] error:nil];
// release properties used
collectedData = nil;
request = nil;
theURL = nil;
}
}
This above method is called from within a while loop in the Application Delegate. The while loop is an undetermined length, as previously mentioned.
--- EDIT 2: ---
The following is what happens within the #finally statement (updating the SQLite database using FMDB). There are a lot of these methods in my class, one for each table. They all follow the same pattern though, as they are all duplicated from the first one:
-(BOOL) insertBatchOfRecords:(NSArray *)records {
__block BOOL queueReturned = YES;
#autoreleasepool {
FMDatabaseQueue *dbQueue = [self instantiateDatabaseQueue];
[dbQueue inTransaction:^(FMDatabase *tdb, BOOL *rollback) {
if (![tdb open]) {
NSLog(#"Couldn't open DB inside Transaction");
queueReturned = NO;
*rollback = YES;
return;
}
for (NSDictionary *record in records) {
[tdb executeUpdate:#"INSERT OR REPLACE INTO table (attr1, attr2) VALUES (?,?)", [record valueForKey:#"attr1"], [record valueForKey:#"attr2"]];
if ([tdb hadError]) {
queueReturned = NO;
*rollback = YES;
NSLog(#"Failed to insert records because %#", [tdb lastErrorMessage]);
return;
}
}
}];
[dbQueue close];
dbQueue = nil;
}
return queueReturned;
}
And follows is the -instantiateDatabaseQueue method:
-(FMDatabaseQueue *) instantiateDatabaseQueue {
#autoreleasepool {
return [FMDatabaseQueue databaseQueueWithPath: [self.getDocumentsDirectory stringByAppendingPathComponent:#"localdb.db"]];
}
}
The autoreleasepools may make it messy, but the code originally did not have these. I implemented them in various locations to see if there was any improvement (there was not).
--- EDIT 3 ---
I have been profiling the application the past few days, and still have no luck in finding an answer. I have separated the part of the app in question to a separate project of it's own, to make sure that it is indeed this causing the memory usage. This proved to be correct, as the app is still acting the same.
I have taken further profiling pictures, and am still having a hard time identifying what is actually wrong. See below that the allocations looks OK (the VM also doesn't look too bad to me?), and there's still no leaks (no picture of this, because there's none!!)
However, when I profiled on Trace Highlights, the memory usage just keeps going up, until reaching too much usage (around 70+MB on a 3GS) and then crashes due to using so much memory.
I reduced the problem by using ASIHTTPRequest for grabbing the NSData (stores to file instead). Please see revised code above. However, problem still persists, just takes longer to happen!
As per originally, question:
- Is there something wrong with the second part of this app process?
Using try / catch in iOS with ARC can cause memory leaks, and is best avoided.
an alternative approach is to use an async NSURLConnection, or an NSOperation with a synch NSURLConnection.

Finding the cause of memory leak in Instruments

I have run the leaks in Instruments and it is showing me a memory leak with a 100% value. I am able to see the line of code that is causing the problem. But not really sure what the error is..
- (void) listAllBooks {
if (marrListFromDB != nil) {
[marrListFromDB removeAllObjects];
marrListFromDB = nil;
}
marrListFromDB = [[NSMutableArray alloc] init];
ServerCommunicationAPI *servApi = [[ServerCommunicationAPI alloc] init];
servApi.delegate = self;
NSURL *url = [NSURL URLWithString:kLISTCONTENTS];
[servApi listBooksWithDeviceID:singleton.g_strdevID deviceKey:singleton.g_strdevID andSessionString:singleton.g_strSessionID sessionKey:#"sessionKey" URL:url andRequestMethod:#"POST"];
}
The line of error is the last one. Not sure why it is causing a memory leak... Need some guidance..
It is hard to tell from the information provided, but maybe the delegate property of ServerCommunicationAPI is declared as (strong)? In this case servApi could never be released, because it keeps a strong reference to itself (retain cycle).
I suggest that you check in instruments which kind of object leaks, this would make the answer much easier.
Try out this. May it resolve your memory leak problem.
- (void) listAllBooks {
if (marrListFromDB != nil) {
[marrListFromDB removeAllObjects];
marrListFromDB = nil;
}
ServerCommunicationAPI *servApi ;
marrListFromDB = [[NSMutableArray alloc] init];
if(servApi == nil){
ServerCommunicationAPI *servApi = [[ServerCommunicationAPI alloc] init];
}//Every time it going to alloc. It's strong object may be due do this memory leak happens.
servApi.delegate = self;
NSURL *url = [NSURL URLWithString:kLISTCONTENTS];
[servApi listBooksWithDeviceID:singleton.g_strdevID deviceKey:singleton.g_strdevID andSessionString:singleton.g_strSessionID sessionKey:#"sessionKey" URL:url andRequestMethod:#"POST"];
}
Just another idea: Maybe you execute your code in a separate thread for which no autorelease pool has been set up? In this case the message sent to servApi could create autorelease objects that cannot be released later since no autorelease pool exists.
So, if your code is not executed in the main thread, please check if an autorelease pool has been set up using a #autoreleasepool {...} block for your thread.

Resources