Parsing my flat-file on iPhone is a pain in the ***, please Help me out - iphone

I am programming an iPhone App which is supposed to parse a flat-file from the web, create managed objects from the flat-file and later on should display them in an UITableView.
There are no problems with the saving and the displaying, but I just can't get the hang of a good Parser.
Thats the file I want to parse: Flat-file
AS far as I know, I can't use the NSXMLParser for this task (because obviously there are no tags).
So I at first tried to programm a NSScanner which should get me the interesting properties --> didn't work out
Now I am using this method:
- (void) parseMemberDataWithURL: (NSString *)urlString
{
self.memberTempCounter = 1;
//Get data from web
self.downloadedText = [NSString stringWithContentsOfURL: [NSURL URLWithString: urlString] encoding:NSUTF8StringEncoding error:nil ];
memberArray = [downloadedText componentsSeparatedByString:#";"];
while (self.memberTempCounter<[memberArray count])
{
[[ExhibitorController sharedController] createExhibitorWithName:[memberArray objectAtIndex:self.memberTempCounter]
street:[memberArray objectAtIndex:self.memberTempCounter+2]
zip:[memberArray objectAtIndex:self.memberTempCounter+3]
city:[memberArray objectAtIndex:self.memberTempCounter+4]
email:[memberArray objectAtIndex:self.memberTempCounter+7]
phone:[memberArray objectAtIndex:self.memberTempCounter+5]
website:[memberArray objectAtIndex:self.memberTempCounter+8]
produktbereiche:[[memberArray objectAtIndex:self.memberTempCounter+9] componentsSeparatedByString:#","]];
self.memberTempCounter= self.memberTempCounter+13;
}
}
I am using the memberTempCounter to identify the property.
The problems are:
This only works out in like 3 of 4 times.1 of 4 times the App crashes and I have no Idea why...
The method has a performance like a 1962 VW Beetle. Parsing the whole chunk of data takes up to 3 Minutes on my iPhone 3G
Any Ideas or a simpler way to do this?
I would be really gratefull. Thanks in advance: -)

You might as well do all the parsing in the background, and then display as the information gets parsed.
As for memory issues, try doing temporary autorelease pools and release every 50 or so iterations through the loop.
int count = 0;
NSAutoreleasePool * loopPool = [[NSAutoreleasePool alloc] init];
while(someInsanelyLargeCondition){
// Do your stuff here
// .............
count++;
if (count > 50) {
count = 0;
[loopPool release];
loopPool = [[NSAutoreleasePool alloc] init];
}
}

Recursive-descent (LL1) parsers are pretty simple, light on memory, and for speed they go almost as fast as you can run a pointer through characters. Building your data structure would probably be the dominant time-taker.

I was finally able to fix my performance problem.
I have a method in another class, which ads Tags for the different Exhibitors. Therefore it first checks if the Tag already is stored in the database or else creates it.
With an growing Set of Tags in my database the search-process took longer and longer and this led to the long parsing time.
Anyone else having this problem: Take a look at the Performance Core Data Programming guide of apple in the "Implementing Find-or-Create Efficiently"-section:
http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html

Related

iOS - Amazon S3 Slow Download

I am creating an iOS app that allows users to write lets say a status update and people can comment on it, like it, interact with it in multiple ways, basically the status update has multiple properties that would be stored with it. Imagine an app with a home-screen of more than 50 of these status updates represented into a table view.
Now take your eyes away and focus on a practice/demo app, a developer trying to master the techniques before the big game (thats me!) So essentially, I have started out by setting up an S3 bucket based in Singapore. I live in Singapore, so there is a server nearby and really everything should be fast and smooth. Except, its just not. Its slow, and its starting to make me annoyed.
I know an app that uses S3 that loads high-definition panorama images with comments, likes etc. and it takes a second or a bit more for all this data to load. I am not sure how they actually carry out the process, I know they store the images on S3 but thats all I know. In my starter demo, I simply upload some pieces of text (say 20) then download them and it takes like 15 seconds under my 60 mbps wifi! These pieces of text don't even exceed a sentence each, they are phrases, so I am really kind of confused here.
I have CloudFront setup but isn't it for websites? I have the manage distributions and URL things all setup but how to setup in my code? This is probably my biggest question to nail down for release later in my other app. Even so, I live in Singapore and the bucket is in the Singapore server, so CloudFront for self-testing / practicing wouldn't be mandatory.
I find this extremely confusing, here is some annotated code I have produced, any problems, misconceptions that is leading it to be slow?
- (void)loadObjects {
S3ListObjectsRequest *listObjectRequest = [[S3ListObjectsRequest alloc] initWithName: #"testbucketquotes.rohanprostudios"];
S3ListObjectsResponse *listObjectResponse = [[AmazonClientManager s3] listObjects:listObjectRequest];
if(listObjectResponse.error != nil)
{
}
else
{
S3ListObjectsResult *listObjectsResults = listObjectResponse.listObjectsResult;
if (objects == nil) {
objects = [[NSMutableArray alloc] initWithCapacity:[listObjectsResults.objectSummaries count]];
}
else {
[objects removeAllObjects];
}
// By defrault, listObjects will only return 1000 keys
// This code will fetch all objects in bucket.
NSString *lastKey = #"";
for (S3ObjectSummary *objectSummary in listObjectsResults.objectSummaries) {
if ([[objectSummary key] rangeOfString: #"UploadedQuote"].location != NSNotFound) {
[objects addObject:[objectSummary key]];
lastKey = [objectSummary key];
}
}
while (listObjectsResults.isTruncated) {
listObjectRequest = [[S3ListObjectsRequest alloc] initWithName: #"testbucketquotes.rohanprostudios"];
listObjectRequest.marker = lastKey;
listObjectResponse = [[AmazonClientManager s3] listObjects:listObjectRequest];
if(listObjectResponse.error != nil)
{
break;
}
listObjectsResults = listObjectResponse.listObjectsResult;
for (S3ObjectSummary *objectSummary in listObjectsResults.objectSummaries) {
if ([[objectSummary key] rangeOfString: #"UploadedQuote"].location != NSNotFound) {
[objects addObject:[objectSummary key]];
}
lastKey = [objectSummary key];
}
}
if (objects.count) {
for (int i = 0; i <= objects.count - 1; i++) {
S3GetObjectRequest *req = [[S3GetObjectRequest alloc] initWithKey:[objects objectAtIndex: i] withBucket: #"testbucketquotes.rohanprostudios"];
`// asynchronously loads text (adds to operation queue)`
AsyncQuoteDownloader *quote = [[AsyncQuoteDownloader alloc] initWithRequest:req andViewController: self];
[operationQueue addOperation: quote];
// in 'AsyncQuoteDownloader' when finished calls a method in this view controller adding the response to an array and reloading a table
}
}
}
});
}
Anything wrong with my code that is making it lag so much? I would have thought this would take a matter of milliseconds if an imaging service would take 1 second to load HQ images with likes and comments etc takes 1-2 seconds.
Thank you for any help...
Update
Ok, so the iteration of keys doesn't seem to be the problem here but rather the downloading of the objects. Any thoughts? Thanks...
This is the first time I've ever even seen Objective C, so I may be completely wrong here. But... it looks like you're iterating through the keys of the entire bucket. This is going to be really, really slow with any appreciable quantity of keys.
A better design would be store a lookup table in something like DynamoDB (since you're already using AWS). You'd query there, get an array of matching id's (S3 keys), and then fetch the match objects from S3.
Another option, as I used in my own iOS app, is to use Heroku as the app layer and create a POSTGRESQL record that points to your content. This way you can create rich queries in rails and mediate upload and download with the power of Rails, rather then paying for both DynamoDB and S3.
Turns out my bucket was set to US region and not Singapore region... #doh
Its faster now, its working fine
I just needed to set the AmazonS3Client's endpoint to Singapore (SEA) region

iOS - libical / const char * - memory usage

I am using the libical library to parse the iCalendar format and read the information I need out of it. It is working absolutely fine so far, but there is one odd thing concerning ical.
This is my code:
icalcomponent *root = icalparser_parse_string([iCalData cStringUsingEncoding:NSUTF8StringEncoding]);
if (root)
{
icalcomponent *currentEvent = icalcomponent_get_first_component(root, ICAL_VEVENT_COMPONENT);
while (currentEvent)
{
while(currentProperty)
{
icalvalue *value = icalproperty_get_value(currentProperty);
char *icalString = icalvalue_as_ical_string_r(value); //seems to leak
NSString *currentValueAsString = [NSString stringWithCString:icalString
encoding:NSUTF8StringEncoding];
icalvalue_free(value);
//...
//import data
//...
icalString = nil;
currentValueAsString = nil;
icalproperty_free(currentProperty);
currentProperty = icalcomponent_get_next_property(currentEvent, ICAL_ANY_PROPERTY);
} //end while
} //end while
icalcomponent_free(currentEvent);
}
icalcomponent_free(root);
//...
I did use instruments to check my memory usage and were able to find out, that this line seems to leak:
char *icalString = icalvalue_as_ical_string_r(value); //seems to leak
If I'd copy and paste this line 5 or six times my memory usage would grow about 400kb and never get released anymore.
There is no free method for the icalvalue_as_ical_string_r method because it's returning a char *..
Any suggestions how to solve this issue? I would appreciate any help!
EDIT
Taking a look at the apple doc says the following:
To get a C string from a string object, you are recommended to use UTF8String. This returns a const char * using UTF8 string encoding.
const char *cString = [#"Hello, world" UTF8String];
The C string you receive is owned by a temporary object, and will become invalid when automatic deallocation takes place. If you want to get a permanent C string, you must create a buffer and copy the contents of the const char * returned by the method.
But how to release a char * string properly now if using arc?
I tried to add #autorelease {...} in front of my while-loop but without any effort. Still increasing memory usage...
Careful with the statement "no free method...because it's returning a char*"; that is never something you can just assume.
In the absence of documentation you can look at the source code of the library to see what it does; for example:
http://libical.sourcearchive.com/documentation/0.44-2/icalvalue_8c-source.html
Unfortunately this function can do a lot of different things. There are certainly some cases where calling free() on the returned buffer would be right but maybe that hasn't been ensured in every case.
I think it would be best to request a proper deallocation method from the maintainers of the library. They need to clean up their own mess; the icalvalue_as_ical_string_r() function has at least a dozen cases in a switch that might have different deallocation requirements.
icalvalue_as_ical_string_r returns a char * because it has done a malloc() for your result string. If your pointer is non-NULL, you have to free() it after use.

Crash log without crash?

Hey this may be a stupid question but I couldn't find the answer anywhere, apologies if the answer is easily found and if my research skills are pants.
anyway is it possible to generate a crash report when an app doesn't crash? so say if a user encounters a bug could there be an option to allow them to generate a crash report which can then be sent to me? also how would I go about doing this?
Thanks for any help :)
I have used it couple of times when I had to print stack trace:
+ (NSArray *)backtrace
{
void* callstack[128];
int frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (
i = UncaughtExceptionHandlerSkipAddressCount;
i < UncaughtExceptionHandlerSkipAddressCount +
UncaughtExceptionHandlerReportAddressCount;
i++)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
"When an application crashes on the iPhone, it disappears without telling the user what happened. However, it is possible to add exception and signal handling to your applications so that an error message can be displayed to the user or you can save changes. It is even possible to try to recover from this situation without crashing at all."
Look at http://cocoawithlove.com/2010/05/handling-unhandled-exceptions-and.html
Here's what I use for my stacktraces:
NSArray *callStackArray = [exception callStackReturnAddresses];
int frameCount = [callStackArray count];
void *backtraceFrames[frameCount];
for (int i=0; i < frameCount; i++) {
backtraceFrames[i] = (void *)[[callStackArray objectAtIndex:i] unsignedIntegerValue];
}
char **strs = backtrace_symbols(backtraceFrames, frameCount);
NSMutableArray *backtraceArray = [NSMutableArray arrayWithCapacity:frameCount];
for (int i = 0; i < frameCount; i++) {
NSString *entry = [NSString stringWithUTF8String:strs[i]];
[backtraceArray addObject:entry];
}
free(strs);
You just have to make sure you don't do any harm to your app: http://landonf.bikemonkey.org/2011/09/14. You could also use PLCrashReporter to handle all your crashes or if you're lazy like me, use a service like Crittercism
I used to use backtrace_symbols for handling my crashes too, but then I found out that it could be dangerous since the method is not async-safe. I've since looked at a bunch of crash reporting solutions and went with Crittercism for my apps and it's been pretty sweet!
I suggest you check out TestFlight SDK released a few days ago. It has some awesome features like remote logging and even live crash reports.
For an ad hoc version, you could just call the function abort(), or throw and exception of some kind.
An App Store version will not be allowed to ship if it crashes at all during testing.

How to sync a section in a MutableArray with UITextView

I have one MutableArray (NSMutableArray *NoteBook;) in which I would like to store Notepages (I set up an object called NotePage.m which simply contains one NSString *noteText and a method to setNoteText). I also have an integer to keep track of my pages (NSUInteger currentPageCounter;).
I start my little program by setting up a new instance of NotePage:
NotePage *newPage = [[NotePage alloc] init];
[newPage setNoteText:#"Sample Text for a certain page..."];
I then copy this *newPage 3 times into my NoteBook Mutable Array:
[self.NoteBook insertObject:newPage atIndex:(self.currentPageCounter)];
This will give me 3 pages, at the indices 0,1,2. So far so good. Everything works splendid. But now comes the enemy, UITextView. First of all, I would like to display the contents of a page within my NoteBook in an UITextView. So I did this to sync one section in the MutableArray (e.g. page at index 0) with the UITextView which works fine:
NotePage *thisPage = [self.NoteBook objectAtIndex:self.currentPageCounter];
TextViewNote.text = thisPage.noteText;
The problem is, however, if I want to edit my UITextView and sync the updated text with the MutableArray. This is where the crashing happens... I have coded this in a separate method which can be clicked once the user is done editing the UITextView:
-(IBAction) savePage
{
NSString *tempString;
tempString = [NSString stringWithFormat:#"%#",TextViewNote.text];
NotePage *newPage = [[NotePage alloc] init];
[newPage setNoteText:tempString];
[self.NoteBook insertObject:newPage atIndex:(self.currentPageCounter)]; // e.g. at index 0 for page 1
[self.NoteBook removeObjectAtIndex:(currentPageCounter+1)]; // i.e. this is to remove the old page 1 which used to be at index 0 but now is at index 1
[self syncTextView];
[self syncCounter];
}
I guess there is a less cumbersome way (I'm still a beginner...) to simply replace an object at a certain index at a Mutable Array. As it stands now, it will simply crash once I try to move onwards to the next index position which is apparently not found anymore.
Thanks for any ideas!
I guess there is a less cumbersome way
(I'm still a beginner...) to simply
replace an object at a certain index
at a Mutable Array.
Indeed. Look at -[NSMutableArray replaceObjectAtIndex:withObject:].
Considering the error you posted, it sounds like you've got a bad pointer somewhere. The error is complaining that somewhere, a -[UITouchData length] message is being sent to an instance of NotePage. Turn on the 'Stop on Objective-C Exceptions' option in the debugger (under the Run menu). That should help you see where the problem is occurring.
NSMutableArray has a method replaceObjectAtIndex:withObject: that should do what you are trying to do.
You've got a memory management issue, however, unless you are calling release on the newPage after you add it to the array. Unless you are planning on making the NotePage class more complex, it might make sense simply to change the item's text rather than substituting a new object:
[[self.noteBook objectAtIndex:currentPageCounter] setNoteText:tempString];
(Also, be aware that inserting the newPage object into the array four times does not copy the object; it simply inserts the same reference four times. If you want four distinct objects, you will need to allocate four objects in a loop:
for(i = 0; i < 4; i++){
NotePage *newPage = [[NotePage alloc] init];
[newPage setNoteText:#"Dummy text"];
[self.notebook addObject:newPage];
[newPage release];
}

if-query: if (Nslog isEqualtoString #"...") - How can I make this?

I want my app to do something when the last NSLog has a certain string. I thought I could realize this with an if-query and isEqualtoString, but how can I make this?
Sorry for my bad English ;)
Maybe I don't understand what you're trying to do, but you can just create the string somewhere, log it, and then test it:
NSInteger _someInt = 2;
NSString *_someString = #"bananas";
NSString *_stringToBeLogged = [NSString stringWithFormat:#"%d %#", _someInt, _someString];
NSLog(#"%#", _stringToBeLogged);
if ([_stringToBeLogged isEqualToString:#"2 bananas"]) {
NSLog(#"I logged two bananas...");
}
You could consider creating your own custom log function which calls NSLog() after checking for your string constant. This would keep your code a bit cleaner if you want this functionality in multiple places and also allows you to easily extend the logging function further if desired.