Moving Objective-C hard-coded string literals to constants automatically - objective-c

I have a project source code with like 100s of classes that liberally uses hard coded strings throughout the code.
NSArray *sql_stmt = [[NSArray alloc] initWithObjects: DB_QUERY,
[[jsonDictionary objectAtIndex:i] valueForKey:#"RECORD_ID"],
[[jsonDictionary objectAtIndex:i] valueForKey:#"RECORD_LANGUAGE"],
[[jsonDictionary objectAtIndex:i] valueForKey:#"INDEX_PATH"],
[[jsonDictionary objectAtIndex:i] valueForKey:#"KEY"],
[[jsonDictionary objectAtIndex:i] valueForKey:#"VALUE"],nil];
The requirement is to extract all such hardcoded string literals and move it to a class that holds all the constants.
#define RECORD_ID #"RECORD_ID"
#define RECORD_LANGUAGE #"RECORD_LANGUAGE"
etc
It will be too much effort to move all those 100s of strings manually from each class.
Is there a way we could automate this in XCode ? If there is no built in way to do it, I was wondering whether XCode supports writing a macro for this purpose.
Though I can quickly put together a Java or C# based GUI tool where I could paste the source code of one class at a time and get all the strings extracted and replaced by auto-generated name constants, I am hoping to find a solution without having to leave XCode.

Related

Unique Instances of NSString for empty or 1-char strings

I would like to understand more about the way XCode/Objective-C handle constant strings. I found a related question, but I would like more information. Consider the following code:
NSString *a = [[NSString alloc] initWithUTF8String:[[_textFieldA stringValue] UTF8String]];
NSString *b = [[NSString alloc] initWithUTF8String:[[_textFieldB stringValue] UTF8String]];
NSString *c = [a copy];
NSString *d = [a mutableCopy];
Note that the textFields are just a way to set the strings at runtime ensuring that the compiler doesn't get too smart on me and build in a single instance.
If my text fields are empty, or contain a single character such as "x" or "$", then a == b == c == the same constant NSString instance. If I instead provide "xy", then a == c != b. d is always unique, as one might expect since it is mutable.
Now normally this wouldn't be an issue, I'm not trying to modify the contents of these strings, however, I am working on a system where I frequently use objc_setAssociatedObject. So here now I might come accross an empty string, and then set associated object data on it, and then have another empty string and collide with the first.
I have, for the moment, solved my issue by creating mutable strings instead.
So my questions:
Is this an Objective-C specification, or an XCode excentricity?
Does anyone know how the instance is determined? Why "x" get's one instance, but not "xy"? I would think some internal dictionary is involved and there's no good reason to stop at 1 character.
Is there a way to turn this off, so all empty strings are unique instances, or other suggestions?
I am using XCode 5.1.1, OSX 10.9.4, SDK 10.9.
Thank you!
Is this an Objective-C specification, or an XCode excentricity?
It is just implementation detail. Not documented any where. These kind of behaviour may changed in future without notice.
Does anyone know how the instance is determined? Why "x" get's one instance, but not "xy"? I would think some internal dictionary is involved and there's no good reason to stop at 1 character.
No until someone able to access source code want to share the details with us.
Is there a way to turn this off, so all empty strings are unique instances, or other suggestions?
No way to turn it off. Don't use objc_setAssociatedObject with NSString
As #Ken Thomases said in comment
In general, it probably doesn't make sense to use objc_setAssociatedObject() with any value class.
Some other examples are NSNumber, NSData and NSValue. They are often cached and reused.

Where to store strings for iOS app [duplicate]

This question already has answers here:
Constants in Objective-C
(14 answers)
Closed 8 years ago.
I have an iOS app that requires me to have a "bank" of multiple strings. What I mean is that I need to have several strings that I can call upon at any time. Here is what I am thinking of.
// Strings.h
#define STR_ONE #"1"
#define STR_TWO #"2"
// ...
And when I need to use these strings, I simply include the header file. I chose to go with a header file because there will be many of these strings, and I just wanted to keep them separate.
So the question: Is this the best approach to solve my problem? Are there any alternate (and better) ways that I am missing?
Side notes: Is there any memory management I need to be thinking about here?
Should this be written to a file, and drawn upon from there?
Thankyou
NSArray: you can store a fixed amount of string insiden an array
NSArray* nameArr = [NSArray arrayWithObjects: #"Jill Valentine", #"Peter Griffin", #"Meg Griffin"
NSMutableArray: this type of array can expand and decrease in size.
NSMutableArray *names = [[NSMutableArray alloc] init];
[self.names addObject:#"Harry Potter"];
If the amount of Strings is not enorm, a simple Plist will work for you. But i also would recommend you to read about core data.
Property List Link

Caesar Shift Encryption Objective C

I'm learning Objective C and iOS development. I'm trying to recreate some of the projects we did in my Java class (I know they're completely different) but I'm running into trouble in one of the projects. We were doing a caesar shift in a lab one day. A string manipulation lab. It was a really basic deal in Java... a for loop through the string and change each character. I can't seem to find any way to change individual characters in Objective C. I've looked through the NSMutableString documentation and NSString documentation and I know I can do a
[NSString stringByReplacingCharactersInRange:(NSRange *) withString:(NSString *)
but that doesn't really help because I don't know what I'm going to be replacing with. I need to find a way to grab a character at a specific index and change it. Any ideas?
Sounds like you are looking for the [NSString characterAtIndex:(NSUInteger)] method
E.g.
NSString *string = #"abcde";
NSString *character = [NSString stringWithFormat:#"%C",[string characterAtIndex: 0]];
NSLog(#"%#", character);
Result: a
Using this, and an NSMutableString, you can build the string you need.
Ussing appendString you can add to the end of an NSMutableString
Probably the best way to do this would be using a good old C-string, as that allows you to change the bytes without the overhead of reallocating a different string every time:
NSString *ceasarShift(NSString *input)
{
char *UTF8Str = strdup([input UTF8String]);
int length = [input length];
for (int i = 0; i < length; i++)
{
UTF8Str[i] = changeValueOf(UTF8Str[i]); // some code here to change the value
}
NSString *result = [NSString stringWithUTF8String:UTF8Str];
free(UTF8Str);
return result;
}
This reduces overhead, and although you have to free the data you allocated when you are done, it gives you the advantage of not relying on a high level API, improving performance drastically. (The difference between an array set and a dynamic method lookup is ~5 CPU cycles, which means a lot if you are doing any major sort of encryption)
Maybe also look into NSMutableData for this kind of task, instead of NSString, as the random \0 may per chance appear in the result string.

Preferred way of storing data in OS X/Cocoa? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
iOS store just a little bit of data
New OS X dev here. I have a modicum of user data I need to store (just paths to recently opened files, really). What is the preferred way of storing these in Cocoa land? I've heard of Core Data before but, as a Windows dev who has encountered tons of APIs from MS like this, does anyone actually use this?
I could just write everything to my own file, of course, but I'd prefer to do things The Right Way(TM).
Any suggestions would be great!
If your application is document based, the list of recently opened files is automatically stored for you. If you need to store them yourself, then I would suggest using NSUserDefaults. It is the most common way to store lightweight information such as preferences and recently used items.
Yes, people do use core data, but it is usually used for more complex data, such as a document with different parts.
See my answer to this thread for five suggestions for storing data. Although that thread covers iOS and therefore Cocoa Touch instead of Cocoa, the answers are all pretty much the same.
Note that the first answer, NSUserDefaults, is meant for saving data like app preferences. That might be most appropriate if the application will always want to load the same set of data; if the data is more like a document, where you might have different sets of data stored in different files, you should use one of the other methods. Writing a property list would probably be simplest in this case:
// store some words in an array and write to a file at pathToFile
NSMutableArray *array = [NSMutableArray array];
[array addObjects: #"foo", #"bar", #"baz", nil];
[array writeToFile:pathToFile];
// (later) read contents of the file at pathToFile into a new array
NSArray *words = [NSArray arrayWithContentsOfFile:pathToFile];
As for Core Data, yes, many people use it. It's a very nice way to manage persistent objects. However, it sounds like it's way more than you need for just storing a bunch of paths.
As ughoavgfhw mentioned, the NSDocument architecture already takes care of keeping a list of recent documents. (If you look through your Preferences folder, the *.LSSharedFileList.plist preference files hold this data).
If you take a look at those files in Property List Editor or Xcode 4, you'll see the preferred way to store a reference to a file in a persistent manner is to use Alias (or "Bookmark") data. If you're coming from a Windows/*nix background, alias data can keep track of an item even if it's renamed or moved.
If you need to store a list of recent files by yourself, and can require OS X 10.6+, you can use NSUserDefaults, along with the bookmark data functionality found in NSURL.
In your method that opens files, you could do something like this:
NSString * const MDRecentDocumentsKey = #"MDRecentDocuments";
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
// assume single item
NSURL *URL = [NSURL fileURLWithPath:[filenames objectAtIndex:0]];
NSMutableArray *recentAppBookmarks =
[[[[NSUserDefaults standardUserDefaults] objectForKey:MDRecentDocumentsKey]
mutableCopy] autorelease];
// assume 20 item limit
if ([recentAppBookmarks count] + 1 > 20) {
[recentAppBookmarks removeLastObject];
}
NSData *data = [ bookmarkDataWithOptions:0 includingResourceValuesForKeys:nil
relativeToURL:nil error:NULL];
[recentAppBookmarks insertObject:data atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:recentAppBookmarks
forKey:MDRecentDocumentsKey];
}
To get the list of recent files at app launch, you could do something like this:
- (void)awakeFromNib {
recentAppURLs = [[NSMutableArray alloc] init];
NSArray *recentAppBookmarks =
[[NSUserDefaults standardUserDefaults] objectForKey:MDRecentDocumentsKey];
for (NSData *bookmarkData in recentAppBookmarks) {
NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithoutUI|NSURLBookmarkResolutionWithoutMounting
relativeToURL:nil bookmarkDataIsStale:NULL error:NULL];
if (resolvedURL) [recentAppURLs addObject:resolvedURL];
}
}
Otherwise, if you need compatibility with OS X 10.5 and earlier, I posted some categories on NSString in this answer.

Objective-C String-Replace

I want to replace multiple elements in my string in Objective-C.
In PHP you can do this:
str_replace(array("itemtoreplace", "anotheritemtoreplace", "yetanotheritemtoreplace"), "replacedValue", $string);
However in objective-c the only method I know is NSString replaceOccurancesOfString. Is there any efficient way to replace multiple strings?
This is my current solution (very inefficient and.. well... long)
NSString *newTitle = [[[itemTitleField.text stringByReplacingOccurrencesOfString:#"'" withString:#""] stringByReplacingOccurrencesOfString:#" " withString:#"'"] stringByReplacingOccurrencesOfString:#"^" withString:#""];
See what I mean?
Thanks,
Christian Stewart
If this is something you're regularly going to do in this program or another program, maybe make a method or conditional loop to pass the original string, and multi-dimensional array to hold the strings to find / replace. Probably not the most efficient, but something like this:
// Original String
NSString *originalString = #"My^ mother^ told me not to go' outside' to' play today. Why did I not listen to her?";
// Method Start
// MutableArray of String-pairs Arrays
NSMutableArray *arrayOfStringsToReplace = [NSMutableArray arrayWithObjects:
[NSArray arrayWithObjects:#"'",#"",nil],
[NSArray arrayWithObjects:#" ",#"'",nil],
[NSArray arrayWithObjects:#"^",#"",nil],
nil];
// For or while loop to Find and Replace strings
while ([arrayOfStringsToReplace count] >= 1) {
originalString = [originalString stringByReplacingOccurrencesOfString:[[arrayOfStringsToReplace objectAtIndex:0] objectAtIndex:0]
withString:[[arrayOfStringsToReplace objectAtIndex:0] objectAtIndex:1]];
[arrayOfStringsToReplace removeObjectAtIndex:0];
}
// Method End
Output:
2010-08-29 19:03:15.127 StackOverflow[1214:a0f] My'mother'told'me'not'to'go'outside'to'play'today.'Why'did'I'not'listen'to'her?
There is no more compact way to write this with the Cocoa frameworks. It may appear inefficient from a code standpoint, but in practice this sort of thing is probably not going to come up that often, and unless your input is extremely large and you're doing this incredibly frequently, you will not suffer for it. Consider writing these on three separate lines for readability versus chaining them like that.
You can always write your own function if you're doing something performance-critical that requires batch replace like this. It would even be a fun interview question. :)
Considered writing your own method? Tokenize the string and iterate through all of them replacing one by one, there really is no faster way than O(n) to replace words in a string.
Would be a single for loop at most.
Add the # to the start of the all the strings, as in
withString:#""
It's missing for a few.