How to use #encode() to get #"NSArray" in Objective-C - objective-c

I'm using the runtime functions to get the type of a property (thanks to eJames for helping me to figure out this way).
The attribute string of the property looks like this:
T#"NSArray",&,Vstuff
I need to check if the property type is an array, at the moment I'm doing it like this:
- (BOOL)valueForKeyIsArray:(NSString *)key fromTagret:(id)target
{
NSString *lowerCaseKey = [self convertToKVCKey:key];
objc_property_t property = class_getProperty([target class], [lowerCaseKey UTF8String]);
NSString *propertyAttrs = [NSString stringWithUTF8String:property_getAttributes(property)];
NSString *encodedType = #"#\"NSArray\"";
NSRange range = [propertyAttrs rangeOfString:encodedType options:NSLiteralSearch];
return range.location != NSNotFound;
}
But since Apple can change the type definition string at any time, I would like to generate this #"NSArray" type string. I tried it with #encode(), but it did not work:
NSString *encodedType = [NSString stringWithUTF8String:#encode(NSArray *)];
So how can I generate this type string? Or is there a better way to check if this property attributes contain the array type?

There is no way to check this. In Objective-C source code the variables being typed as NSArray * is only there for the compiler to issue warnings. It has no meaning, and does not exist at runtime. If you mis-typed an NSArray as an NSString, you would get lots of warnings when compiling, but your code would behave exactly the same when run. At runtime all that is known is that the ivar/property is "an object".
Another way to think of it, is that once Objective-C is compiled, all object references are id references.

Just accept that if the runtime changes, your code will break, and move on. However, I think you might be miscategorizing ivars of type NSMutableArray *, CFArrayRef, or CFMutableArrayRef. You also seem to be assuming all keys correspond directly to a declared property.
The cleanest solution might be to assert that the sample object being used for the test (the target) must have a non-nil value for that key, and just grab the value and test that [[target valueForKey:key] isKindOfClass:[NSArray class]].

Related

Bulding property paths programatically with another class's selectors. How does this work?

I have a method I that takes a variable number of #selector() values and returns a proper NSString I found on this site a while back. This is useful when building property paths for me because I don't like the idea of using strings for this and like the idea of the compiler being able to check the values the path is built from. The method is:
+(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:#"."];
}
This works beautifully but the question is: Why? If I have property on my current object named person and it has a name the path would be person.name obviously and the call to my method would look like:
+(NSSet *)keyPathsForValuesAffectingFoo
{
return [NSSet setWithObject:[self keyPathFromSelectors:#selector(person), #selector(name),nil]];
}
The name selector is (should not) technically visible in my current class, it's in the Person class to which I have a reference so how am I accessing the correct name SEL object?
Selectors are do not carry the guarantee that the method actually exists on the object in question. You can get compiler warnings that a selector name doesn't exist anywhere, but you won't get assurance that it does exist on the object it will be used on. For example, this compiles:
[person performSelector: #selector(applicationDidBecomeActive:)]
Even though applicationDidBecomeActive exists on your app delegate and not person.
However! This kind of thing is possible through compiler macros. Look at the libextobjc library which provides exactly the kind of selector/key path compile time checking you're looking for. This is what powers a lot of the keypath magic in ReactiveCocoa but it can also be used standalone.

Objective-C: Is there any trick to avoid casting of NSArray objects?

Very often I find myself casting objects in NSArray to my types, when I want to access their specific properties with dot notation (instead of getter) without creating an extra variable.
Is there any cool feature or trick to tell objective-c which one class of objects I'm going to store to NSArray, so that compiler will assume objects in an array to be my type, not an id?
If you mean you're doing things like:
x = ((MyClass *)[myArray objectAtIndex:2]).property1;
You can just split it into two lines to be easier to read:
MyClass *myObject = [myArray objectAtIndex:2]
x = myObject.property1;
If you're really set on the first case, you could make a category on NSArray that has an accessor for your type:
#implementation NSArray (MyCategory)
- (MyClass *)myClassObjectAtIndex:(NSUInteger)index
{
return [self objectAtIndex:index];
}
#end
And then you can use it like you want:
x = [myArray myClassObjectAtIndex:2].property1;
Don't use properties in this situation. You can't say
arr[ix].myProperty
But you can always say
[arr[ix] myProperty]
Strictly answering to your question, no.
There's no language support for indicating the parametric type of a collection, i.e. something like NSArray<MyClass>.
That said, you can find workarounds for avoiding an explicit cast.
Since the returned object is of type id you can invoke any - existing - method on it and the compiler won't raise an eyebrow, unless you're using dot-syntax notation, which has stricter compiler checks.
So for instance
NSString * name = [people[0] firstName];
works flawlessly without a cast, whereas
NSString * name = people[0].firstName;
doesn't.

Getting the class type for a nil object?

If I have an object that is already allocated, then doing object.class returns a non-nil value. So far so good. But, if the object has not yet been allocated, then accessing object.class returns nil.
I want to allocate an object based on its type dynamically, so for example:
#property NSArray *myArray;
...
// myArray is nil so far
self.myArray = [_myArray.class new];
However, I can't do this because _myArray.class is returning nil. So how would I determine the class type of a nil instance?
Update:
It is in fact possible. Check out my answer below.
You cannot determine the class of a nil instance, because it does not have one: it can be, quite literally, of any type derived from the type of the variable. For example, NSMutableArray is perfectly compatible with NSArray:
NSArray *myArray = [NSArray new]; // OK
NSArray *myArray = [NSMutableArray new]; // Also OK
Since the run-time capabilities of different subclasses can vary a lot, it is always up to your program to decide what kind of objects it wants.
Objective-C is a duck-typed language. This means that there are several things you can or can't do, and one of the things you can't is statically get a reference to the type of a variable.
Specifically, in your expression:
[_myArray.class new]
First, _myArray.class is evaluated, and then the result is sent the new message. Since _myArray is nil to begin with, _myArray.class returns nil as well, and the new message will return nil too, because sending any message to nil returns nil (or the closest representation to zero the return type has). This is why it doesn't work.
I suspect you come from a strongly-typed language like C#; what you're doing right now is the equivalent of Foo foo = (Foo)Activator.CreateInstance(foo.GetType()), which is sure to fail because foo.GetType() will either not compile or throw an exception (depending on if it's a class field or a local variable) since it was never assigned a value. In Objective-C, it compiles but it doesn't works. What you would want is Activator.CreateInstance(typeof(Foo)), but notice that Foo is now hardcoded here too, so you might as well just create a new Foo().
You say that the compiler "knows the type" of the object. This is not exactly true. First, NSArray and NSMutableArray are the root classes of the NSArray class cluster. This means that both are abstract, and [NSArray alloc] and [NSMutableArray alloc] return an instance of a subclass (NSCFArray last time I checked, and possibly something else; I recall seeing _NSArrayM). Maybe [NSArray new] works, but it's not giving you a plain NSArray.
Second, type safety is not enforced. Consider this code:
id foo = #"foo";
NSArray* bar = foo; // no warning!
So even though the compiler thinks that bar is an NSArray, it's in fact a NSString. If we plug in your code:
id foo = #"foo";
NSArray* bar = foo; // no warning!
NSArray* baz = [bar.class new];
baz is now an NSString as well. Since you ask for the runtime class of bar, the compiler has nothing to do with the operations.
And precisely because of that kind of behavior, you should probably instantiate your object with a class that you know, using [NSArray new] instead of trusting _myArray to be non-nil, and to be what you think it is.
You must init the property , or it will be nil , send a message to a nil object , it will return nil , so ,you must first init the array like _array = [[NSArray alloc] init];
So, for anyone wondering if this is possible, it is:
objc_property_t property = class_getProperty(self.class, "myArray");
const char * const attrString = property_getAttributes(property);
const char *typeString = attrString + 1;
const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL);
const char *className = typeString + 2;
next = strchr(className, '"');
size_t classNameLength = next - className;
char trimmedName[classNameLength + 1];
strncpy(trimmedName, className, classNameLength);
trimmedName[classNameLength] = '\0';
Class objectClass = objc_getClass(trimmedName);
NSLog(#"%#", objectClass);
Output:
NSArray
Done with the help of extobjc.
Nil has no class type
In Objective-C the actual class on an instance variable is only determined at runtime. So, you can't know the class of a nil object.
This is not an issue in your situation since you only need to do:
NSArray *myArray = [NSArray new];
Or
NSArray *myArray = [[NSArray alloc] init];
In Objective-C most decisions are deferred to the runtime
(as much as possible)
Objective-C is a runtime oriented language, which means that when it's
possible it defers decisions about what will actually be executed from
compile & link time to when it's actually executing on the runtime.
This gives you a lot of flexibility in that you can redirect messages
to appropriate objects as you need to or you can even intentionally
swap method implementations, etc.
This requires the use of a runtime
which can introspect objects to see what they do & don't respond to
and dispatch methods appropriately. If we contrast this to a language
like C. In C you start out with a main() method and then from there
it's pretty much a top down design of following your logic and
executing functions as you've written your code. A C struct can't
forward requests to perform a function onto other targets.
Source: Understanding the Objective-C Runtime

Why set types in Obj-c fast enumeration loops?

NSMutableArray *array = [[NSMutableArray alloc] init];
NSString *string = #"string";
[array addObject:string];
NSDate *date = [[NSDate alloc] init];
[array addObject:date];
for (*placeholder* stuff in array)
NSLog(#"one");
If I change placeholder to either NSString* or NSDate*, I expect to see "one", because the for loop should just ignore a non-matching type. However, the result is "one one".
Doesn't this imply that you should just have placeholder be id whatever the situation, since it doesn't seem to matter anyhow?
fast enumeration always iterates over all object in a collection. it does not filter.
The only thing that happens is, that you will have some strange casts.
if your array contains objects of differnt classes, you can determine the class for each object with isMemberOfClass:
if you would do for (NSDate *obj in array), any object in the array will be casts to NSDate, no matter if that is sense-full or not. and due to the nature of objective-c it will even work, as-long as you dont send a message that is only understandable by NSDate objects or send the object as an argument to a method that needs to receive a date object, as a cast does not change the object in anyway. A cast is just a promise you make to the compiler that you know what you are doing. Actually you also can call it a lie.
To answer your question title itself: You dont have to set the class inside the loop statement. the generic object type id is sufficient. But usually you have objects of one kind in an array — views, numbers, string, dates,…. by declaring the right class you gain some comfort like better autocompletion.
Yes, using id (or some other common ancestor class) is the correct approach, and then it's necessary to determine which type of class has been enumerated in order to handle it differently:
for (id obj in array)
{
if ([obj isMemberOfClass:[NSString class]])
{
NSString *str = (NSString *)obj;
NSLog("obj is a string: %#", str);
}
else if ([obj isMemberOfClass:[NSDate class]])
{
NSDate *date = (NSDate *)obj;
NSLog("obj is a date: %#", date);
}
}
The problem has nothing to do with fast enumeration, but with collections which can contain any type of object. The same question arises when you access an individual element of an array:
id lastObject = [array lastObject];
or
NSString *string = [array lastObject];
Which will you chose? It all depends on your code. If you're sure that array only contains strings, then in my opinion it is better to use the second choice, because you get additional type checking, autocompletion, and method matching from the compiler (i.e. you won't get warnings if you call a method that has different signatures for two different objects). The same applies to fast enumeration: if your collection can contain any kind of object, use id. If you know what it contains, use the specific type. (And the same also applies to block tests. In NSArray's method
- (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
if you know it only contains strings for instance, you can replace id with NSString * in the block arguments. It won't change at all the compiled code or the behavior of your application, it will only change the compiler type checking.

NSMutableArray from filterUsingPredicate error

I am trying to return a subset of my NSMutableArray (MessageArray) with the following code. MessageArray contains an NSDictionary, one of the keys being FriendStatus. I get a strange error which I know is a DUH syntax issue. "error. void value not ignored as it ought to be".
-(NSMutableArray*)FriendMessageArray {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"FriendStatus == 1"];
NSMutableArray *filtered = [MessageArray filterUsingPredicate:predicate];
return filtered;
}
"void value not ignored as it ought to be" means that a method with a signature that starts with (void) is being used to assign a value or object to a variable. What's the signature for filterUsingPredicate? does it start with (void) ?
I'm assuming "MessageArray" is an instance variable (never name instance variables this way; you should have a property called -messages and access it with self.messages). I'l further assume that it is an NSMutableArray or else you'd be getting warnings from the compiler.
NSMutableArray -filterUsingPredicate: modifies the array itself, returning void. The method you want is -filteredArrayUsingPredicate: which returns an array. The fact that the former is a verb and the latter is a noun indicates this fact even without reading the docs. Cocoa naming is extremely consistent, which is why I mention it in the first paragraph. Pay attention to the names and you will have far fewer bugs.