So, following up from last week’s post on introspection, an obvious question is why: why would anyone need or want to do that? The answer lies in a power OOP concept known as Dynamic Typing.
The id
type
In developing any large program, or when using Apple’s provided code, you will have to confront a situation in which you don’t know the type of an object (typically an argument, although sometimes an ivar). How might this come to be?
Imagine that you had a class that processed some external data. You don’t know or don’t care where the data comes from, but you should be prepared to handle many different types. Your data might come from a text file, where the contents might be read and passed into your method as an NSString. You might have to process the data in your own program somewhere else, and the data then would be as an NSArray or NSSet. Alternatively, the data could be coming from the internet as a JSON response, which must be parsed into an NSDictionary (don’t worry if you don’t know what JSON is…there’ll be something about this later down the road). Or just maybe the data is coming in as a bag of bits, stored as an NSData. How would you handle all this?
To begin, we need to introduce a new data type: the id type:
id someObject;
The id type is designed to be a generic type which can hold any object type (in other words, id does not work with primitive types, such as ints and BOOLs). Note that you do not need the asterisk (*) after id—the asterisk is implied. The id type is typically typed (cast) to a specific type after using the introspection methods. To implement our previous hypothetical example:
- (void)processData:(id)someData {
if ([someData isKindOfClass:[NSString class]])
NSLog(@"input data is %@", someData);
else if ([someData isKindOfClass:[NSArray class]]) {
// Cast someData into an NSArray
NSArray *dataArray = (NSArray *)someData;
NSLog(@"First object in dataArray is %@", [dataArray objectAtIndex:0]);
}
else if ([someData isKindOfClass:[NSDictionary class]]) {
// Cast someData into an NSDictionary
NSDictionary *dataDict = (NSDictionary *)someData;
NSLog(@"Keys in dataDict are %@", [dataDict allKeys]);
}
else if ([someData isKindOfClass:[NSData class]])
NSLog(@"someData is a bag of bits.");
else
NSLog(@"someData is an unsupported type:\n%@", someData);
}
Type Checking and Polymorphism
Given the following method:
- (void)showObject:(id)anObject {
[anObject display];
}
What would happen if anObject is a Fraction? What would happen if it was a MixedNumber? More importantly, what would happen if it was either (and you didn’t know which)? How would the system know which version of the method to call?
This is where polymorphism comes in.
The first issue is whether anObject even responds to the object. You might remember the respondsToSelector: method, which would hopefully solve this minor obstacle. If the type had not been id but instead been something like NSArray, the compiler would gladly tell you something along the lines of
warning: 'NSArray' does not respond to 'display'
But because the compiler doesn’t know that—it is an id type—there is no compile time checking. Therefore, the introspection method is a must, to avoid a runtime crash (although the build will not display any sort of warning or error about this).
But back to the larger issue at hand—the point of the id type is to allow you to not know what type the value is; that is eventually determined at runtime. But in fact, the issue is resolved a runtime without any more programmer or user intervention. Every object keeps track of what class it is through the isa variable that is inherited from NSObject. The runtime system will be able to determine which class the object is of, and call the appropriate method then. Keep in mind that it is part of Objective-C’s philosophy to defer as many decisions as possible from compile time to runtime, to allow a more fluid and dynamic coding style.
Dynamic versus Static Typing
Why wouldn’t you want to use the id type for all objects?
When you declare a variable to be of a particular class, such as in
NSArray *array = myArray;
You are using static typing, where “static” indicates that the type of the variable won’t (or shouldn’t—the compiler will warn you) change.
So what are the benefits?
- The compiler will ensure that the variable is used consistently—that is, you’re only assigning proper objects of the same type to the variable, to avoid issues (with the object not respond to methods and such) later on. The compiler can check to see that an object actually implements a method, rather than having the program crash at runtime because a method implementation was not actually found.
- It makes the code more readable and self-documenting. Given the following lines:
Fraction *f1;
or
id f1;
Which is more clear? Using proper type declarations (along with good variable names) will make the code a lot more meaningful later on.
- It is better to fix issues at compile time than runtime. It’s difficult to test everything at runtime; the reason many commercial programs crash (assuming that they have actually been tested before they ship) is because the testers haven’t explored every possible user input. Remember that the user is trying to break your code; in a large program, you may not be the one using your program which has already shipped when a runtime issue is encountered, leading to all sorts of issues and negative reviews as you scramble to put out a patch. Do yourself a favor, and let the compiler help you catch issues before you ship.
One Final Rule
If you declare a method by the same name (including colons) in multiple classes, make sure that each declaration agrees on the type of each argument and the method’s return type. Otherwise, the compiler will have to make assumptions, especially when id types are involved, which could result in the compiler generating code that doesn’t work when the program is run. For example, if one version of an add: method took an integer and returned a float while another took a Fraction and returned void, you might have random issues (on the order of garbage output or values that simply don’t make sense) because the compiler has no way of knowing if myNumber (a hypothetical example) is of the former or latter type.
This post is part of the Learn Objective-C in 24 Days course.
Author: Feifan Zhou