Awesome
Valid Key-Path
The Problem
Keys and key-paths are quite common thing when it comes to OS X or iOS development. Many core frameworks uses or even relies on features like Key-Value Coding or Key-Value Observing (you know, Core Data or Bindings).
Example of key @"view"
and key-path @"view.superview.backgroundColor"
.
We know, that KVC and KVO are good and realiable features, but why a lot of programmers is trying to avoid using them massively? We know this too. Because of keys / key-paths. They are simple strings, that can handle anything and are source of troubles when it comes to refactoring. Also, since there is only runtime validiation of these keys, one typo can crash whole application:
This class is not key value coding-compliant for the key 'backgroudColor'.
I wanted to start heavily using KVO with Core Data, so I decided to find a way to bring symbol-like features to key-paths. Impossible?
The Solution
I created a set of macros, that allows you to specify key-paths using symbols – classes and selectors.
Variable keyPath
is now equal to @"view.backgroundColor"
, so you can pass it as an argument to any KVO or KVO method.
I must admit, that just after publishing this repo, I found much better and elegant solution. This libextobjc on GitHub. Just take two files: extobjc/EXTKeyPathCoding.h
and extobjc/metamacros.h
and you have the same feature in better syntax.
Major Advantages:
- Just a string – This is not a new way to use Key-Value Coding, but rather a new way to create strings containing key-paths. You can mix it with raw string notation (
@"key.path"
). - Code completion – When typing a selector name Xcode suggests you only selectors (keys) from class you specified.
(Well, it offers you all methods from that class, but this works only with methods taking no arguments = getters.) - Compile-time validation – If you use validating macros, Xcode will throw a compilation error once the given class does not declare given selector.
(You may also use non-validating macros to avoid this, but you may lose other advantages. Use them only if you don't know the class.) - Refactorable – Last, but not least major advantage. These keys are fully refactorable using Xcode built-in tool.
(This works only with validating macros. Non-validating macros will just show a warning during refactoring preview. Validating macros will also display refactoring warning, but you may absolutely ignore it – it will work.) - This is not enough? Check out implementation details below for what is happening under the hood.
Minor Disadvantages:
- Little more typing – Yes, in general you hit keyboard more times than with raw key-paths. But you will hit less while refactoring or fixing stupid typos.
- Import classes – Sometimes it happen, that you will need to import more classes only for the key-path validation. Terrible! Or you can just use non-validating macros instead.
- Longer compilation – Since macros contains some code. Project will take longer to compile – additional 0.5 second or so.
- Slower runtine – Additional code will in theory add some time. Content of macros are altered on RELEASE, so there is less of it.
- Does these really matters? Check out implementation details below for what is happening under the hood.
How To Use & Requirements
-
You need to be able to use blocks and use of ARC is encouraged.
-
Import the two source files located in directory
MTKValidKeyPath/
into precompiled header. -
Now you can use these macros:
MTK_KEY( KEY )
– simple non-validating symbol-to-string conversionMTK_VALID_KEY( CLASS , KEY )
– symbol-to-string conversion that validates key against given classMTK_BEGIN_KEY
– macro that createsNSMutableString
that can be used for dot-chainingMTK_APPEND_KEY( KEY )
– used for dot-chaining key-paths, non-validatingMTK_APPEND_VALID_KEY( CLASS , KEY )
– also for dot-chaining key-paths, but with validation
-
Create project-specific aliases for these macros in some global file, so you don't have to type whole names. See
example.m
:
#define KEY MTK_BEGIN_KEY
#define __ MTK_APPEND_VALID_KEY
Then use it like this KEY.__(MyVideo, metadata).__(MyMetadata, title)
and the resulting string is @"metadata.title"
.
How Does It Work
Symbol-To-String Conversion
Macro for converting method name to NSString
uses NSStringFromSelector
function and @selector
directive.
#define MTK_KEY(__KEY__) (NSStringFromSelector(@selector(__KEY__)))
MTK_KEY(title) >>>>> NSStringFromSelector(@selector(title))
Key Validation
Macro for validating given key against a class contains a chunk of code. Main part is while
loop, that is breaked immediately, so the code is not actually executed in runtime. Inside it calls class
method on given class and then given selector on instance of this class. This provides refactoring and compile-time validation. Finally return string created by macro above.
#define MTK_VALID_KEY(__CLASS__, __KEY__) \
({ \
while (1) { \
break; \
[__CLASS__ class]; \
__CLASS__ * instance = nil; \
[instance __KEY__]; \
} \
MTK_KEY(__KEY__); \
})
Key-Path Creation
Simple contructor of NSMutableString
that also cast the resulting object.
#define MTK_BEGIN_KEY ((NSMutableString *)[NSMutableString string])
Key-Path Chaining
Key-path chaining uses dot syntax and blocks. You call method returning block and immediately execute the block with argument in parenthesis. This instance method is added to NSMutableString
class in category. Block appends given argument to the receiver and returns it, so you can continue chaining.
- (NSMutableString * (^)(NSString *))mtk_blockAppendingString;
mutableString.mtk_blockAppendingString(@"part1").mtk_blockAppendingString(@"part2");
That awkward moment, when README is longer than the source code.