Based on a python name generator grammar I found here I've decided to write my own in objective-C. The idea is I can load the grammar from a plist file and generate random names from it.
I'm looking for a general code review and any comments and or suggestions.
NameGenerator.h:
@interface NameGenerator : NSObject
- (id)initWithGrammar:(NSString *)plistName;
- (NSString*)name;
@end
NameGenerator.m:
@interface NameGenerator()
@property (nonatomic,strong) NSDictionary *grammar;
-(NSString*)replaceKey:(NSString*)input;
@end
@implementation NameGenerator
@synthesize grammar = _grammar;
- (id)init {
@throw [NSException exceptionWithName: @"NameGeneratorInit"
reason: @"-init is not allowed, use -initWithGrammar: instead"
userInfo: nil];
}
- (id)initWithGrammar:(NSString *)plistName
{
self = [super init];
if (self) {
NSString* plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:@"plist"];
self.grammar = [NSDictionary dictionaryWithContentsOfFile:plistPath];
id object = [self.grammar objectForKey:@"grammar"];
if (![object isKindOfClass:[NSString class]]) {
NSLog(@"initWithGrammar: Not a grammar plist");
self = nil;
}
NSString *ver = object;
if (![ver isEqualToString:@"NG.CF.1"]) {
NSLog(@"initWithGrammar: plist version is wrong");
self = nil;
}
}
return self;
}
- (NSString*)name {
return [self replaceKey:@"name"];
}
-(NSString*)replaceKey:(NSString*)input
{
NSString *output = @"";
NSArray *parts = [input componentsSeparatedByString:@","];
if ([parts count] > 1) {
for(NSString *part in parts) {
output = [output stringByAppendingString:[self replaceKey:part]];
}
} else {
id object = [self.grammar objectForKey:input];
if ([object isKindOfClass:[NSString class]]) {
output = [self replaceKey:object];
}
else if ([object isKindOfClass:[NSArray class]]) {
NSUInteger randomIndex = arc4random() % [object count];
output = [self replaceKey:[object objectAtIndex:randomIndex]];
}
else {
output = input;
}
}
return output;
}
ork.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>grammar</key>
<string>NG.CF.1</string>
<key>name</key>
<string>nameStart,nameMiddle0to3,nameEnd</string>
<key>nameMiddle0to3</key>
<array>
<string></string>
<string>nameMiddle</string>
<string>nameMiddle,nameMiddle</string>
<string>nameMiddle,nameMiddle,nameMiddle</string>
</array>
<key>nameStart</key>
<array>
<string>nsCons,tick,nsCons,nmVowel</string>
<string>nsCons,nmVowel</string>
<string>nsCons,nmVowel</string>
<string>nsCons,nmVowel</string>
<string>nsVowel</string>
</array>
<key>nameMiddle</key>
<string>nmCons,nmVowel</string>
<key>nameEnd</key>
<array>
<string>neCons,neVowel</string>
<string>neCons</string>
<string>neCons</string>
</array>
<key>nsCons</key>
<array>
<string>d</string>
<string>g</string>
<string>k</string>
<string>t</string>
<string>gr</string>
</array>
<key>nmCons</key>
<array>
<string>d</string>
<string>g</string>
<string>k</string>
<string>t</string>
<string>r</string>
<string>s</string>
<string>z</string>
<string>kt</string>
<string>rs</string>
<string>gr</string>
</array>
<key>neCons</key>
<array>
<string>r</string>
<string>s</string>
<string>z</string>
</array>
<key>nsVowel</key>
<array>
<string>e</string>
<string>u</string>
</array>
<key>nmVowel</key>
<array>
<string>a</string>
<string>e</string>
<string>i</string>
<string>o</string>
<string>u</string>
</array>
<key>neVowel</key>
<array>
<string>a</string>
<string>u</string>
</array>
<key>tick</key>
<array>
<string>'</string>
</array>
</dict>
</plist>
1 Answer 1
It generally looks like good code to me, so these come under "nits":
In
-initWithGrammar:
you're actually passing the name of the grammar, not the grammar itself. Perhaps-initWithGrammarName:
would be better.It's conventional not to use accessors in initialisers or destructors, because the object isn't in a consistent state during these methods. This means replacing
self.grammar =
with_grammar =
in the initialiser.Given the change made above, why does the
grammar
property need to be readwrite? Customers of your class pass a filename into the initialiser and then, presumably, never change the constructed dictionary so it can bereadonly
.in
replaceKey:
, if the plist happens to contain a number or a dictionary value for the key then you'll return that instead of anNSString
. I realise the grammar format isn't supposed to do that but it's good form to assert this so that when it does happen, people find out the easy way.