I have a parent class
@interface Parent : NSObject
@end
And a child class
@interface Child : Parent
@end
In general, we can store child object in parent pointer like this
Parent *p = [Child alloc]init];
I was surprised to know that when I store parent object in child pointer like this
Child *c = [[Parent alloc] init];
Although compiler gives warning "Semantic error incompatible pointers" but when I run it works like first case. I am unable to understand why runtime is allowing this to work?
You don't say why you are surprised by the behaviour, but one possibility is the difference between static and dynamic method lookup and typing.
First note that with inheritance wherever you have a Parent reference you may actually have a Child one - the latter can do everything the former can and may be substituted for one.
Now in many object-oriented languages, for example C++, variable typing and method lookup is based on the static (i.e. declared) types. So for example in C++ your variable c is assumed to be an instance of Child and when a method is called on c the C++ compiler determines what method to call based on this alone. Therefore assigning an instance of Parent is unsafe - such an instance does not have the methods of Child and disaster will surely result. You can make the assignment safe in C++ using a cast:
Parent *p;
Child *c;
...
c = (Child *)p;
The cast will check at runtime whether the object referenced by p is a Child (or any class which inherits from Child etc.) and produce an error if not. As this is done at runtime it is usually wrapped within a conditional which first checks whether p is a Child.
However in Obj-C method lookup is done dynamically at runtime based on the actual type of a referenced object and not on the type of the variable which references it. As a result of this if your c variable contains a reference to a Parent and you try to invoke a Child method you will get a runtime error (which will be a clean one, in the C++ case your code will probably just malfunction and/or blowup).
This dynamic nature of Obj-C makes it easy to miss a lot of programming errors, with them only becoming apparent when the code is run - and as it is very hard to test an application completely, after the code is shipped to customers. Therefore the Obj-C compiler does as much type checking as it can to help minimise the number of errors left to appear at runtime. However, again because of the dynamic nature, it will sometimes only warn rather than report an error and refuse to compile - as it did in your example.
The solution in Obj-C is the same as for C++ above - use a cast to state the object must be a certain type and protect it with a conditional:
Parent *p;
Child *c;
...
if([p isKindOfClass:[Child class]) // we need a Child
{
c = (Child *)p;
...
}
else
{ // handle p not being a Child
...
}
The above intentionally does not discuss the relative advantages and disadvantages of dynamic vs. static , you'll do better reading that from a good book.
The reason for this is pretty simple. Child objects are guaranteed to implement all the methods implemented by Parent, because Child is a subclass of Parent. So, there's no concern that calling a particular Parent method on an object of type Child will cause an exception. However, Parent objects do not necessarily implement all the methods implemented by Child. So later method calls (message sends to) c might cause an exception. The compiler doesn't know that c is not a Child object, since that's its declared type. Essentially, this is the once place where the compiler has a chance to warn you that what you're doing might be wrong.
The runtime is a different matter entirely. At runtime, object pointers are the same. So, there's no reason you can't store a (reference to a) Parent object in a pointer declared in the source code as type Child*. You usually shouldn't do that, for the reasons mentioned above. As long as you only call method defined by Parent, it won't cause a problem, but if you try to call a Child-specific method, you'll get a runtime exception.
In short, let the compiler help you catch errors by typing your variables as strictly as you can. It's fine to store a Child object in a variable typed Parent if you only intend to use its functionality defined by Parent. If you truly need an generic/untyped object variable, you can use id. In that case, you can bracket method calls with if ([object resondsToSelector:@selector(theMethodName:)]) if you're not sure whether the object in question implements a given method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With