My application generates loads of images and in order to save memory, I write these files to the temporary directory and read them when needed. I write two versions of the same image to the tmp folder one the thumbnail version at lower resolution and the other is full size. To make the file names unpredictable, I add a string hash at the end.
For instance I want to have two images one called "ThumbnailImage.fgl8bda" and the other "FullImage.fgl8bda"
This is the code I use:
NSString *fileName = #"Image.XXXXXXX";
NSString *thumbName = [#"Thumbnail" stringByAppendingFormat:#"%#", fileName];
NSString * thumbPath = [self writeToTempFile:thumbNailImage andName: thumbName];
NSString *fullName = [#"Full" stringByAppendingFormat:#"%#", fileName];
NSString *fullPath = [self writeToTempFile:fullImage andName: fullName];
However, the two files come out with different names, i.e. each time I use the fileName variable the hash is regenerated. For instance, my two images are called "ThumbnailImage.jhu078l" and "FullImage.ksi9ert".
Any idea how I can use the same hash more than once?
It is generally not safe to reuse a temporary file name suffix, because even if you can ensure A.ashkjd does not exist, there is a chance B.ashkjd is occupied.
You could construct a folder and store the two images in it, e.g.
char tmpl[] = "Image.XXXXXX";
char* res = mkdtemp(tmpl);
if (res != NULL) {
NSString* dirName = [NSString stringWithUTF8String:res];
NSString* thumbPath = [dirName stringByAppendingPathComponent:#"Thumbnail.png"];
[thumbImage writeToFile:thumbPath atomically:YES];
NSString* fullPath = [dirName stringByAppendingPathComponent:#"Full.png"];
[fullImage writeToFile:fullPath atomically:YES];
}
(Note: you need to remove the folder manually.)
#KennyTM has a correct solution, but he didn't explain the cause.
writeToTempFile does not use a hash to fill in the unique part of the name. Instead, it uses a new unique random string for each call.
#TimG
It doesn't really make a difference. That too ends up in different names.
When I write
NSString *fileName = #"Image.XXXXXXX";
The XXXXXXX part of the fileName is replaced by 7 random characters so that the filenames are not predictable.
Cheers
I'm new to objc so apologies if this is a stupid suggestion. What happens if you use stringByAppendingString instead of description that you're invoking:
NSString *thumbName = [#"Thumbnail" stringByAppendingString:fileName];
I don't see why the two aren't equivalent for this usage, but still.
Also, how/where are you generating the hash?
EDIT
#ar06 I think you're saying that your (I'm assuming it's your) writeToTempFile method does a replace on the XXXXX' in the fileName parameter to a random value. If so then there's your problem - it generates a new random every time it's called, and you're calling it twice. The code fragment you posted does work, because fileName is immutable; it will save with a 'XXXXX' extension.
Do you need to refer to these random suffixes later? Whatever mechanism you use for tracking them could be also used by writeToTempFile to attach the same value to the thumb and the fullsize.
FWIW I agree that Kenny's approach is better since you can't guarantee uniqueness.
Related
I have an app (Cocoa Touch, Web Browser), however I need to be able to compare an NSString with thousands of other strings. Here's the deal.
When a WebView loads, I get the URL. I need to compare this URL with literally thousands of results (27,847). Each of those numbers represents a line of text in a plain text file.
I would like to know the best way to go about getting the data from the text file, and comparing it with the NSString. I need to know if the URL that the WebView is loading contains any of these strings.
The app needs to be very fast, so I can't just parse through every line in the text file, turn it into an array, and then compare each and every result.
Please share your ideas. Thanks.
I think the cleanest solution is to:
Create a web service that can offload the work to a server and return a response. Since it sounds like you're building a web protection service, your database may grow to be quite substantial over time, and you can just scale your server up to increase its speed. Furthermore, you don't want to have to update your app every time the lookup data changes.
Other options are:
Use a local SQLite database. SQL databases should perform lookups relatively fast.
If you don't want to use any database, have you tried putting all the search strings into an NSDictionary or NSMutableDictionary object? This way, you would just check if the valueForKey: for the string you're searching for is nil.
Sample code for this:
NSDictionary *searchDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], #"google.com",
[NSNumber numberWithBool:YES], #"yahoo.com",
[NSNumber numberWithBool:YES], #"bing.com",
nil];
NSString *searchString = #"bing.com";
if ([searchDictionary valueForKey:searchString]) {
// search string found
} else {
// search string not found
}
Note: if you want the NSDictionary to perform case-insensitive comparisons, pre-load all values lowercase, and make the search string lowercase when using valueForKey:.
How much memory this could take is a whole other story, but I don't see how this comparison could be made much faster locally. I strongly recommend the remove web service approach, though.
Create a string from the file and enumerate through the lines.
NSString *stringToCheck;
NSData *bytesOfFile = [NSData dataWithContentsOfFile:#"/path/myfile.txt"];
NSString *fileString = [[NSString alloc] initWithData:bytesOfFile
encoding:NSUTF8Encoding];
__block BOOL foundMatch = NO;
[fileString enumerateLinesUsingBlock:^(NSString *line, BOOL *stop){
if([stringToCheck isEqualToString:line]){
*stop = YES;
foundMatch = YES;
}
}];
This is a job for regular expressions. Take all of the substrings you're looking for/filtering against, escape them appropriately (escaping characters such as [, ], |, and \, among others, with \), and join them with a |. The resulting string is your regular expression, which you apply to each URL.
You could loop through an entire array full of substrings, doing rangeOfString:options: with each one, but that's the slow way. A good regular expression implementation is built for this sort of thing, and I would hope that Apple's implementation is suitable.
That said, profile the hell out of it. I've seen some regex implementations choke on the | operator, so you'll want to make sure that Apple's is not one of them.
If you need to compare each string in your text file, you are going to have to compare it, no way around it.
What you can do however is do it on a background thread while showing some loading or something, and it won't feel as if the app got stuck.
I would suggest you try with NSDictionary first. You can load up all your URLs into this, and internally it will use some sort of hash table/map for very quick (O(1)) lookup.
You can then check the result of [dictionary objectForKey:userURL], and if it returns something then the URL matched one in the dictionary.
The only problem with this is that it requires an exact string match. If your dictionary contains http://server/foobar and the user enters http://server/FOOBAR (because it's a case-insensitive server), you are going to get a miss on your lookup. Similarly, adding ?foobar queries to the end of URLs will result in a miss. You could also add an explicit port with server:80, and with %XX character encoding you can create hundreds of variations of the same URL. You will have to account for this and canonicalize both the URLs in your dictionary, and the URL entered by the user prior to lookup.
Looking for some advice with an iOS application.
Essentially what my app does is generate a CSV file that logs certain events within a period of time. So users can press a button and an entry will be added to the log saying "Event of type X happened at Time T"
The way I'm doing this is by maintaining an NSMutableArray which stores NSStrings. Each event adds a string to the NSMutableArray.
When the user is done with a session, they can "export" the file. I'm using the NSMutableArray's writeToFile; then I use an e-mail interface to send that file as a CSV to a target e-mail.
It all works, except the CSV file that is generated has some meta-data in it. Specifically, I believe at the top of the file I see and at the beginning of each row of cells when opened in excel.
Is this something inherent in the data structure (NSMutableArray) or data type (NSString) that I am using? Is there a way for me to just get the raw data?
I can upload code if need be (I'm not near the work computer now though, so I'm testing the waters to see if there is something simple I can do to stop seeing this meta-data).
Thank you!
CSV is a very simple format. You can separate the strings with semi-colons and then write everything to a file using NSOutputStream.
This code assumes you already have a string array with CSV rows:
NSOutputStream* csvoutput = [NSOutputStream outputStreamToFileAtPath:filepath append:NO];
[csvoutput open];
for (NSString* str in array) {
NSString* tempStr = [str stringByAppendingString:#"\n"]; //new line
NSData *strData = [tempStr dataUsingEncoding:NSUTF8StringEncoding];
[csvoutput write:(uint8_t *)[strData bytes] maxLength:[strData length]];
}
[csvoutput close];
You better create a model class (Event) and fill the array with Event-instances instead of strings. Thats cleaner and more efficient. Then you would create the CSV-strings when exporting to a file.
I have a Mac application that keeps it's own log file. It appends info to the file using NSString's writeToFile method. One of the things that it logs are URL's of web services that it is interacting with. To encode the URL, I'm doing this:
searchString = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)searchString, NULL, (CFStringRef)#"!*'();:#&=+$,/?%#[]", kCFStringEncodingUTF8 );
The app then appends searchString to the rest of the URL and writes it to the log file. Now the problem is that after adding that URL encoding line, nothing seems to be getting written to the file. The program functions as expected otherwise however. Removing the line of code above results in all of the correct information being logged to the file (removing that line is not an option because searchString must be URL encoded).
Oh and I am using NSUTF8StringEncoding when writing the NSString to the file.
Thanks for any help.
EDIT: I know there's also a similar function to CFURLCreateStringByAddingPercentEscapes in NSString, but I've read that it doesn't always work. Can anyone shed some light on this if my original question cannot be answered? Thanks! (EDIT: same problem occurs when using stringByAddingPercentEscapesUsingEncoding:)
EDIT 2: Here's the code that I'm using to append messages to the log file.
+(void)logText:(NSString *)theString{
NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,NSUserDomainMask,YES) objectAtIndex:0];
NSString *path = [docsDirectory stringByAppendingPathComponent:#"Folder/File.log"];
NSString *fileContents = [[[NSString alloc] initWithContentsOfFile:path] autorelease];
if([fileContents lengthOfBytesUsingEncoding:NSUTF8StringEncoding] >= 204800){
fileContents = #"";
}
NSString *timeStamp = [[NSDate date] description];
timeStamp = [timeStamp stringByAppendingString:#": "];
timeStamp = [timeStamp stringByAppendingString:theString];
fileContents = [fileContents stringByAppendingString:timeStamp];
fileContents = [fileContents stringByAppendingString:#"\n"];
[fileContents writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL];
}
Because after almost a whole day no one else has offered any answers, I'm going to post a wild guess here: you're not accidentally using the string you want to output (with percent characters in it) as a format string are you?
That is, making the mistake of doing:
NSLog(#"In format strings you can use %# as a placeholder for an object, and %i for a plain C integer.")
Instead of:
NSLog(#"%#", #"In format strings you can use %# as a placeholder for an object, and %i for a plain C integer.");
But I'm going to be surprised if this turns out to be the cause of your problem, as it usually causes random-looking output, rather than absolutely no output. And in some cases, Xcode also gives compiler warnings about it (when I tried NSLog(myString), I got "warning: format not a string literal and no format arguments").
So don't shoot me down if this answer doesn't help. It would be easier to answer your question if you could show us more of your logging code. As for the one line you provided, I can't detect anything wrong with it.
Edit: Oops, I kind of missed that you mentioned you're using writeToFile:atomically:encoding:error: to write the string to the file, so it's even more unlikely you're accidentally treating it as a format string somewhere. But I'm going to leave this answer up for now. Again, you should really show us more of your code though ...
Edit: Regarding your question on a method in NSString that has similar percent encoding functionality, that would be stringByAddingPercentEscapesUsingEncoding:. I'm not sure what kind of problems you're thinking of when you say you've heard it doesn't always work. But one thing is that CFURLCreateStringByAddingPercentEscapes allows you to specify extra characters that don't normally have to be escaped but which you still want to be escaped, while the method of NSString doesn't allow you to specify this.
I'm getting a EXEC_BAD_ACCESS when concatenting a large string.
I've read from a feed and to create my webview I build up my string like:
NSString *pageData = #"<h1>header</h1>";
pageData = [pageData stringByAppendingFormat#"<p>"];
pageData = [pageData stringByAppendingFormat#"self.bodyText"];
pageData = [pageData stringByAppendingFormat#"</p>"];
etc
The problem I've got is self.bodytext is 21,089 characters with spaces when I do a count on word.
Is there a better method for doing this?
Thanks
You would definitely want to use NSMutableString for something like this:
NSMutableString * pageData = [NSMutableString stringWithCapacity:0];
[pageData appendFormat:#"<h1>header</h1>"];
[pageData appendFormat:#"<p>"];
...
NSMutableString is designed for this kind of sequential concatenation, where the basic NSString class is really not meant to be used in this manner. Your original code would actually allocate a new NSString every time you called stringByAppendFormat:, and then procede to copy into it all of the thousands of characters you had already appended. This could easily result in an out of memory error, since the size of the temporary strings would be growing exponentially as you add more and more calls.
Using NSMutableString will not re-copy all of the string data when you call appendFormat:, since the mutable string maintains an internal buffer and simply tacks new strings on to the end of it. Depending on the size of your string, you may want to reserve a huge chunk of memory ahead of time (use a meaningful number for the ...WithCapacity: argument). But there is no need to go that route unless you actually run into performance issues.
There are a few problems with your sample code:
You should be using a NSMutableString to build up an output string by appending multiple parts. NSString is an immutable class which means that each time you call stringByAppendingFormat: you are incurring the overhead of creating an additional new NSString object which will need to be collected and released by the autorelease pool.
NSMutableString * pageData = [NSMutableString stringWithCapacity:0];
You should use appendString: on your NSMutableString to append content, instead of stringByAppendingFormat: or appendFormat:. The format methods are intended for creating new strings based on a format specifier which includes special fields as placeholders. See Formatting String Objects for more details. When you're using stringByAppendingFormat: with just a literal string like your code has, you are incurring the overhead of parsing the string for the non-existant placeholders, and more importantly, if the string happens to have a placeholder (or something that looks like one) in it, you'll end up with the EXEC_BAD_ACCESS crash that you are getting. Most likely this happening when your bodyText is appended. Thus if you simply want to append a '' to your NSMutableString do something like this:
[pageData appendString:#"<p>"];
If you want to append the contents of the self.bodyText property to the string, you shouldn't put the name of the property inside of a string literal (i.e. #"self.bodyText" is the literal string "self.bodyText", not the contents of the property. Try:
[pageData appendString:self.bodyText];
As an example, you could actually combine all three lines of your sample code by using a format specification:
pageData = [pageData stringByAppendingFormat:#"<p>%#</p>", self.bodyText];
In the format specification %# is a placeholder that means insert the result of sending the description or descriptionWithLocale: message to the object. For an NSString this is simply the contents of the string.
I doubt the length of the string is really a problem. A 50,000-character string is only about 100 KB. But you want to be very careful about using format strings. If your string contains something that looks like a formatting specifier, there had better be a corresponding argument or you'll get garbage if you're lucky and a crash if you're not. I suspect this is the error, since there is no other obvious problem from your description. Be careful about what you put in there, and avoid ever putting dynamic text in a format string — just put a %# in the format string and pass the dynamic text as an argument.
Use appendString: instead of appendFormat: when dealing with arbitrary strings.
pageData = [pageData stringByAppendingString:#"<p>"];
pageData = [pageData stringByAppendingString:#"self.bodyText"];
pageData = [pageData stringByAppendingString:#"</p>"];
or do not use an arbitrary string as the format:
pageData = [pageData stringByAppendingFormat:#"<p>%#</p>" , #"self.bodyText"];
If you are building the string up in pieces, use NSMutableString instead of several stringBy calls.
Remember that % is a special character for formatted strings and for url escapes, so if bodyText contains a url it could easily cause a crash.
This works -- it does compile -- but I just wanted to check if it would be considered good practice or something to be avoided?
NSString *fileName = #"image";
fileName = [fileName stringByAppendingString:#".png"];
NSLog(#"TEST : %#", fileName);
OUTPUT: TEST : image.png
Might be better written with a temporary variable:
NSString *fileName = #"image";
NSString *tempName;
tempName = [fileName stringByAppendingString:#".png"];
NSLog(#"TEST : %#", tempName);
just curious.
Internally, compilers will normally break your code up into a representation called "Single Static Assignment" where a given variable is only ever assigned one value and all statements are as simple as possible (compound elements are separated out into different lines). Your second example follows this approach.
Programmers do sometimes write like this. It is considered the clearest way of writing code since you can write all statements as basic tuples: A = B operator C. But it is normally considered too verbose for code that is "obvious", so it is an uncommon style (outside of situations where you're trying to make very cryptic code comprehensible).
Generally speaking, programmers will not be confused by your first example and it is considered acceptable where you don't need the original fileName again. However, many Obj-C programmers, encourage the following style:
NSString *fileName = [#"image" stringByAppendingString:#".png"];
NSLog(#"TEST : %#", fileName);
or even (depending on horizontal space on the line):
NSLog(#"TEST : %#", [#"image" stringByAppendingString:#".png"]);
i.e. if you only use a variable once, don't name it (just use it in place).
On a stylistic note though, if you were following the Single Static Assigment approach, you shouldn't use tempName as your variable name since it doesn't explain the role of the variable -- you'd instead use something like fileNameWithExtension. In a broader sense, I normally avoid using "temp" as a prefix since it is too easy to start naming everything "temp" (all local variables are temporary so it has little meaning).
The first line is declaring an NSString literal. It has storage that lasts the lifetime of the process, so doesn't need to be released.
The call to stringByAppendingString returns an autoreleased NSString. That should not be released either, but will last until it gets to the next autorelease pool drain.
So assigning the result of the the stringByAppendingString call back to the fileName pointer is perfectly fine in this case. In general, however, you should check what your object lifetimes are, and handle them accordingly (e.g. if fileName had been declared as a string that you own the memory to you would need to release it, so using a temp going to be necessary).
The other thing to check is if you're doing anything with fileName after this snippet - e.g. holding on to it in a instance variable - in which case your will need to retain it.
The difference is merely whether you still need the reference to the literal string or not. From the memory management POV and the object creational POV it really shouldn't matter. One thing to keep in mind though is that the second example makes it slightly easier when debugging. My preferred version would look like this:
NSString *fileName = #"image";
NSString *tempName = [fileName stringByAppendingString:#".png"];
NSLog(#"TEST : %#", tempName);
But in the end this is just a matter of preference.
I think you're right this is really down to preferred style.
Personally I like your first example, the codes not complicated and the first version is concise and easier on the eyes. Theres too much of the 'language' hiding what it's doing in the second example.
As noted memory management doesn't seem to be an issue in the examples.