I am kind of new to iOS development.
I want to retrieve strings from the text(.rtf) file I have. The file is within my application main bundle. It's content are:
#start word1 First word end word2 Second word end //lots of things to be added later
Code:
path = [[NSBundle mainBundle]pathForResource:#"words" ofType:#"rtf"];
if(path)
{
NSLog(#"path exists");
}
NSError *error = nil;
NSString *file = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
if(error)
{
NSLog(#"error");
}
NSString *finalword= [[NSString alloc]init ];
NSString *startfrom = [[NSString alloc] initWithFormat:#"word%i",i+1];
i++;
NSLog(#"%#",startfrom);
NSString *wordoftheday = [[NSString alloc]init ];
NSScanner *scanner = [NSScanner scannerWithString:file];
[scanner scanUpToString:startfrom intoString:nil];
[scanner scanUpToString:#"end" intoString:&wordoftheday];
finalword = [wordoftheday substringFromIndex:[startfrom length]];
NSLog(#"%#",finalword);
Word.text = final word; //change label text
//[final word release];
//[wordoftheday release];
//[file release];
Code is working fine but it leaves me with memory management issues. App crashes if I release the variables in the last commented code.
Also this method is in my viewdidload. I want the label to change text when user click a button. I will have to write the same code again in that method which leaves me with more memory issue.
So looking at these one by one, focusing on the memory issues and not the overall strategy here:
NSString *finalword= [[NSString alloc]init ];
Here you alloc/init a new immutable and empty NSString, and then you end up overwriting the pointer to this later anyway. You should just delete this line. And then you'll need to move the declaration down a few lines to this:
NSString *finalword = [wordoftheday substringFromIndex:[startfrom length]];
Then you have:
NSString *startfrom = [[NSString alloc] initWithFormat:#"word%i",i+1];
This one you need to release later. Or just change it to:
NSString *startfrom = [NSString stringWithFormat:#"word%i",i+1];
Then you have:
NSString *wordoftheday = [[NSString alloc]init ];
Same story as finalword. Except that you do need to define this variable so you can pass it to the scanner later. So change it to:
NSString *wordoftheday = nil;
And lastly, you can release 'file'. That is fine. But you don't want to release 'wordoftheday' or 'finalword' because you don't own those strings. You did not create them yourself.
And one other note:
if(error)
That is not the correct way to check for an error in loading 'file'. You should check the return from the method and then look for an error if and only if the return value was nil. So change that line to:
if(!file)
(OK, that wasn't really a memory issue, but a bug I did notice.)
I think that's all of it at least as far as memory issues. I hope that helps.
make those variables as member variables and release in the dealloc
Related
I want to parse a .csv file. For this I use the CHCSV Parser. But when I push into the view where the parser should start parsing, the app crashes.
Terminating app due to uncaught exception 'NSMallocException', reason:
'* -[NSConcreteMutableData appendBytes:length:]: unable to allocate
memory for length (4294967295)'
NSString *filePath = #"http://somewhere.com/test.csv";
NSString *fileContent = [NSString stringWithContentsOfURL:[NSURL URLWithString:filePath] encoding:NSUTF8StringEncoding error:nil];
self.csvParser = [[CHCSVParser alloc] initWithContentsOfCSVFile:fileContent];
Edit:
I'm developing for iOS 6+. Thanks for the great comments and answers. I hope to get the right solution.
Input Stream
It doesn't work. When I want to work with the input stream the app crashes because of the wrong encoding.
Incompatible integer to pointer conversion sending 'int' to
parameter of type 'NSStringEncoding *' (aka 'unsigned int *')
NSData *downloadData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://example.com/test.csv"]];
NSInputStream *stream = [NSInputStream inputStreamWithData:downloadData];
self.csvParser = [[CHCSVParser alloc] initWithInputStream:stream usedEncoding:NSUTF8StringEncoding delimiter:#";"];
self.csvParser.delegate = self;
[self.csvParser parse];
CSV-String
NSString *filePath = #"http://example.com/test.csv";
NSString *fileContent = [NSString stringWithContentsOfURL:[NSURL URLWithString:filePath] encoding:NSUTF8StringEncoding error:nil];
self.csvParser = [[CHCSVParser alloc] initWithCSVString:fileContent];
self.csvParser.delegate = self;
[self.csvParser parse];
This parse only (null).
Final Edit: Dave, the author of CHCSVParser, updated his code on github, so this problem should be solved when you use the most recent version. Get it now!
Okay, here we go:
First add the following code in CHCSVParser.m:
In method - (void)_sniffEncoding at the very beginning you have:
uint8_t bytes[CHUNK_SIZE];
NSUInteger readLength = [_stream read:bytes maxLength:CHUNK_SIZE];
[_stringBuffer appendBytes:bytes length:readLength];
[self setTotalBytesRead:[self totalBytesRead] + readLength];
change it to:
uint8_t bytes[CHUNK_SIZE];
NSUInteger readLength = [_stream read:bytes maxLength:CHUNK_SIZE];
if (readLength > CHUNK_SIZE) {
readLength = CHUNK_SIZE;
}
[_stringBuffer appendBytes:bytes length:readLength];
[self setTotalBytesRead:[self totalBytesRead] + readLength];
After that changed I got only null values so I changed the file path (in the sample project it is located in the main(), however I did the parsing in viewDidLoad.
Make sure you copied the file in your bundle directory for that to work!
file = [NSBundle pathForResource:#"Test" ofType:#"scsv" inDirectory:[[NSBundle mainBundle] bundlePath]];
Edit:
When you say you need to download the file you can do following (but notice that this is quick and dirty solution especially on mobile devices)
NSData *downloadData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://www.yourdomain.tld/Test.scsv"]];
NSInputStream *stream = [NSInputStream inputStreamWithData:downloadData];
The last line is the important one here you need to change.
Hope that solves your issue.
Edit 2:
I've just created a repository with a demo project for you where the code actually works. Perhaps you can find out what you do wrong (or at least different). Here is the link.
Edit 3:
Change
self.csvParser = [[CHCSVParser alloc] initWithInputStream:stream usedEncoding:NSUTF8StringEncoding delimiter:#";"];
to
self.csvParser = [[CHCSVParser alloc] initWithInputStream:stream usedEncoding:&encoding delimiter:';'];
Aloha,
I've come across a problem in iOS 6.1.3 reading webarchive files where -occasionally- the WebResourceData returns a null.
These are known good files (created in TextEdit) stored inside the bundle, and usually read fine. It's just that every so often, they don't.
In a simple test shown below, I read 3 different files over and over until I spot an error. For iOS 6.1.3, I hit an error between 1 and 200 iterations, every time I run the test. I've run this on various devices and the simulator with the same results.
// trying to find out why reading webarchives occasionally
// returns with empty WebResourceData from known good files.
//
- (BOOL) testMe {
NSMutableDictionary *plist = [[NSMutableDictionary alloc] initWithCapacity:100] ;
int iteration = 1;
BOOL ok = TRUE;
// keep going until we have an error
while (ok) {
NSArray *fileNames = #[#"file1",
#"file2",
#"file3" ];
// LOOP through the webArchives...
//
for (NSString *webarchiveName in fileNames) {
// get the webarchive template
//
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:webarchiveName withExtension:#"webarchive"];
//
NSData *plistData = [NSData dataWithContentsOfURL:fileURL];
NSString *error;
NSPropertyListFormat format;
//
plist = (NSMutableDictionary *)[NSPropertyListSerialization propertyListFromData:plistData
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format
errorDescription:&error];
// check to see if it loaded
//
//
if(!plist){
NSLog(#"ERROR: did not load %#", webarchiveName);
}else{
NSData *foundWebResourceData = [[plist objectForKey:#"WebMainResource"] objectForKey:#"WebResourceData"];
NSString *foundHTML = [NSString stringWithUTF8String:[foundWebResourceData bytes]];
if (foundHTML == NULL) {
NSLog(#" %# descr = %#", webarchiveName, foundHTML);
[errorOutlet setText:[NSString stringWithFormat:#"%# returned with no content (null) in WebResourceData", webarchiveName]];
ok = FALSE;
}
} //---- end of if plist exists
} // loop through all 3 files
[countOutlet setText:[NSString stringWithFormat:#"%d", iteration]];
++ iteration;
} // keep looping until error
return ok;
} // end of testMe
The error shows up in these two lines:
NSData *foundWebResourceData = [[plist objectForKey:#"WebMainResource"] objectForKey:#"WebResourceData"];
NSString *foundHTML = [NSString stringWithUTF8String:[foundWebResourceData bytes]];
but it's not consistent. I've isolated the code by making a new xcode project where this is the only activity, other than displaying the iteration count and a retest button. The files always load, and always have a WebMainResource with a WebResourceData key.
A possible clue is that if I instead insert the code into ViewDidLoad, it runs for many more iterations but still finds a null. Calling [self testMe] from a button action hits an error much faster...not sure why.
I'm a bit at a loss, and hoping that it's not an iOS bug, but rather something basic I'm just missing. Any help would be appreciated.
You might try using the NSString initializer designed for reading NSData:
NSString *foundHTML = [[NSString alloc] initWithData:foundWebResourceData encoding:NSUTF8StringEncoding];
i am trying to parse a text file saved in doc dir below show is the code for it
NSArray *filePaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *docDirPath=[filePaths objectAtIndex:0];
NSString *filePath=[docDirPath stringByAppendingPathComponent:#"SKU.txt"];
NSError *error;
NSString *fileContents=[NSString stringWithContentsOfFile:filePath];
NSLog(#"fileContents---%#",fileContents);
if(!fileContents)
NSLog(#"error in reading file----%#",error);
NSArray *values=[fileContents componentsSeparatedByString:#"\n"];
NSLog(#"values-----%#",values);
NSMutableArray *parsedValues=[[NSMutableArray alloc]init];
for(int i=0;i<[values count];i++){
NSString *lineStr=[values objectAtIndex:i];
NSLog(#"linestr---%#",lineStr);
NSMutableDictionary *valuesDic=[[NSMutableDictionary alloc]init];
NSArray *seperatedValues=[[NSArray alloc]init];
seperatedValues=[lineStr componentsSeparatedByString:#","];
NSLog(#"seperatedvalues---%#",seperatedValues);
[valuesDic setObject:seperatedValues forKey:[seperatedValues objectAtIndex:0]];
NSLog(#"valuesDic---%#",valuesDic);
[parsedValues addObject:valuesDic];
[seperatedValues release];
[valuesDic release];
}
NSLog(#"parsedValues----%#",parsedValues);
NSMutableDictionary *result;
result=[parsedValues objectAtIndex:1];
NSLog(#"res----%#",[result objectForKey:#"WALM-FT"]);
The problem what i am facing is when i try to print lineStr ie the data of the text file it is printing as a single string so i could not able to get the contents in line by line way please help me solve this issue.
Instead use:
- (NSArray *)componentsSeparatedByCharactersInSet:(NSCharacterSet *)separator
it covers several different newline characters.
Example:
NSArray *values = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *lineStr in values) {
// Parsing code here
}
ALso seperatedValues is over released. First one is created with alloc init, then on the next line it is replaced by the method componentsSeparatedByString. So the first one od lost without being released, that is a leak. Later the seperatedValues created by componentsSeparatedByString is released but it is already auto released by componentsSeparatedByString to that is an over release;
Solve all the retain/release/autorelease problem with ARC (Automatic Reference Counting).
Here is a version that uses convenience methods and omits over release:
NSArray *values = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (NSString *lineStr in values) {
NSArray *seperatedValues = [lineStr componentsSeparatedByString:#","];
NSString *key = [seperatedValues objectAtIndex:0];
NSDictionary *valuesDic = [NSDictionary dictionaryWithObject:seperatedValues forKey:key];
[parsedValues addObject:valuesDic];
}
NSLog(#"parsedValues---%#",parsedValues);
Are you sure the line separator used in your text file is \n and not \r (or \r\n)?
The problem may come from this, explaining why you don't manage to split the files into different lines.
What does mean? I get this error when trying to iterate through a file in Cocoa obj-c.
I can't find any information on the web.
Would appreciate some help. Thanks.
EDIT
I've been following this tutorial (link) to preload Core Data. I've tried creating a Cococa application and have also tried doing this from within my iPhone app. I think all my setup code for Core Data is fine. Whenever this method is called I get EXEC BAD ACCESS.
- (void)loadInitialData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// name ZSTREET_1 ZSTREET_2 ZCITY ZZIP ZURL ZTEL latitude longitude
NSString *path = [[NSBundle mainBundle] pathForResource:#"placesdata" ofType:#"txt"];
NSString *fileString = [NSString stringWithContentsOfFile:path]; // reads file into memory as an NSString
NSArray *lines = [fileString componentsSeparatedByString:#"\r"]; // each line, adjust character for line endings
NSManagedObjectContext *context = [self managedObjectContext];
for (NSString *line in lines)
{
NSLog(line);
NSString* string = [[NSString alloc] initWithUTF8String:line];
NSArray *parts = [string componentsSeparatedByString:#"\t"];
// value mapping
NSString *name = [parts objectAtIndex:0];
NSString *street_1 = [parts objectAtIndex:1];
NSString *street_2 = [parts objectAtIndex:2];
NSString *city = [parts objectAtIndex:3];
NSString *zip = [parts objectAtIndex:4];
NSString *url = [parts objectAtIndex:5];
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *latitude = [f numberFromString:[parts objectAtIndex:6]];
NSNumber *longitude = [f numberFromString:[parts objectAtIndex:7]];
[f release];
// splitting the parts to create the objects
Place *place = (Place *)[NSEntityDescription insertNewObjectForEntityForName:#"Place" inManagedObjectContext:context];
Address *address = (Address *)[NSEntityDescription insertNewObjectForEntityForName:#"Address" inManagedObjectContext:context];
Location *location = (Location *)[NSEntityDescription insertNewObjectForEntityForName:#"Location" inManagedObjectContext:context];
// set attributes
[place setValue:name forKey:#"name"];
[address setValue:street_1 forKey:#"street_1"];
[address setValue:street_2 forKey:#"street_2"];
[address setValue:city forKey:#"city"];
[address setValue:zip forKey:"#zip"];
[address setValue:url forKey:#"url"];
[location setValue:latitude forKey:#"latitude"];
[location setValue:longitude forKey:#"longitude"];
// link the objects together
[place setValue:address forKey:#"address"];
[place setValue:location forKeyPath:#"address.location"];
[string release];
}
NSLog(#"Done initial load");
NSError *error;
if (![context save:&error]) {
NSLog(#"Error saving: %#", error);
}
[context release];
[pool drain];
}
For other people running into this problem with entirely different code, this is a bit of a red herring.
The warning is from the debugger itself. The debugger creates a struct containing info for each object in the system. After the EXC_BAD_ACCESS, it tried to create one of these but was unable to. Note that this is a warning and not an error so it may even be expected in situations like this.
In any event, the details surrounding this don't matter. You've got to find the source of your EXC_BAD_ACCESS. A common cause is trying to access an object after it has been released.
For someone who comes across this in the future, I got this problem because I was doing this too much:
NSString* aString = #"";
for(int i=0; i<someLargeNumber; i++) {
aString = [aString stringByAppendingFormat:#"..."];
}
Once I switched to using NSMutableString, the problem was resolved.
This line is wrong and should produce a compiler warning:
NSString* string = [[NSString alloc] initWithUTF8String:line];
The method initWithUTF8String: expects an UTF-8 encoded C string and not a NSString object.
Before you continue you should fix all compiler warnings! And you also should check that the parts array actually contains as many objects as you expect. You also need to use a format string with NSLog, you might even crash there if your line contains any % characters.
I happen to run into EXC_BAD_ACCESS on a fairly regular basis (which is not a good thing), because we haven't enabled ARC yet.
I find the best way to track those errors down by using the Profiler in Zombie modus. This post shows you how to "hunt for zombies":
How do I set up NSZombieEnabled in Xcode 4?
I have some source code to get the file name of an url
for example:
http://www.google.com/a.pdf
I hope to get a.pdf
because the way to join 2 NSStrings I can get is 'appendString' which only for adding a string at right side, so I planned to check each char one by one from the right side of string 'http://www.google.com/a.pdf', when it reach at the char '/', stop the checking, return string fdp.a , after that I change fdp.a to a.pdf
source codes are below
-(NSMutableString *) getSubStringAfterH : originalString:(NSString *)s0
{
NSInteger i,l;
l=[s0 length];
NSMutableString *h=[[NSMutableString alloc] init];
NSMutableString *ttt=[[NSMutableString alloc] init ];
for(i=l-1;i>=0;i--) //check each char one by one from the right side of string 'http://www.google.com/a.pdf', when it reach at the char '/', stop
{
ttt=[s0 substringWithRange:NSMakeRange(i, 1)];
if([ttt isEqualToString:#"/"])
{
break;
}
else
{
[h appendString:ttt];
}
}
[ttt release];
NSMutableString *h1=[[[NSMutableString alloc] initWithFormat:#""] autorelease];
for (i=[h length]-1;i>=0;i--)
{
NSMutableString *t1=[[NSMutableString alloc] init ];
t1=[h substringWithRange:NSMakeRange(i, 1)];
[h1 appendString:t1];
[t1 release];
}
[h release];
return h1;
}
h1 can reuturn the coorect string a.pdf, but if it returns to the codes where it was called, after a while system reports
'double free
*** set a breakpoint in malloc_error_break to debug'
I checked a long time and foudn that if I removed the code
ttt=[s0 substringWithRange:NSMakeRange(i, 1)];
everything will be Ok (of course getSubStringAfterH can not returns the corrent result I expected.), no error reported.
I try to fix the bug a few hours, but still no clue.
Welcome any comment
Thanks
interdev
The following line does the job if url is a NSString:
NSString *filename = [url lastPathComponent];
If url is a NSURL, then the following does the job:
NSString *filename = [[url path] lastPathComponent];
Try this:
Edit: from blow comment
NSString *url = #"http://www.google.com/a.pdf";
NSArray *parts = [url componentsSeparatedByString:#"/"];
NSString *filename = [parts lastObject];
I think if you have already had the NSURL object, there is lastPathComponent method available from the iOS 4 onwards.
NSURL *url = [NSURL URLWithString:#"http://www.google.com/a.pdf"];
NSString *filename = [url lastPathComponent];
Swift 3
Let's say that your url is http://www.google.com/a.pdf
let filename = url.lastPathComponent
\\filename = "a.pdf"
This is more error free and meant for getting the localized name in the URL.
NSString *localizedName = nil;
[url getResourceValue:&localizedName forKey:NSURLLocalizedNameKey error:NULL];
I haven't tried this yet, but it seems like you might be trying to do this the hard way. The iPhone libraries have the NSURL class, and I imagine that you could simply do:
NSString *url = [NSURL URLWithString:#"http://www.google.com/a.pdf"];
NSString *path = [url path];
Definitely look for a built in function. The libraries have far more testing and will handle the edge cases better than anything you or I will write in an hour or two (generally speaking).