Reputation: 32071
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
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
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
Reputation: 7673
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];
(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
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
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