Obj-C literals make your code cleaner and more compact, but hand-updating a
large codebase to take advantage of Obj-C literals would be a bore, and all too
easy to mess up during a distracted moment.
This is what automated refactoring tools were designed for. And Apple has
provided us with an oft-overlooked arrow in our devtools quiver that’s just
what we need here: tops.
Check out man tops . The tool has a decent understanding of Obj-C
syntax and accepts scripts that let you rewrite code to use new method calls,
new functions, and what-have-you. The examples make it look like this tool was
invented to ease the transition from NeXT-style Obj-C to Cocoa, like this gem:
replace "NXGetNamedObject(<b args>)" with same
error "ApplicationConversion: NXGetNamedObject() is obsolete.
Replace with nib file outlets."
That should take some of you way back.
Anyway, with this tool, modernizing your code can be as simple as:
tops -semiverbose -scriptfile literals.tops **/*.(h|m|hpp|mm)
Want to check that it will do the right thing? Throw -dont into the args.
Want to watch over its shoulders as it rewrites your code? Replace
-semiverbose with straight-up -verbose.
Now, for that magical script file:
/*tops -semiverbose -scriptfile literals.tops Project/**/*.{h,m,hh,mm}*/
/* @file literals.tops
* @author Jeremy W. Sherman
* @url http://jeremywsherman.com/
* @date 2012-08-27 */
/* Dictionary Creation
* Literals create immutable dicts, so only replace NSDictionary not
* NSMutableDictionary. */
replace "[NSDictionary dictionary]" with "@{}"
replace "[[NSDictionary alloc] init]" with "@{}"
replace "[NSDictionary new]" with "@{}"
replace "[NSDictionary dictionaryWithObjectsAndKeys:<a args>]"
with "@{<args>}"
within ("<args>") {
replace ", nil" with ""
replace ", (<b cast>)nil" with ""
replace "<val>, <key>" with "<key> : <val>!!!"
replace "!!!" with ""
}
replace "[[NSDictionary alloc] initWithObjectsAndKeys:<a args>]"
with "@{<args>}"
within ("<args>") {
replace ", nil" with ""
replace ", (<b cast>)nil" with ""
replace "<val>, <key>" with "<key> : <val>!!!"
replace "!!!" with ""
}
/* Dictionary Access */
/* We distinguish between simple tokens and entire expressions, and wrap
* expressions with parens in the substitution.
* We do the same for arrays later. */
replace "[<t dict> objectForKey:<key>]" with "<dict>[<key>]"
replace "[<expr> objectForKey:<key>]" with "(<expr>)[<key>]"
/* Dictionary Mutation */
replace "[<t dict> setObject:<obj> forKey:<key>]" with "<dict>[<key>] = <obj>"
replace "[<expr> setObject:<obj> forKey:<key>]" with "(<expr>)[<key>] = <obj>"
/* Array Creation */
replace "[NSArray array]" with "@()"
replace "[[NSArray alloc] init]" with "@()"
replace "[NSArray new]" with "@()"
replace "[[NSArray alloc] initWithObjects:<a objs>]" with "@(<objs>)"
within ("<objs>") {
replace ", nil" with ""
replace ", (<b cast>)nil" with ""
}
replace "[NSArray arrayWithObjects:<a objs>]" with "@(<objs>)"
within ("<objs>") {
replace ", nil" with ""
replace ", (<b cast>)nil" with ""
}
/* Array Access */
replace "[<t arr> objectAtIndex:<i>]" with "<arr>[<i>]"
replace "[<expr> objectAtIndex:<i>]" with "(<expr>)[<i>]"
/* Array Mutation */
replace "[<t arr> replaceObjectAtIndex:<i> withObject:<obj>]"
with "<arr>[<i>] = <obj>"
replace "[<expr> replaceObjectAtIndex:<i> withObject:<obj>]"
with "(expr)[<i>] = <obj>"
/* Number Literals */
/* Not handled, because Apple releases don't yet support boxed expressions,
* many numbers are likely to be created with boxed expressions, and tops
* can't distinguish type well enough to restrict transformations to
* those involving only numeric literals. */
And here’s an Obj-C file to test it against:
// clang -g -Weverything -framework Foundation literals.m -o literals
/* @file literals.m
* @author Jeremy W. Sherman
* @url http://jeremywsherman.com/
* @date 2012-08-27 */
#import <Foundation/Foundation.h>
int
main(void)
{
@autoreleasepool {
NSString *string = @"string";
/* Array Literals */
NSArray *array = [[NSArray alloc] init];
array = [NSArray array];
array = [[NSArray alloc] initWithObjects:@"0", @"1", @"2", (void *)nil];
array = [NSArray arrayWithObjects:@"0", @"1", @"2", nil];
/* Don't rewrite mutable creation, since container literals
* create immutable containers. */
NSMutableArray *marray = [NSMutableArray arrayWithObjects:@"NO", nil];
[marray replaceObjectAtIndex:0 withObject:string];
string = [array objectAtIndex:0];
array = [[NSMutableArray alloc] initWithObjects:
@"1", @"2", @"3", nil];
/* Dictionary Literals */
NSDictionary *dict = [[NSDictionary alloc] init];
dict = [NSDictionary dictionary];
dict = [[NSDictionary alloc]
initWithObjectsAndKeys:@"obj", @"key", @"v", @"k", (void *)nil];
dict = [NSDictionary dictionaryWithObjectsAndKeys:
@"bar", @"key", (void *)nil];
NSMutableDictionary *mdict = [[NSMutableDictionary alloc]
init];
[mdict setObject:@"foo" forKey:@"key"];
/* Trickier? */
NSArray *arrays[] = {array, array};
id obj = [*arrays objectAtIndex:0];
obj = [*(arrays + 1) objectAtIndex:0];
NSLog(@"%@", obj);
}
}