Magic constants in the code - evil. String constants in the code - even more evil.
And, it seems, they won’t get anywhere, they are everywhere:
1) When loading objects from xibs:
MyView* view = [[[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil] lastObject];
MyViewController* controller = [MyViewController initWithNibName:@"MyViewController" bundle:nil];
2) When working with CoreData:
NSFetchRequest *request = [[NSFetchRequest alloc] init]; [request setEntity:[NSEntityDescription entityForName:@"MyCoreDataClass" inManagedObjectContext:moc]]; [request setSortDescriptors:@[ [[NSSortDescriptor alloc] initWithKey:@"someProperty" ascending:NO] ]];
3) If you use KVO, then the lines appear here:
[self addObserver:someObservedObject forKeyPath:@"someProperty" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
4) Well, KVC:
NSInteger maxValue = [[arrayOfMyClassObjects valueForKeyPath:@"@max.someProperty"] intValue];
5) But even if CoreData you prefer working with SQLite directly, xib-you squeamish, then this code should be familiar to you:
[self.tableView dequeueReusableCellWithIdentifier:@"MyTableViewCell"];
6) Well, when Apple introduced the Storyboard to the world - it would be great if it were not for one thing:
[self performSegueWithIdentifier:@"MySegue" sender:nil]
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:( id )sender { if ( [segue.identifier isEqual:@"MySegue"] ); }
Do you see the problem? It consists in the fact that the compiler does not check the contents of the strings in any way, because it does not know (and cannot know in principle) what they contain. And if you seal or change the value of the corresponding fields in xcdatamodel / xib / storyboard / rename property, then the error will not come out at the compilation stage, but in runtime, and it will be longer and more expensive to catch and fix it.
So what can be done?
With some lines you can handle administrative measures, with some - with the help of special tools.
Loading from xibs
For example, if you start with the rule that the name of the xib should match the name of the class it contains, then the code from the first example can be rewritten as:
MyView* view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([MyView class]) owner:self options:nil] lastObject];
MyViewController* controller = [MyViewController initWithNibName:NSStringFromClass([MyViewController class] bundle:nil];
The advantage of this solution is that if we decide to rename our class (Xcode -> Edit -> Refactor -> Rename and leave the "Rename related files" checkbox selected), then renaming xib will also be renamed, and, accordingly, loading view / controller will not suffer.
')
Coredata
With example 2, the solution is a bit more complicated and complex.
First we need
MagicalRecord and
mogeneratorIf you are working with CoreData and are still not using these great tools, then it's time to start.
Add MagicalRecord to our podfile (or, in the old manner, by copying files into the project - for details in the GiHab)
pod MagicalRecord
And install the mogenerator:
brew install mogenerator
Or download the installer from the site and install it manually.
mogenerator creates source files based on the CoreData model file that you would otherwise have to write with your hands.
Now we need to configure the project to run the utility with each build.
Project -> Targets -> Add Build Phase -> Add Run Script:
Go to the settings of the target tab Build Phases and add a new phase in the form of a script:

Well, the script itself:

On the same level with our model, you need to create two folders, Human and Machine. We press CMD + B - after which we add these two folders to the project. They will contain generated model files from CoreData.
KVO and KVC
One more thing in Objective-C that actively uses string constants is KeyPath for KVO and KVC. You can use the above mogenerator. If we have the class MyCoreDataClass in the model, then mogenerator will create the structures MyCoreDataClassAttributes, MyCoreDataClassRelationships and MyCoreDataClassFetchedProperties. So now you can rewrite example number 2 as follows:
NSArray* arr = [MyCoreDataClass MR_findAllSortedBy:MyCoreDataClassAttributes.someProperty ascending:NO inContext:moc];
And example number 3 as follows:
[self addObserver:myCoreDataClass forKeyPath:MyCoreDataClassAttributes.someProperty options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
But such a solution is suitable only for CoreData. We need something more general.
The
Valid-KeyPath library is quite suitable for this:
#import "EXTKeyPathCoding.h" [self addObserver:myClass forKeyPath:KEY.__(MyClass, someProperty) options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
Or
EXTKeyPathCoding libextobjc library libraries :
pod libextobjc
#import "MTKValidKeyPath.h" [self addObserver:myClass forKeyPath:@keypath(MyClass.new, someProperty) options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
The advantages of both solutions will be autocompletion from the IDE while writing the code itself, as well as checking KeyPath itself at the compilation stage.
If you use KVC, as in example 4, then to generate KeyPath you can use any of the libraries presented above, or you can use any of the LINQ-like libraries for Objective-C, for example
LinqToObjectiveC pod LinqToObjectiveC
NSInteger maxValue = [arrayOfMyClassObjects aggregate:^(MyClass* myClass, NSInteger aggregate){ return MAX( [myClass.someProperty intValue], aggregate); }];
Storyboard
As for the UI part, then the
ssgenerator utility will help us
. Download it from here.Create another script for it in the project:

After that we add the generated StoryboardSegue.h and StoryboardSegue.m to the project. These files contain categories for those controllers in the storyboard that contain UIStoryboardSegue or UITableViewCell for which identifiers are defined. Now you can use:
[self.tableView dequeueReusableCellWithIdentifier:self.cell.MyTableViewCell];
[self performSegueWithIdentifier:self.segue.MySegue sender:nil]
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:( id )sender { if ( [segue.identifier isEqual:self.segue.MySegue] ); }
Conclusion
Getting rid of string constants in code is not an end in itself, but a way to significantly save on writing and maintaining code through checks during compilation. Some of the methods described require third-party utilities, some - third-party libraries. For some programmers, the described techniques will seem “complicated” and “poorly readable,” but all of them are aimed at detecting errors in the code as early as possible. So if you write code that does not change, code that does not contain errors, these methods are not for you.
If you have in mind utilities and libraries that help you personally get rid of the constants in the code and make it more flexible and supported - write in the comments, I’ll be happy to add them to this review.