Dynamic and Static Typing in Objective-C
Polymorphism
Polymorphism allows different classes to define methods with the same name.
Dynamic Typing
Dynamic typing means that the class of an object is determined at runtime, not at compile time.
Static Typing
Static typing involves defining a variable as an instance of a specific class. The compiler knows the variable's class at compile time, and it always stores objects of that class. Static typing enables the compiler to ensure consistent usage, verify that methods are defined or inherited by the class, and issue warnings otherwise. This helps catch errors early during compilation and improves code readability.
Example of Static Typing
Person *personInstance = [Person new];
In this case, the compiler checks that both sides have the same type (Person), which is static typing.
Example of Dynamic Typing
Person *personInstance = [Man new];
Here, the compiler detects a type mismatch (Person vs. Man) and treats it as dynamic typing, then checks for class relationships.
Why Use Dynamic Typing?
Dynamic typing supports polymorphism, allowing different classes to use methods with the same name, which enhances readability and reduces programming complexity.
Compile-Time vs. Runtime Checks
Since the type of an object stored in an id variable is unknown at compile time, some checks must occur at runtime.
Example of Compile-Time Error:
Dog *dogInstance = [[Dog alloc] init];
[dogInstance jump]; // Compiler error: Dog class has no jump method
Example of Runtime Error:
Animal *animalInstance = [[Dog alloc] init];
[(Cat *)animalInstance jump]; // Compiles but crashes at runtime
The compiler doesn't know the object type in animalInstance, so it compiles but fails during execution.
The id Type and Its Applications
id is a generic object type that can store objects of any class, acting as a "universal pointer."
Note: In id definitions, the pointer (*) is implicit. id pointers can only point to Objective-C objects.
Using id to Access Subclass Methods
// Define id type
NSObject *objectInstance = catInstance;
[(Cat *)objectInstance run];
Definition of id
typedef struct objc_object {
Class isa;
} *id;
Limitation: Calling a non-existent method with id results in a compiler error.
instancetype vs. id
Introduced in iOS 5, instancetype differs from id:
- Similarities: Both can be used as method return types.
- Differences:
instancetypereturns an object of the same type as the method's class, whileidreturns an unknown type.instancetypecan only be a return type, not a parameter likeid.
Application Scenarios for id
// Animal class
Animal *animalInstance = [Animal new];
// Dog class, inherits from Animal
Dog *dogInstance = [Dog new];
// Cat class, inherits from Animal
Cat *catInstance = [Cat new];
// Define id type
id genericObject = catInstance; // or id genericObject = dogInstance;
[genericObject run];
Both NSObject and id can point to any object, but NSObject requires type casting for compile-time checks, whereas id does not. The compiler treats id as a dynamic type and skips type checking.
Dynamic Binding
In Objective-C, whether an object invokes a specific method is determined at runtime, not compile time, known as dynamic binding. Objects receive messages, not call methods. The runtime system identifies the receiver's type (dynamic type recognition) and selects the appropriate method based on the message name (selector).
Message Function Mechanism
- Use the receiver's
isapointer to find its Class object. - Search for the selector in the Class object's method list.
- If not found, move up the inheritance hierarchy using the
isapointer. - Upon finding the method, use the receiver's
selfpointer to invoke the implementation (IMP) with parameters. - If the method is not found up to NSObject, report an "unrecognized selector" error.
Dynamic Type Inspection Methods
Introspection allows objects to determine their type at runtime.
Type Checking Methods
-
isKindOfClass:Checks if an instance is of a specific class or its subclass.Animal *animalInstance = [Animal new]; BOOL isInstance = [animalInstance isKindOfClass:[Animal class]]; -
isMemberOfClass:Checks if an instance is exactly of a specific class, excluding subclasses.BOOL isInstance = [dogInstance isMemberOfClass:[Animal class]]; // Returns NO (0) -
isSubclassOfClass:Checks if a class is a subclass of another class.
Checking if an Instance Can Respond to a Method
respondsToSelector:Determines if an instance can respond to a given selector.Animal *animalInstance = [Animal new]; SEL selector = @selector(eat); // Wrap eat as a SEL type BOOL canRespond = [animalInstance respondsToSelector:selector];
Responding to Methods
-
performSelector:Invokes a method specified by a selector.Animal *animalInstance = [Animal new]; SEL selector = @selector(eat); if ([animalInstance respondsToSelector:selector]) { [animalInstance performSelector:selector]; } else { NSLog(@"Method cannot be responded to"); } -
Multiple Parameters: Use
performSelector:withObject:for methods with parameters.SEL multiSelector = @selector(eat:andFoodName:); [animalInstance performSelector:multiSelector withObject:@"Hungry Dog" withObject:@"Food"];