Snowman
Snowman

Reputation: 32071

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.

Upvotes: 2

Views: 898

Answers (5)

Snowman
Snowman

Reputation: 32071

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.

Upvotes: 1

zneak
zneak

Reputation: 138071

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.

Upvotes: 3

Jean
Jean

Reputation: 7673

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

Upvotes: -1

Guo Luchuan
Guo Luchuan

Reputation: 4731

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];

Upvotes: 1

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726639

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.

Upvotes: 4

Related Questions