0

I am uploading Zips containing folders of images from my application on a remote server in my iOS app. The function is called in a separate NSThread so it should not block the UI. Here I create batches of folders containing images (of max 10 MB) and upload them on remote server by giving calls to an SP on remote server. The problem is each call to this method increases my live bytes depensing on the size of zip file which never comes down again. Bellow is my code:

-(IBAction)syncPressed:(id)sender
{
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *strServerFolderPath = [[paths objectAtIndex:0]stringByAppendingPathComponent:app.objDataAccess.tempServerIPAddress];
NSString *strImagesFolderPath=[strServerFolderPath stringByAppendingPathComponent:@"Images"];
NSString *strDatabaseImagesPath=[strImagesFolderPath stringByAppendingPathComponent:app.objDataAccess.tempDBName];
NSArray *arrOfDirectoryToZip=[[NSFileManager defaultManager] contentsOfDirectoryAtPath:strDatabaseImagesPath error:NULL];
int iZippedFolderCounter=0;

if(app.viewController.arrADataSyncProgress==nil)
    app.viewController.arrADataSyncProgress=[[NSMutableArray alloc]init];
else
    [app.viewController.arrADataSyncProgress removeAllObjects];

unsigned long long int iSizeOfFolder=[app.globalVcImageViewController folderSize:strDatabaseImagesPath];

int iNoOfBatches=iSizeOfFolder/10485760;
if(iSizeOfFolder%10485760>0)
    iNoOfBatches++;       //We will need to add one more batch for remainings

int iCounter=1;
//Add batch objects in array initially so that they can be updated at correct time
while(iCounter<=iNoOfBatches)
{
    SyncProgress *objSync=[[SyncProgress alloc]init];
    objSync.cSyncName=[NSString stringWithFormat:@"Batch %d",iCounter];
    objSync.iSyncStatus=0;
    objSync.cSpName=@"UploadImageData";
    objSync.cStartSyncTime=@" ";
    objSync.cEndTime=@" ";
    [app.viewController.arrADataSyncProgress addObject:objSync];
    objSync=nil;
    iCounter++;
}

[app.viewController.tblviwSyncProgress performSelectorInBackground:@selector(reloadData) withObject:nil];

float fPerBatchIncrement=100.0/iNoOfBatches;

[Flurry logEvent:@"Sync started"];
[app.objLogger writetoLogFileWithMessage:@"Sync started" withMethodName:@"syncPressed:" withLoggerLevel:eLoggerInfo];

int iBatchCounter=0;
//Upload images batchwise
while(iBatchCounter<iNoOfBatches)
{
        NSMutableArray *arrmPathsToZip=nil;
        unsigned long long int iSizeOfBatch=0;
        //In each batch 10 MB or remaining folders are zipped
        while(iZippedFolderCounter<arrOfDirectoryToZip.count && (iSizeOfBatch+[app.globalVcImageViewController folderSize:[strDatabaseImagesPath stringByAppendingPathComponent:[arrOfDirectoryToZip objectAtIndex:iZippedFolderCounter]]])<10485760 && iZippedFolderCounter<arrOfDirectoryToZip.count)
        {
            //Add Paths to temporary array
            if(arrmPathsToZip==nil)
                arrmPathsToZip=[[NSMutableArray alloc]init];

            [arrmPathsToZip addObject:[strDatabaseImagesPath stringByAppendingPathComponent:[arrOfDirectoryToZip objectAtIndex:iZippedFolderCounter]]];
            iSizeOfBatch+=[app.globalVcImageViewController folderSize:[strDatabaseImagesPath stringByAppendingPathComponent:[arrOfDirectoryToZip objectAtIndex:iZippedFolderCounter]]];

            iZippedFolderCounter++;
        }


        //Give the zip a random unique name
        NSString *strRandomFolderName=[NSString stringWithFormat:@"%f",[[NSDate date] timeIntervalSince1970]];
        NSString *strZipPath= [strImagesFolderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.zip",strRandomFolderName]];

        //Create zip from the folder
        BOOL bSuccess = [SSZipArchive createZipFileAtPath:strZipPath withContentsOfDirectoris:arrmPathsToZip];
        arrmPathsToZip=nil;

        if(!bSuccess)
        {
            [app removeCustomLoader];
            UIAlertView *altError=[[UIAlertView alloc]initWithTitle:app.strAppName message:@"Error occured while gathering data" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
            [altError show];
            altError=nil;
        }
        else
        {
            //Update sync progress object
            SyncProgress *objSyncProgress=(SyncProgress*)[app.viewController.arrADataSyncProgress objectAtIndex:iBatchCounter];
            objSyncProgress.cStartSyncTime = [app.loginView getCurrentTimeInUTCFormatToString];
            [app.viewController.tblviwSyncProgress performSelectorInBackground:@selector(reloadData) withObject:nil];

            NSData *dat=[NSData dataWithContentsOfFile:strZipPath];
            //[Base64 initialize];
            NSString *strZipEncoded = [Base64 encode:dat];
            NSString *cDBToken=app.dbToken;

            //Get Key From GUID
            NSString *cGUID=[app.loginView generateUuidString];
            int iKey = [app.loginView KeyFromGUID:cGUID];

            //Encrypt Parameter
            cDBToken=[app.loginView  encrypt:cDBToken withShiftCount:iKey];
            cDBToken= [NSString stringWithFormat:@"%@##%@",cGUID,cDBToken];
            NSString *strUserName=[app.loginView encrypt:app.LoginUsrnm withShiftCount:iKey];
            NSString *strPassword=[app.loginView encrypt:app.LoginPswd withShiftCount:iKey];
            NSString *strSPName=[app.loginView encrypt:@"_wspIICInsertImages" withShiftCount:iKey];

            //SOAP Request
            NSMutableString *soapMsg =[[NSMutableString alloc] initWithFormat:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\
                                <soap:Envelope\
                                xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\
                                xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\
                                xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\
                                <soap:Body>\
                                <UploadImageData\
                                xmlns=\"http://tempuri.org/\">\
                                <DBToken><![CDATA[%@]]></DBToken>\
                                <UserName><![CDATA[%@]]></UserName>\
                                <Password><![CDATA[%@]]></Password>\
                                <DeviceID>1</DeviceID>\
                                <BatchSize>1</BatchSize>\
                                <DocString><![CDATA[%@]]></DocString>\
                                <fileName><![CDATA[%@]]></fileName>\
                                <SPName><![CDATA[%@]]></SPName>\
                                </UploadImageData>\
                                </soap:Body>\
                                </soap:Envelope>",cDBToken,strUserName,strPassword,strZipEncoded,strRandomFolderName,strSPName];

            NSURL *url = [NSURL URLWithString:app.webServiceName];

            NSMutableURLRequest *req =
            [NSMutableURLRequest requestWithURL:url];

            NSString *msgLength =
            [NSString stringWithFormat:@"%d", [soapMsg length]];

            [req addValue:@"text/xml; charset=utf-8"
       forHTTPHeaderField:@"Content-Type"];

            [req addValue:msgLength
       forHTTPHeaderField:@"Content-Length"];

            //add gzip-encoding to HTTP header
            [req setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];


            [req addValue:@"http://tempuri.org/UploadImageData"
       forHTTPHeaderField:@"SOAPAction"];

            [req setHTTPMethod:@"POST"];
            [req setHTTPBody: [soapMsg dataUsingEncoding:NSUTF8StringEncoding]];
            soapMsg=nil;

            [req setTimeoutInterval: 60];

            //call method to connect to webservice by passing request object
            NSURLResponse* urlResponse = nil;
            NSError *error;
            NSData *responseData =  [NSURLConnection sendSynchronousRequest:req returningResponse:&urlResponse error:&error];
            NSString *strResponse=[[NSString alloc]initWithData:responseData encoding:NSUTF8StringEncoding];
            [[NSFileManager defaultManager] removeItemAtPath:strZipPath error:NULL];

            if(iZippedFolderCounter<arrOfDirectoryToZip.count)
                app.viewController.fPercentageSync=app.viewController.fPercentageSync+fPerBatchIncrement;
            else
                app.viewController.fPercentageSync=100;

            app.viewController.fDataSize+=dat.length/(1024.0*1024);
            dat=nil;

            //Update sync progress object
            objSyncProgress=(SyncProgress*)[app.viewController.arrADataSyncProgress objectAtIndex:iBatchCounter];

            if([[app stringBetweenString:@"<UploadImageDataResult>" andString:@"</UploadImageDataResult>" withstring:strResponse] isEqualToString:@"true"])
            {
                objSyncProgress.iSyncStatus=1;

            }
            else
                objSyncProgress.iSyncStatus=2;

            objSyncProgress.cEndTime=[app.loginView getCurrentTimeInUTCFormatToString];

            [app performSelectorInBackground:@selector(setHUDSubtitle:) withObject:[NSNumber numberWithFloat:app.viewController.fPercentageSync]];
            [app.viewController.tblviwSyncProgress performSelectorInBackground:@selector(reloadData) withObject:nil];

            responseData=nil;
            strResponse=nil;
            strZipEncoded=nil;
        }

        iBatchCounter++;
}
btnSync.selected=NO;
[Flurry logEvent:@"Sync completed"];
[app.viewController.actSyncProgress stopAnimating];
[app removeCustomLoader];
NSDateFormatter *formatter=[[NSDateFormatter alloc]init];
formatter.dateFormat=@"dd-MM-yyyy hh:mm:ss a";
NSString *strSyncCompletionDate=[formatter stringFromDate:[NSDate date]];
[app.objDataAccess insertIntoDatabaseUserName:app.LoginUsrnm syncCompletionTime:strSyncCompletionDate dataSize:app.viewController.fDataSize serverName:app.loginView.tempServerName databaseName:app.loginView.tempDBName];
[[UIApplication sharedApplication] setIdleTimerDisabled:NO];
}

The call to above function is given as bellow:

[NSThread detachNewThreadSelector:@selector(syncPressed:) toTarget:self withObject:nil];

Any pointers where am I going wrong will be most appreciated!

EDIT: After using instruments to see allocations, I noted that they are pointing towards te line shown in bellow screenshot:

 NSData *responseData =  [NSURLConnection sendSynchronousRequest:req returningResponse:&urlResponse error:&error];

I have also enclosed my code in @autoreleasepool Please help as I am still not getting how can I avoid this.

Memory Leak

Yogi
  • 3,578
  • 3
  • 35
  • 56
  • It's too hard to detect the leaks by looking but I have one simple suggestion that may help. Surround your code with @autorelease {}. – Boran Sep 17 '13 at 12:37
  • Tried that..didn't help! – Yogi Sep 17 '13 at 12:44
  • 1
    did you try making heapshot analysis, it will help you to find the source of the leak. – Boran Sep 17 '13 at 13:05
  • Thank You all for useful comments. I did the heap shot analysis as well has checked the Allocations and instruments are pointing to the code snippet shown in my updated question. Please help me solve this issue. – Yogi Sep 19 '13 at 07:37
  • I have last three suggestions. make req = nil; after call, clear url cache when you get memory warnings, don't use sendSynchronousRequest. One more thing take look at gcd and quees. – Boran Sep 19 '13 at 10:00
  • @BoranA: Thanks for the suggestions. I have implemented all of them except calling the request asynchronously. Can you please explain why should we not use synchronous request as it's a standard API given by Apple.. – Yogi Sep 20 '13 at 06:05

1 Answers1

0

When you set these pointers to nil, the object is still in memory:

responseData=nil;
strResponse=nil;
strZipEncoded=nil;

Try this instead and look for other places where memory is not released:

  [responseData release];
  [strResponse release];
  [strZipEncoded release];
IanStallings
  • 806
  • 10
  • 21