String storage in Objective C - objective-c

I have a handful of SQL queries in my objective-c project. Is there any way I can store these in separate files query1.sql, query2.sql, in my tree and sub them in at compile time?
Motivating this is the fact that my queries are either copy-pastable but unreadable (no whitespace):
NSString* query = #"SELECT A.a, B.* from myTable A INNER JOIN otherTable B ON ...
Or readable but littered with line splices:
NSString* query = #"SELECT A.a, B.* \
FROM myTable A
INNER JOIN \
...
I am aware of .strings files for objective c, but thought they were more for localization. Is that right?
EDIT: I could have been more clear: I want to store my individual sql statements in separate text files that 1.) are valid SQL independently, and 2.) can be imported at compile time.

you can try this
NSString* query =
#"SELECT A.a, B.* "
"FROM myTable A "
"INNER JOIN "
"..."
or move the sql string to file query1.sql
"SELECT A.a, B.* "
"FROM myTable A "
"INNER JOIN "
"..."
and
NSString *query= #(
#include "query1.sql"
);

I'm going to say "no", but we'll see if anyone disagrees!
If you are prepared to forgo the compile time inclusion just have the sql files copied into your bundle and read them in. This is of course a runtime cost on each run, but that probably isn't significant, but YMMV!
Another, more involve approach - but you only need to write it once - is to develop a script, say in Ruby, which transforms your SQL files into Objective-C source and include it as build phase. Apple has an example somewhere of doing this (using Ruby, hence the suggestion of Ruby - start with Apple's sample and edit it) to run the preprocessor over strings files. The approach does work very well, but you need to put the effort in first and then can reuse it in all your projects.
HTH

The other recommendations for macros or multi-line strings are good, but if you want to put them in .strings files, that's fine. Just use NSLocalizedStringFromTable so that you can specify a different strings table than your localized strings. And of course, don't localize the SQL.
But I'd probably build something custom that read the file and cached its contents. Building exactly what you want would probably take about fifteen minutes, and there's no harm in it if it makes development simpler.
If you want to store all your select queries in their own files (like you say), just use NSBundle to load find your resource file by some token. Something like:
NSString * SQL(NSString *key) {
NSString *path = [[NSBundle mainBundle] pathForResource:key ofType:#"sql"];
return [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
}
NSString * const SELECT_EM = #"SELECT_EM";
NSString *select_query = SQL(SELECT_EM);
Then you'd have a file called SELECT_EM.sql in your resources directory.
Again, lots of choices; they're all fine. If you're calling SQL a lot, you should probably add an NSDictionary cache for them so you don't have to load the string every time.

Related

Performance of sorting NSURLs with localizedStandardCompare

I need to sort a NSMutableArray containing NSURLs with localizedStandardCompare:
[array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *f1 = [(NSURL *)obj1 absoluteString];
NSString *f2 = [(NSURL *)obj2 absoluteString];
return [f1 localizedStandardCompare:f2];
}];
This works fine, but I worry a bit about the performance: the block will be evaluated n log n times during the sort, so I'd like it to be fast (the array might have up to 100,000 elements). Since localizedStandardCompare is only available on NSString, I need to convert the URLs to strings. Above, I use absoluteString, but there are other methods that return a NSString, for example relativeString. Reading the NSURL class reference, I get the impression that relativeString might be faster, since the URL does not need to be resolved, but this is my first time with Cocoa and OS-X, and thus just a wild guess.
Additional constraint: in this case, all URLs come from a NSDirectoryEnumerator on local storage, so all are file URLs. It would be a bonus if the method would work for all kinds of URL, though.
My question: which method should I use to convert NSURL to NSString for best performance?
Profiling all possible methods might be possible, but I have only one (rather fast) OS-X machine, and who knows - one day the code might end up on iOS.
I'm using Xcode 4.5.2 on OS-X 10.8.2, but the program should work on older version, too (within reasonable bounds).
You may need to use Carbon's FSCatalogSearch, which is faster than NSDirectoryEnumerator. As for getting the path, I see no choice.
The only thing you may consider for speeding up the sorting is that the paths are partially sorted, because the file system will return all the files of the same folder in alphabetical order.
So you may want to take all the path of the same directory and merge them with the other results.
For example the home contents may be:
ab1.txt
bb.txt
c.txt
The documents directory may contain:
adf.txt
fgh.txt
So you just merge them with a customized algorithm, which just applies the merge part of a mergesort.
I benchmarked the sort. It turned out that absoluteString and relativeString are much faster that path or relativePath.
Sorting about 26000 entries:
relativeString 550ms
absoluteString 580ms
path 920ms
relativePath 960ms
field access 480ms
For field access, I put the value of absoluteString into a field prior to the sort and access that. So, the ...String accessors are almost as fast as field access, and thus a good choice for my use case.

How to embed SQL schema in an Objective-C command-line app?

I have an SQL schema that I need to feed to SQLite in my Objective-C command-line app. I’d like the schema to be a part of the binary, so that I can distribute just one file. Unlike a regular Mac or iOS app, the binary has no resource bundle, so the traditional way of storing resources inside the app bundle is out. Is there an elegant way to include the schema in the source? I know I can simply store it as a multiline string in a header, but that sucks.
Schema.h
extern NSString * someSchema;
Schema.m
NSString * someSchema = #"CREATE TABLE IF EXISTS blah...."
#"More SQL here"
#"more SQL here";
What about creating an array of sql statements, and then you use an enum to access the statements.
Schema.h file:
static const NSString *sqlStatements[] = {
#"CREATE TABLE...",
#"SELECT * FROM ...",
... // Lots of other statements
#"DELETE ..."
};
typedef enum {
SQLCREATECommand=0,
SQLSelectCommand,
... // Matching enums
SQLDeleteCommand
} SQLCommands;
Used in some other file:
NSString *stmt = sqlStatements[SQLCREATECommand];
The benefit of doing it this way is that the code becomes more maintainable.
One interesting solution I have come up with is extended attributes. It’s possible to add a Run Script phase to the Xcode build process that does something like:
xattr -w com.company.Schema "`cat SQL/schema.sql`" \
${BUILT_PRODUCTS_DIR}/${EXECUTABLE_NAME}
And then in runtime:
const size_t maxSchemaSize = 1000;
char schema[maxSchemaSize] = {0};
getxattr(argv[0], "com.company.Schema", schema, maxSchemaSize, 0, 0);
NSLog(#"%s", schema);
This way I can keep the schema in a separate file without mangling it into a header file. The obvious downside is that the extended attribute might not survive some file operations.

How do I programmatically find the user's logging directory?

Given an app called ExampleApp, I want to find "~/Library/Logs/ExampleApp" basically without using hard coded paths. There exists NSSearchPathForDirectoriesInDomains, which you can use to find things like "~/Library/Application Support/ExampleApp" using the NSApplicationSupportDirectory search term, but there doesn't seem to be a search term for logging.
I don't think ~/Library/Logs is non-standard, since CrashReporter puts its logs there.
Try this :
NSString* libraryPath = [NSHomeDirectory stringByAppendingPathComponent:#"Library/Logs"];
Update (~/Library/Logs/AppName) :
NSString* bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleName"];
NSString* logsPath = [NSString stringWithFormat:#"Library/Logs/%#",bundleName];
NSString* libraryPath = [NSHomeDirectory stringByAppendingPathComponent:logsPath];
Cocoa doesn't provide a means for finding all of the standard directories. The old FSFindFolder() function can provide many more, but does involve converting from an FSRef back to a path or URL. Apple discourages its use, but it's still the only way to get certain standard directories without hard-coding. That said, it won't ever incorporate your app name. You have to append that.
Edited to add: the link to the legacy docs.

How to use string hash more than once?

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.

Should I use an intermediate temp variable when appending to an NSString?

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.