Hi folks hope you can help. I'm attempting to create an anagram checker for an iPhone application. What I want to do is to be able to take a long string of maximum 81 letters and then check this against a word list to find all possible permutations of any length.
I've figured out how to do this on the simulator, but when I run this on the iPhone it is extremely slow, taking around 90 seconds to loop through the entire db (about 110000 rows). I checked Instruments and it doesn't show any memory leaks. However when I use Object Allocations it is clear that running the query creates a massive allocation for CFString that drains everything else. This immediately runs up 3.09 MB under the overall bytes column.
(In the code below, I've stripped out all of the anagram checking code as I wanted to identify what was causing the problem. So all this does at present is loop through the db without any output).
//create query
NSString *querySQL2 = #"SELECT name FROM table ";
sqlite3_stmt *statement2;
const char *query_stmt2 = [querySQL2 UTF8String];
sqlite3_prepare_v2(contactDB, query_stmt2, -1, &statement2, NULL);
//loop through all rows of database
while (sqlite3_step(statement2) == SQLITE_ROW)
{
NSString *laststring = [[NSString alloc] initWithUTF8String:
(const char *) sqlite3_column_text(statement2, 0)];
[laststring release];
}
sqlite3_finalize(statement2);
sqlite3_close(contactDB);
}
It seems obvious to me that the creation of 'laststring' below is what is sucking up all the memory. So why is it that when I put [laststring release]; at the end of the while loop it appears to have no effect? I've run this code with and without releasing and the same quantity of memory is used up. I've also tried wrapping an autorelease around it and this also had no effect.
I've read several other queries on looping through SQLite. Some of them suggested indexing but I'm not sure this will save me significant amounts of time with this problem. Also if I am searching all possible permutations from a large string of 81 letters, I'm guessing that at least 50% of the word list will need to be checked through anyway.
Any suggestions on how to keep CFString down?
Many thanks
Dave
Why do you alloc the NSString inside the while() loop?
while(sqlite3_step(compiledStatement) == SQLITE_ROW)
{
NSString *laststring = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement2, 0)];
// do something with laststring
}
would suffice wouldn't it?
Related
I hope it's even possible.
I've got code to load really big file (dictionary) after app started:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSString *filePath = [NSString stringWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:#"en_US" ofType:#"txt"]
encoding:NSUTF8StringEncoding error:nil];
dictionary = [NSSet setWithArray:[NSArray arrayWithArray:
[filePath componentsSeparatedByCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]]]];
}
Also I've got proper code in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
self.progressView.progress = 0.0;
[self performSelectorOnMainThread:
#selector(applicationDidFinishLaunching:)
withObject:nil
waitUntilDone:NO];
}
But I've got not a clue how to force UIProgressView to show progress of creating NSSet dictionary from an NSArray. It takes few seconds in iOS Simulator, so on device it can take over a dozen. I really need to show progress, and I prefer this way than Activity Indicator View.
I'm interested in how to get count of imported items at the moment (count of all items is known).
Any idea? :)
You need to use NSURLRequest/NSURLConnection to start an asynchronous request to get the dictionary data. The delegates of NSURLConnection will give you the bytes read and bytes remaining where you can perform a async dispatch to the main thread to update your progress view value.
If you have a long text file to parse, the framework classes will take long time. You can't change that. And you can't change that they will never call back during work to tell you about their progress.
Your only chance is to perform text file splitting on your own and set your progress as you dig yourself through your textfile. You can split a textfile with regular expressions. The "\w+" collect words with at least one character.
If you set the progress bar in a heavy working method you have to give it some time to refresh the screen once in a while. See the "futuredate magic" below.
Maybe the following can act as a starting point - in the code below the content of the text file is stored in the NSString "content".
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:#"\\w+"
options:NSRegularExpressionCaseInsensitive
error:&error];
[regex enumerateMatchesInString:content
options:0
range:NSMakeRange(0, [content length])
usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
// push one match to the dictionary
[dictionary addObject:[content substringWithRange:match.range]];
// update the progress bar
NSDate* futureDate = [NSDate dateWithTimeInterval:0.001 sinceDate:[NSDate date]];
[[NSRunLoop currentRunLoop] runUntilDate:futureDate];
self.progressbar.progress = (float) match.range.location/content.length;
}];
Note that the code is not optimized for speed. For example you should not update the progress bar with every token. Depending on how much tokens you have - count the detected ones and update the pgogress bar every 100 matches, or so...
Use SVProgressHUD. It has delegates such as showProgress to show progress.
EDIT:
Note that this is just a suggestion to the approach and not practical implementation. Depending on your observation you may or may not choose to implement it.
If it's a local file, you can take some calculated guess about the total time it takes to read the entire file. Then divide this time by 100.
Create an async queue to read from file using one of arrayWithContentsOfFile or dictionaryWithContentsOfFile or whatever. Implement a timer on main thread, and increment progress by 1 through it, and keep posting progress on main thread (do not read progress from async thread because it is never going to tell you of that, we are faking it).
Once arrayWithContentsOfFile returns, post 100% on main thread. You may need to tweak a bit to be accurate (like in Windows where you keep seeing file download time fluctuating) but variation will not be much I suppose. Every type of target device would have given hardware and memory so it would be simply a function of file size.
Or at least I can't figure out how to locate the error. I've read a ton of stuff but every time I go into my Product scheme and turn on Guard Malloc my program won't run.
The line of code that is giving me the problem is this... It happens on the NSString *str = ... line
- (void) setVolumeCompletionDate: (NSString *)volumeID: (double)completionDate
{
sqlite3_stmt *pStmt;
NSString *str = [NSString stringWithFormat:#"UPDATE Volumes SET GoalDate = %d WHERE VolumeID = '%#'", completionDate, volumeID];
//more stuff
}
If I hardcode the completionDate value (1350129600) on that line, the code works just fine, so it seems from everything I've read that the memory of completionDate is getting stepped on somewhere, or something bad is happening. My project is set up for ARC.
I think part of my problem is I don't even know where to go to begin to resolve this problem. I don't even know what to search for. All of the posts I've read about tracking it down with some Malloc tool don't make sense because I can't get that to run.
I would like to be able to figure this out. Maybe I need more, and complete steps, for debugging and tracing through things. Many of the answers I've read seem like they are written for people who know everything, because so much is left out of the answer, or so much is assumed about the person asking the question. If there is a better place for me to go to ask my question, please point me in that direction.
Thanks a lot. I appreciate your help.
completionDate is a double, so use %f instead of %d.
https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html
I'm quite new to iPhone development. My target is a remote control app, the server of which is TightVNC. But I met a problem and it's driving me crazy... I've successfully connected to the server(using socket), and next would like to request desktop update at least every one second. So here comes the timer which is created through the selector in "performSelectorInBackground". The timer's main task is as fellow:
int picLength;
[self readExact:(char*)(&picLength) bySize:sizeof(int)];
char *picBuffer;
picBuffer = (char *)malloc(picLength);
[self readExact:picBuffer bySize:picLength];
NSData *picData = [[NSData alloc]initWithBytes:picBuffer length:picLength];
[self performSelectorOnMainThread:#selector(setPicInMainThread:) withObject:picData waitUntilDone:YES];
[picData release];
free(picBuffer);
And "setPicInMainThread" is as follow (each picture is around 200KB, iTouch is connected to PC through computer-to-computer wifi, so the speed would be fast enough):
- (void) setPicInMainThread:(NSData *)data {
[chatController.imageView.image release];
chatController.imageView.image = [UIImage imageWithData:data];
}
The app crushes after presenting the first desktop update. I am wondering if I've met the "memory leak" concerning NSTimer and NSData, which lots of people is talking about... If so, is there any way to solve the problem? Thank you very much for helping!
Your malloc code is looking bad. Why would you take the address of an int and then cast it as a character pointer? Then why would even pass an int for the size to the same function you pass a char * to get set? I have a feeling you are getting an EXC_BAD_ACCESS and it is because of your first few lines and the readExact method. Make sure you get the length the correct and pass the correct parameters.
int picLength;
[self readExact:(char*)(&picLength) bySize:sizeof(int)];
char *picBuffer;
picBuffer = (char *)malloc(picLength);
What I'm doing:
I am reading some data off a file several times while my app runs. I use the following code to do so:
NSString *dataPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"data.txt"];
NSString *data = [NSString stringWithContentsOfFile:dataPath encoding:NSStringEncodingConversionExternalRepresentation error:NULL];
NSArray *components = [data componentsSeparatedByString:#"|||||"];
The first time this is called, it works as expected - I get an array of length 5, each section containing a relevant string.
What goes wrong:
This file is never modified. Yet, when I call the same procedure a second time (or third, fourth etc) I don't get the same result. Instead, an array of length 1 is returned, containing only part of the necessary data.
Why? I can't see any reason for this to go wrong... any help is much appreciated!
Since the file is in you AppBundle this means that you can't modify this file at all.
Are you sure that, where ever this code is called, the autorelease object are retained correctly?
If you call this block of code every time you want this data, it might be an idea to save the results of the first time and use that every time. This will speed things up a bit.
Turns out that the code works fine. The problem was elsewhere in my code, where I was (accidentally) accessing protected directories. As a result, iOS blocked my app from accessing any files at all :o
Hey all, I'm a total noob when it comes to Objective-C / iPhone development.
I'm trying to pull in text from a SQLite DB. I have a while loop that looks like this:
while(sqlite3_step(selectstmt) == SQLITE_ROW) {
And within that loop, this prints to the log just fine:
NSLog(#"Text: %s",sqlite3_column_text(selectstmt, 1));
This does not work:
Category *categoryObj = [[Category alloc] initWithPrimaryKey:primaryKey];
categoryObj.categoryName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 1)];
NSLog(#"cat name: %s",categoryObj.categoryName);
When I run the above and look at the logs I see:
cat name: ‡}00å
I tried to write the field out to a label, thinking it might be something specific to the NSLog but nothing shows up there. Clearly I'm missing something fundamental but I'm at a loss for what it is.
Log your string with %# instead of %s and you'll be fine. NSStrings aren't pointers to characters, they're full-fledged objects, so you need to use the "object" placeholder in the log format string.
This has the added advantage of doing the right thing with non-ASCII strings and all of the other important things that NSString gives you.
Note that if you had just logged the result from SQLite directly instead of creating an NSString with it, then your %s would have been correct.
Remember: %s is for C strings, %# is for Objective-C objects.