Best way to avoid hardcoded strings in nested KVO keypaths - objective-c

When registering to observe an object via KVO I write this code to avoid hardcoded strings:
[myObject addObserver:self
forKeyPath:NSStringFromSelector(#selector(myProperty))
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:NULL];
Note the NSStringFromSelector. It protects me at compile-time in the event that I change the name of myProperty and forget I had something observing it.
But the situation is more complicated when using nested properties, e.g., "myProperty1.myProperty2"
My naive solution is to use macros like the following:
#define KEYPATHSTRING1(a) NSStringFromSelector(#selector(a))
#define KEYPATHSTRING2(a, b) [NSString stringWithFormat:#"%#.%#", NSStringFromSelector(#selector(a)), NSStringFromSelector(#selector(b))]
#define KEYPATHSTRING3(a, b, c) [NSString stringWithFormat:#"%#.%#.%#", NSStringFromSelector(#selector(a)), NSStringFromSelector(#selector(b)), NSStringFromSelector(#selector(c))]
Any better or standardized solutions out there? Searches on Google and SO turned up nothing for me that addressed this particular question.

I haven't tried this (or even compiling it), but you could look at using a variadic helper method:
+ (NSString *)keyPathFromSelectors:(SEL)firstArg, ...
{
NSMutableArray *keys = [NSMutableArray array];
va_list args;
va_start(args, firstArg);
for (SEL arg = firstArg; arg != nil; arg = va_arg(args, SEL))
{
[keys addObject:NSStringFromSelector(arg)];
}
va_end(args);
return [keys componentsJoinedByString:#"."];
}

I use EXTKeyPathCoding.h from libextobjc. You can check how they do same at https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTKeyPathCoding.h

Swift 3 adds #keyPath() to generate a key path string from a "property chain".
let keyPathString = #keyPath(MyType.myProperty1.myProperty2)
The tokens inside the function are not Strings and will be checked by the compiler.

Related

Objective-C dynamic properties at runtime?

Is it possible to create an Objective-C class that can have an arbitrary number of dynamic properties at runtime?
I want to be able to call mySpecialClass.anyProperty and intercept this inside my class to be able to provide my own custom implementation that can then return an NSString (for instance) at runtime with raising an exception. Obviously this all has to compile.
Ideal would be if I could refer to my properties using something similar to the new literal syntax, e.g. mySpecialClass["anyProperty"].
I guess in a way I want to create something like a dynamic NSDictionary with no CFDictionary backing store, that executes 2 custom methods on property getting and setting respectively, with the property name passed in to these accessor methods so they can decide what to do.
There are at least two ways to do this.
Subscripting
Use objectForKeyedSubscript: and setObject:forKeyedSubscript:
#property (nonatomic,strong) NSMutableDictionary *properties;
- (id)objectForKeyedSubscript:(id)key {
return [[self properties] valueForKey:[NSString stringWithFormat:#"%#",key]];
}
- (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)key {
[[self properties] setValue:object forKey:[NSString stringWithFormat:#"%#",key]];
}
Person *p = [Person new];
p[#"name"] = #"Jon";
NSLog(#"%#",p[#"name"]);
resolveInstanceMethod:
This is the objc_sendMsg executed by the runtime for all methods:
If you look at the bottom, you have the opportunity to resolveInstanceMethod:, which lets you redirect the method call to one of your choosing. To answer your question, you need to write a generic getter and setter that looks-up a value on a dictionary ivar:
// generic getter
static id propertyIMP(id self, SEL _cmd) {
return [[self properties] valueForKey:NSStringFromSelector(_cmd)];
}
// generic setter
static void setPropertyIMP(id self, SEL _cmd, id aValue) {
id value = [aValue copy];
NSMutableString *key = [NSStringFromSelector(_cmd) mutableCopy];
// delete "set" and ":" and lowercase first letter
[key deleteCharactersInRange:NSMakeRange(0, 3)];
[key deleteCharactersInRange:NSMakeRange([key length] - 1, 1)];
NSString *firstChar = [key substringToIndex:1];
[key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar lowercaseString]];
[[self properties] setValue:value forKey:key];
}
And then implement resolveInstanceMethod: to add the requested method to the class.
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if ([NSStringFromSelector(aSEL) hasPrefix:#"set"]) {
class_addMethod([self class], aSEL, (IMP)setPropertyIMP, "v#:#");
} else {
class_addMethod([self class], aSEL,(IMP)propertyIMP, "##:");
}
return YES;
}
You could also do it returning a NSMethodSignature for the method, which is then wrapped in a NSInvocation and passed to forwardInvocation:, but adding the method is faster.
Here is a gist that runs in CodeRunner. It doesn't handle myClass["anyProperty"] calls.
You're asking different things. If you want to be able to use the bracket syntax mySpecialClass[#"anyProperty"] on instances of your class, it is very easy. Just implement the methods:
- (id)objectForKeyedSubscript:(id)key
{
return ###something based on the key argument###
}
- (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)key
{
###set something with object based on key####
}
It will be called everytime you use the bracket syntax in your source code.
Otherwise if you want to create properties at runtime, there are different ways to proceed, take a look at NSObject's forwardInvocation: method, or look at the Objective-C Runtime Reference for functions to dynamically alter a class...
Guillaume is right. forwardInvocation: is the way to go. This answer gives some more details: method_missing-like functionality in objective-c (i.e. dynamic delegation at run time)
This has even more details: Equivalent of Ruby method_missing in Objective C / iOS
And these are some other lesser known Obj-C features that might help you: Hidden features of Objective-C
Enjoy!

Variable Argument List - Memory Management Error

I am trying to create a method to quickly and easily create an NSArray from a va_list, however, when I run the method, I receive an exc_bad_access due to some bad memory management somewhere, although I cannot determine where this place is.
Please could you take a look at the code and tell me where and why this is occurring.
Thanks in advanced,
Max.
NSArray *arrayCreate(id firstObject, ...) {
NSMutableArray *objects = [NSMutableArray array];
[objects addObject:firstObject];
va_list args;
va_start(args, firstObject);
id arg;
while ((arg = va_arg(args, id))) {
[objects addObject:arg];
}
va_end(args);
return [objects copy];
}
Usage (just to test that it's working):
NSLog(#"%#", arrayCreate(#"1", #"2", #"3", #"4"));
You forgot to nil-terminate your arglist. In C, functions have no way of knowing how many variadic arguments you passed, so it's common to end a series of pointers with a null pointer (to indicate no more valid input.) Your code appears to be checking for this (arg = va_arg(args, id) will be false when it reaches nil) but your input is missing it.

Fast Enumeration on NSArray of Different Types

I have this question here (as well other quesrtions on SO), and the Apple docs about Objective-C collections and fast enumeration. What is not made clear is if an NSArray populated with different types, and a loop is created like:
for ( NSString *string in myArray )
NSLog( #"%#\n", string );
What exactly happens here? Will the loop skip over anything that is not an NSString? For example, if (for the sake of argument) a UIView is in the array, what would happen when the loop encounters that item?
Why would you want to do that? I think that would cause buggy and unintended behavior. If your array is populated with different elements, use this instead:
for (id object in myArray) {
// Check what kind of class it is
if ([object isKindOfClass:[UIView class]]) {
// Do something
}
else {
// Handle accordingly
}
}
What you are doing in your example is effectively the same as,
for (id object in myArray) {
NSString *string = (NSString *)object;
NSLog(#"%#\n", string);
}
Just because you cast object as (NSString *) doesn't mean string will actually be pointing to an NSString object. Calling NSLog() in this way will call the - (NSString *)description method according to the NSObject protocol, which the class being referenced inside the array may or may not conform to. If it conforms, it will print that. Otherwise, it will crash.
You have to understand that a pointer in obj-c has no type information. Even if you write NSString*, it's only a compilation check. During runtime, everything is just an id.
Obj-c runtime never checks whether objects are of the given class. You can put NSNumbers into NSString pointers without problems. An error appears only when you try to call a method (send a message) which is not defined on the object.
How does fast enumeration work? It's exactly the same as:
for (NSUInteger i = 0; i < myArray.count; i++) {
NSString* string = [myArray objectAtIndex:i];
[...]
}
It's just faster because it operates on lower level.
I just tried a quick example... Here is my code.
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:1];
NSNumber *number = [NSNumber numberWithInteger:6];
[array addObject:number];
[array addObject:#"Second"];
Now if I simply log the object, no problem. The NSNumber instance is being cast as an NSString, but both methods respond to -description, so its not a problem.
for (NSString *string in array)
{
NSLog(#"%#", string);
}
However, if I attempt to log -length on NSString...
for (NSString *string in array)
{
NSLog(#"%i", string.length);
}
... it throws an NSInvalidArgumentException because NSNumber doesn't respond to the -length selector. Long story short, Objective-C gives you a lot of rope. Don't hang yourself with it.
Interesting question. The most generic syntax for fast enumeration is
for ( NSObject *obj in myArray )
NSLog( #"%#\n", obj );
I believe that by doing
for ( NSString *string in myArray )
NSLog( #"%#\n", string );
instead, you are simply casting each object as an NSString. That is, I believe the above is equivalent to
for ( NSObject *obj in myArray ) {
NSString *string = obj;
NSLog( #"%#\n", string );
}
I could not find precise mention of this in Apple's documentation for Fast Enumeration, but you can check it on an example and see what happens.
Since all NSObject's respond to isKindOfClass, you could still keep the casting to a minimum:
for(NSString *string in myArray) {
if (![string isKindOfClass:[NSString class]])
continue;
// proceed, knowing you have a valid NSString *
// ...
}

variadic methods: Check, if a argument is a typedef'ed block

I created a veriadic Category method on NSArray to filter it into a dictionary with given blocks and keys
typedef BOOL (^TestBlock)(id element);
- (NSDictionary *)dictionaryByFilteringWithBlocksAndKeys:(TestBlock)firstBlock, id firstKey,... NS_REQUIRES_NIL_TERMINATION;
Usage:
NSArray *array = [NSArray arrayWithObjects:#"a", #"aa", #"ab", #"cc", #"cd", #"dd", nil];
NSDictionary *dict = [array dictionaryByFilteringWithBlocksAndKeys:
^BOOL(id element) {return [element hasPrefix:#"a"];},#"a",
^BOOL(id element) {return [element hasPrefix:#"c"];},#"c",
nil];
It works quite good, but I wonder, how I can check, if a given block (other then the first) has the typedef'ed signature.
I get the blocks from the va_list with TestBlock block = va_arg(args, TestBlock);, but it also eats non-sense blocks like
NSDictionary *dict = [array dictionaryByFilteringWithBlocksAndKeys:
^BOOL(id element) {return [element hasPrefix:#"a"];}, #"a",
^(id element) {;}, #"c",
nil];
without complaining.
Is there a way to check, if a block has the same signature as a typedef at runtime?
No, there is no way to check. This would require new syntax in the compiler. Effectively, you want "these arguments are optional/variadic, but must follow this type pattern".
Also note that treating a specifically typed argument list as a va_list is not technically correct, either. It will mostly work by coincidence on most ABIs, but not all. (Not that you are doing that, but it often comes up in these contexts).
can you give me an example of what you mean with specially typed
argument list?
Sure, consider:
id objc_msgSend(id,SEL,...);
That is a var-args argument list. Now, you might think you can do this:
objc_msgSend(mutableArray, #selector(addObject:), anObject);
But, technically, you can't. varargs are not compatible with specifically typed argument lists, by the C spec. Thus, you need to typecast:
void (*func)(id,SEL,id) = (void*)objc_msgSend;
func(mutableArray, #selector(addObject:), anObject);
That is, if the called API is not also varargs, then the call site must be compiled against a declaration of the call that has full argument type info...

NSDictionary: URL-Encoding

Can anyone please review the following method and tell me if it is necessary to release the enumerator for the dictionary keys after using it?
I'm new to objective c and not always sure, when to release variables returned from the SDK methods. I suppose the keyEnumerator method returns an autoreleased instance so I don't have to care about it when I'm finished with it. Right?
Hmm.. maybe anything else wrong in this method? ;)
#import "NSDictionaryURLEncoding.h"
#import <Foundation/NSURL.h>
#implementation NSDictionary(URLEncoding)
-(NSString *)urlEncoded
{
NSEnumerator *keyEnum = [self keyEnumerator];
NSString *currKey;
NSString *currObject;
NSMutableString *result = [NSMutableString stringWithCapacity: 64];
while ((currKey = [keyEnum nextObject]) != nil)
{
if ([result length] > 0)
{
[result appendString: #"&"];
}
currObject = [[self objectForKey: currKey] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
currKey = [currKey stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[result appendString: [NSString stringWithFormat:#"%#=%#", currKey, currObject]];
}
return result;
}
#end
You must never release objects for which you did not call the alloc method if you also didn't retain them. See the Memory Management Programming Guide for Cocoa.
Also, if you're programming for the iPhone OS or if you are targetting Mac OS X 10.5 or later, I suggest you use the for(... in ...) language construct to enumerate through your dictionary. It's much faster than using enumerators.
for(NSString* currKey in self)
{
NSString* currObject = [self objectForKey:currKey];
// the rest of your loop code
}
You should read the Fast Enumeration section of the Objective-C Programming Language Reference.
I also suggest, as a friendly advice, that you don't extend NSDictionary or create a category on it unless you really, really need it.