Learn Objective-C, Objects (Part 8): Dynamic Typing

So, following up from the last 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:

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?

Which is more clear? Using proper type declarations (along with good variable names) will make the code a lot more meaningful later on.

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.


Previous Lesson Next Lesson