iOS - App crash without error during loop process - ios

during a loop process, my App crash without error. The array count is equal to 175260. With profiler I don't have leaks, so I don't know why the App exit, maybe the CPU usage 100% during a lot of time?
Thank you for your help.
Just this code following crash the App :
for(unsigned int i = 0; i <14;i++)
{
if(findSensor[i]==YES)
{
for(unsigned int j = 1; j <[array count];j++)
{
#autoreleasepool {
if([[[[array objectAtIndex:j] componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
//Code here
}
}
}
}
}
The full code is :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = [NSString stringWithFormat:#"%#/%#",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase];
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:fileName];
NSFileHandle *output = [NSFileHandle fileHandleForReadingAtPath:[NSString stringWithFormat:#"%#/%#10",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase]];
if(output == nil)
{
NSManagedObjectContext *context = [self managedObjectContext];
_recordlocal = [NSEntityDescription insertNewObjectForEntityForName:#"RECORD" inManagedObjectContext:context];
_recordlocal.date = [ibNavSettings interfaceSettings].selectedFileToDataBase;
NSData *inputData = [NSData dataWithData:[fh readDataToEndOfFile]];
NSString *inputString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding];
NSArray *array = [[NSArray alloc] initWithArray:[inputString componentsSeparatedByString:#"\n"]];
for(unsigned int i = 0; i <14;i++)
{
if(findSensor[i]==YES)
{
[[NSFileManager defaultManager] createFileAtPath:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)] contents:nil attributes:nil];
NSMutableString *saveString = [[NSMutableString alloc] init];
int count = 0;
for(unsigned int j = 1; j <[array count];j++)
{
#autoreleasepool {
if([[[[array objectAtIndex:j] componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
[saveString appendString:[array objectAtIndex:j]];
[saveString appendString:#"\n"];
if(i == 0)
count++;
progress++;
pourcent = progress/total;
load = pourcent*100;
if(load%5==0)
[self performSelectorInBackground:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
}
}
}
[saveString writeToFile:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)] atomically:YES encoding:NSUTF8StringEncoding error:nil];
if(i == 0)
_recordlocal.count = [[NSNumber alloc] initWithInt:(count/50)];
}
}
_recordlocal.load = [[NSNumber alloc] initWithBool:YES];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Core data error %#, %#", error, [error userInfo]);
abort();
}

I would guess that your app is crashing without a readable exception because it is running out of available RAM, especially since you indicated that it is running through a large number of iterations.
For a test, I would recommend doing what Rikkles suggests with the autorelease pool. In addition, since the value of i (and as a result the comparison string) rarely changes, I would create that string outside the j loop as well. This would avoid the creation of a lot of extra strings laying around.
Beyond that, since it appears that you are looking for a string at the beginning of a string that is delimited by a semicolon, I would recommend instead of doing componentsSeparatedByString and then examining element zero that you use the NSString method hasPrefix to check for the condition you are looking for.
Here is an example:
for(unsigned int i = 0; i <14;i++)
{
NSString *searchString = [NSString stringWithFormat:#"%d;", 10*(i+1)];
if(findSensor[i]==YES)
{
for(unsigned int j = 1; j <[array count];j++)
{
if([[array objectAtIndex:j] hasPrefix:searchString])
{
//Code here
}
}
}
}
(I hope this compiles and runs, if it doesn't it should require more than minor tweaks. I am away from my Mac right now.)
If this doesn't help, then something going on inside //Code here must be the culprit.

Why are you creating [array count] autoreleasepools? What's the point of creating so many of them? It could crash because of that. Put the #autoreleasepool outside the for loop.
The only reason I could think that you would do that is if you create so many transient objects inside each iteration of the for loop that you'd want to get rid of them as soon as you got out of the iteration. But there are other ways to do that, including reusing those objects within each iteration.

First suggestion
Just use fast enumeration for the inner loop, you aren't actually using the index 'j' for anything
https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/Enumeration.html
Second suggestion
Put some NSLog's in place, it will slow everything down, but you need to figure out what point you are failing at. That will help point everyone in the right direction.
Third suggestion
Actually use NSError objects and output their value if an error is thrown:
NSError *writeError = nil;
[saveString writeToFile:[NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)]
atomically:YES
encoding:NSUTF8StringEncoding
error:&writeError];
if(error != nil) NSLog(#"error writing file: %#", [[writeError userInfo]description]);
Fourth suggestion
You appear to try to be updating the UI from a background thread. This will not work or will cause a crash. UI code can only be called from a main thread. So dont do this:
[self performSelectorInBackground:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
If you are already on a background thread this will probably crash because you are creating threads on threads on threads. You instead would want to call:
[self performSelectorOnMainThread:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
Fifth suggestion
You may be going over the maximum length for NSString (it's big but I did it once on accident before). You should probably just be appending the file on each iteration of the loop instead, so you don't have an ever growing NSMutableString:
NSString *path = [NSString stringWithFormat:#"%#/%#%d",documentsDirectory,[ibNavSettings interfaceSettings].selectedFileToDataBase,10*(i+1)]
NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:filePath];
NSData *newLine = [#"\n" dataUsingEncoding:NSUTF8StringEncoding];
for(NSString *rowString in array)
{
if([[[rowString componentsSeparatedByString:#";"] objectAtIndex:0] isEqualToString:[NSString stringWithFormat:#"%d",10*(i+1)]])
{
NSData *stringData = [rowString dataUsingEncoding:NSUTF8StringEncoding];
[fh truncateFileAtOffset:[fh seekToEndOfFile]];
[fh writeData:stringData];
[fh truncateFileAtOffset:[fh seekToEndOfFile]];
[fh writeData:newLine];
if(i == 0)
count++;
progress++;
pourcent = progress/total;
load = pourcent*100;
if(load%5==0)
[self performSelectorOnMainThread:#selector(changeUI:)withObject:[NSNumber numberWithFloat:(pourcent)]];
}
}
}
And this has the added benefit of helping you ditch the autoreleasepools
This was invalid
If your array does in fact have 175260 rows, that is probably your issue. You are looping using unsigned int as your index var. Unsigned ints in c only have a max value of 65535. Use an unsigned long int, max 4294967295.

Related

How to fix leak on [NSString availableStringEncodings]?

When profiling the project, leaks were found and located to [NSString availableStringEncodings] in below function. No idea how to fix it :
- (NSString *)stringFromArchive:(NSString *)fileName inDirectory:(NSString *)folderName
{
NSString *rStr = #"";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *folderPath = [documentsDirectory stringByAppendingPathComponent:folderName];
NSString *filePath = [folderPath stringByAppendingPathComponent:fileName];
NSFileManager * fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
NSError *error;
const NSStringEncoding *encodings = [NSString availableStringEncodings];
NSStringEncoding encoding1;
while ((encoding1 = *encodings++) != 0)
{
rStr = [NSString stringWithContentsOfFile:filePath encoding:encoding1 error:&error];
if (rStr == nil) {
NSLog(#"error: %#", error);
}else{
break;
}
}
}
return rStr;
}
enter image description here
As seen often and it is very common to find Object declarations without initial value but found out in hard learning curve that a lot of leaks I found where bound to the easy peasy practice of unitialised declarations...
in Short: proper declaration helps the debugger/instruments as well.
NSError *error;   very often seen. Can even leak, at least instruments finds it suspicious
NSError *error = nil;   better, and also not NULL (which leaks for sure).
NSStringEncoding encoding1;
is an ENUM of NSUInteger and you seem to try to iterate thru all encodings from the bottom up until you find a match that properly reads the file.. then you may want to start not with an undefined value but with 1 which is the most simple one (NSASCIIStringEncoding). Which is why you use break; and compare against !=0, so it iterates up to the end of the zero-terminated list (tells you also why the enum does not start with 0 but NSASCIIStringEncoding==1).
and also when you use const NSStringEncoding *var and then *var++ what is increased? The pointers address or the value that was stored at that address.., *(var++) vs (*var)++. Both could work fine but one is more risky the other iterates thru encodings that do not exist. So guessing you want to go thru the list of encodings up to its zero-termination, take the values and try reading...
NSString *rStr = #"";
NSError *error = nil;
const NSStringEncoding *a = [NSString availableStringEncodings];
NSStringEncoding b = NSASCIIStringEncoding;
// increase addr of a, take value to b
// and check for b not 0==end-of-list
while ( (b=*(a++))!=0 ) {
//iterate until encoding allows reading successful..
rStr = [NSString stringWithContentsOfFile:#"yourfile.." encoding:b error:&error];
if (rStr != nil) break; // rStr has content, so end loop
// print reason on any missed trial
NSLog(#"error: %#", error);
}
edit: made a leak on purpose just for fun if Instruments will detect it, the leaking code i used...
extern "C" int leakingCppFunction(const char * s)
{
Example *e = new Example(s); //e is never deleted, so it leaks.
return e->getLen();
}
clearly finds this leak and none in the encoding iteration discussed above. So guessing your extension (NSStringOtherEncodings) is causing availableStringEncodings to leak.

Use iclems/iOS-htmltopdf to generate multiple pdf from multiple html file

I'm wrapping a for loop for function to enable multiple html to multiple pdf conversion:
for (int i=0; i<= 47; i++) {
NSString *inputHTMLfileName = [NSString stringWithFormat:#"wkhtml_tempfile_%d",j];
NSString *outputPDFfileName = [NSString stringWithFormat:#"~/Documents/%d_delegateDemo%d.pdf",loop,j];
NSURL *htmlFileUrl = [[NSBundle mainBundle]
URLForResource:inputHTMLfileName withExtension:#"html"];
// Check for existing pdf file and remove it
NSError *pdfDeleteError;
if ([[NSFileManager defaultManager] fileExistsAtPath:outputPDFfileName]){
//removing file
if (![[NSFileManager defaultManager] removeItemAtPath:outputPDFfileName error:&pdfDeleteError]){
NSString * errorMessage = [NSString stringWithFormat:#"wk %d Could not remove old pdf files. Error:%#",j, pdfDeleteError];
NSLog(#"%#",errorMessage);
}
}
self.PDFCreator = [NDHTMLtoPDF createPDFWithURL:htmlFileUrl pathForPDF:[outputPDFfileName stringByExpandingTildeInPath] pageSize:kPaperSizeA4 margins:UIEdgeInsetsMake(10, 5, 10, 5) successBlock:^(NDHTMLtoPDF *htmlToPDF) {
NSString *result = [NSString stringWithFormat:#"HTMLtoPDF did succeed (%# / %#)", htmlToPDF, htmlToPDF.PDFpath];
NSLog(#"%#",result);
self.resultLabel.text = result;
} errorBlock:^(NDHTMLtoPDF *htmlToPDF) {
NSString *result = [NSString stringWithFormat:#"HTMLtoPDF did fail (%#)", htmlToPDF];
NSLog(#"%#",result);
self.resultLabel.text = result;
}];
}
However, it crashes with
Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
Link to github:
https://github.com/iclems/iOS-htmltopdf
However, if I replace the for loop with a button pressed (trigger this function once per second) the app doesn't crash.
I think your for loop crashing is because you are using block within the for loop
So what happens here is that the another iteration of the calls the block before it completes the previous block call
so here what you can do that you can use dispatch_group feature of ios to call each iteration call on different thread instead of calling sequentially
so to achieve this you can create one method with block parametes and call the block in that method some thing like this,
- (void)blockTask:(NSString*)strPath
{
dispatch_group_enter(serviceGroup);
self.PDFCreator = [NDHTMLtoPDF createPDFWithURL:htmlFileUrl pathForPDF:[outputPDFfileName stringByExpandingTildeInPath] pageSize:kPaperSizeA4 margins:UIEdgeInsetsMake(10, 5, 10, 5) successBlock:^(NDHTMLtoPDF *htmlToPDF) {
NSString *result = [NSString stringWithFormat:#"HTMLtoPDF did succeed (%# / %#)", htmlToPDF, htmlToPDF.PDFpath];
NSLog(#"%#",result);
self.resultLabel.text = result;
dispatch_group_leave(serviceGroup);
} errorBlock:^(NDHTMLtoPDF *htmlToPDF) {
NSString *result = [NSString stringWithFormat:#"HTMLtoPDF did fail (%#)", htmlToPDF];
NSLog(#"%#",result);
self.resultLabel.text = result;
dispatch_group_leave(serviceGroup);
}];
}
Note : This just a psuedo code may contains errors,
For dispatch_group tutorial check here

NSArrayM setObject:atIndex:]: object cannot be nil

Having a little issue with a bit of code, returning the error listed above after running its respective action:
- (IBAction)uploadData:(id)sender {
NSMutableArray *uploadArray = [[NSMutableArray alloc]init];
[uploadArray addObject:#"hire.csv"];
[uploadArray addObject:#"equipment.csv"];
[uploadArray addObject:#"signature.png"];
NSString * docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
NSString * proof1 = [docPath stringByAppendingPathComponent:#"proof1.png"];
if([[NSFileManager defaultManager] fileExistsAtPath: proof1])
{
[uploadArray addObject:#"proof1.png"];
}
NSString * proof2 = [docPath stringByAppendingPathComponent:#"proof2.png"];
if([[NSFileManager defaultManager] fileExistsAtPath: proof2])
{
[uploadArray addObject:#"proof2.png"];
}
NSString * proof3 = [docPath stringByAppendingPathComponent:#"proof3.png"];
if([[NSFileManager defaultManager] fileExistsAtPath: proof3])
{
[uploadArray addObject:#"proof3.png"];
}
int arrayCount = [uploadArray count];
int i = 0;
NSString *arrayString;
for (i = 0; i < arrayCount; i++)
{
uploadArray[i] = arrayString;
if(arrayString != nil)
{
NSString * uploadString = [docPath stringByAppendingPathComponent:arrayString];
[self upload:uploadString ftpUrl:#"ftp.xxxxxxxx.com/xx/xx" ftpUsr:#"xxx" ftpPass:#"xxx"];
}
NSLog(uploadArray[i]);
}
}
Now, if I disable everything in the for loop except for the NSLog() I get no error, and it returns the correct list of objects in the array.
This to me suggests that none of the objects should return nil, surely?
Could anyone help me spot what I'm doing wrong? (probably quite a lot as im pretty new to this stuff)
As your error clearly stats, you are adding a nil object to an array and this is not allowed.
In the below code you have not assigned anything to arrayString making it nil. Then you try to add it to your array.
NSString *arrayString;
for (i = 0; i < arrayCount; i++)
{
uploadArray[i] = arrayString;
From the looks of your code you are trying to add every element form your array to your string. You are doing this the wrong way around. You need a NSMutableString and do something like:
[arrayString appendString: uploadArray[i]];

NSThread Causing memory Leaks in iPhone

I am uploading images chunk wise, in a background thread, each chunk will be size of 512kb,to the best of my knowledge,i have taken care of memory leaks using release,nsautoreleasepool.
Below is the code for uploading images chunkwise.
- (void)FetchDataFromDB : (NSNumber *) isOffline
{
#autoreleasepool {
#try {
NSLog(#"FetchDatafromDB");
isThreadStarted = YES;
VYukaDBFunctions *vdb = [VYukaDBFunctions getInstance];
NSMutableArray *fileNames = [vdb GetFileNames:[isOffline integerValue]];
for(int j=0 ; j<[fileNames count] ; j++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString * filename = fileNames [j] ;
int _outgoingMsgId = [[vdb SelectMsgId:filename] intValue];
int _totalchunk =[[vdb SelectTotalChunk:filename]intValue];
int currentChunk = [vdb GetCurrentChunk:filename];
for( int i=currentChunk ; i <= _totalchunk ; i++)
{
NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
NSString *AsyncRequest = [[NSString alloc] init];
AsyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
if(![AsyncRequest isEqual:#""])
{
BOOL status = [self UploadChunkWise :AsyncRequest : 1 : i : vdb : filename : _outgoingMsgId];
// AsyncRequest = NULL;
// [AsyncRequest release];
if(status){
if(i==_totalchunk)
{
NSLog(#"Deleting from medialist , FileName :%#", filename);
[vdb DeleteFromMediaList : filename];
}
}
else{
[vdb DeleteFromMediaList : filename];
break;
}
}
[innerPool drain];
}
[pool drain];
}
[fileNames removeAllObjects];
// [fileNames release];
//recurssive call to check any pending uploads..
if([[vdb GetFileNames:[isOffline integerValue]] count] > 0)
{
NSLog(#"Calling Recursively..");
[self FetchDataFromDB:[isOffline integerValue]];
}
}
#catch (NSException *exception) {
NSLog(#"Exception caught on Uploading from FetchDataFromDB:%#", exception);
}
#finally {
}
}
NSLog(#"thread quit ");
isThreadStarted = NO;
[NSThread exit];
}
-(BOOL) UploadChunkWise :(NSString *) AsyncRequest : (int) count : (int)currentChunk : (VYukaDBFunctions * ) vdb : (NSString *) currentFileName : (int) outgoingMsgId
{
NSHTTPURLResponse *response ;
NSError *error;
//Yes, http
NSMutableURLRequest *httpRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"Url goes here"]];
NSData* data = [AsyncRequest dataUsingEncoding:NSUTF8StringEncoding];
[httpRequest setHTTPMethod:#"POST"];
[httpRequest setHTTPBody:data];
[httpRequest setValue:#"application/xml" forHTTPHeaderField:#"Content-Type"];
NSData *returnedData = [NSURLConnection sendSynchronousRequest: httpRequest returningResponse:&response error:&error] ;
NSString *result= [[NSString alloc] initWithData:returnedData encoding:NSASCIIStringEncoding];
[httpRequest release];
returnedData= NULL;
[returnedData release];
data = NULL;
[data release];
if ([result rangeOfString:#"success"].location != NSNotFound )
{
NSLog(#" success");
[vdb DeleteCurrentChunkFromOutgoingTable:currentChunk : outgoingMsgId];
[result release];
return YES ;
}
else if ([result rangeOfString:#"fail"].location != NSNotFound )
{
[result release];
if (count < 3) {
return [self UploadChunkWise :AsyncRequest : count+1 : currentChunk: vdb : currentFileName : outgoingMsgId ];
}
else
{
NSLog(#" failed");
[vdb DeleteAllCurrentFileChunksFromOutgoingTable:currentFileName];
return NO ;
}
}
return NO;
}
I am starting thread as below
[NSThread detachNewThreadSelector:#selector(FetchDataFromDB:) toTarget:self withObject:[NSNumber numberWithInt:0]];
The problem is after uploading 9 to 12 chunks, i am getting memory error. i am getting 4 to 5 times memory warning and after that app crashes.in console i am getting memory warning first at app delegate class, followed by 4 classes which are extending UIViewController. why i am getting warning at app delegate, and other classes which is of type UIViewController.Why i have to release object of other class if the separate thread is giving me memory error? what i am doing wrong here? I cannot use ARC, as i have integrated this with old code, which is not using ARC, i tried enabling ARC class wise, but it dint work. Can any one help me to find out if there is any memory leaks in this code. Suggestions are welcomed and appreciated.Thanks in advance..
Two things- first, I see this:
NSString *AsyncRequest = [[NSString alloc] init];
AsyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
This should be consolidated to this:
NSString *asyncRequest = [vdb SelectAsyncRequest: i : _outgoingMsgId];
You instead are creating a new instance, then immediately either generating or referencing another instance.
Second:
Your code is very hard to read and doesn't follow the Objective-C smalltalk conventions.
Variable names should begin with a lowercase letter. Method names should also start with lowercase letters. Class names and functions should begin with capital letters. It makes it difficult to read because I and many others have been trained to see capital letters and think CLASS NAME instead of POSSIBLE VARIABLE NAME. Just FYI
Finally, some of your methods take multiple parameters, like the one above. You really should add a prefix to each parameter so that it's easy to understand what the parameter is for. This:
[vdb SelectAsyncRequest: PARAMETER : PARAMETER];
would look much better if it was :
[vdb selectAsyncRequestForParameter: PARAMETER withOtherParameter:OTHERPARAM];
EDIT: I also don't think you need so many autorelease pools. The entire thing is wrapped in a big autorelease pool already.
EDIT2: I also see a lot of release calls that aren't necessary. In your UploadChunkWise method you are calling release on *data and *returnedData which are both already implicitly autoreleased. Methods that return objects to you will already have ownership given up and "handed over" to you. Essentially, those methods will do this:
NSData *data = [[NSData alloc] init];
return [data autorelease];
When you get it, if you want to keep it you will have to retain it yourself, otherwise it will be destroyed at the return of your method.
However, it is correct for you to call release on the NSString *result instance you created with -init.

csv to core data method error 2012

I have read a few posts and found a method. I adapted it to my app, however at [self saveContext]; I get a bad instruction error.
Please let me know why, and if this method makes sense. Also I am using \r because in the csv each item is in its own row.
Thank you in advance
-(void)addData{
NSString *paths = [[NSBundle mainBundle] resourcePath];
NSString *bundlePath = [paths stringByAppendingPathComponent:#"coredatainfo.csv"];
NSString *dataFile = [[NSString alloc] initWithContentsOfFile:bundlePath];
NSArray *dataRows = [dataFile componentsSeparatedByString:#"\r"];
[dataFile release];
FirstCDitem *myItem;
for (int i = 0 ; i < [dataRows count] ; i++)
{
NSArray *dataElements = [[dataRows objectAtIndex:i] componentsSeparatedByString:#","];
NSLog(#"Added: %d %#",i,dataElements);
myItem = (FirstCDitem *)[NSEntityDescription insertNewObjectForEntityForName:#"FirstCDitem" inManagedObjectContext:[self managedObjectContext]];
[FirstCDitem setTitle:[dataElements objectAtIndex:i]];
[self saveContext];
}
}
There are two problems in this code line:
[FirstCDitem setTitle:[dataElements objectAtIndex:i]];
i is the current row number, so it probably makes no sense to use it as index to the dataElements array. Perhaps you want the first element in the row?
setTitle must sent to the instance myItem, not to the class FirstCDItem.
So the line should look like this:
[myItem setTitle:[dataElements objectAtIndex:0]];

Resources