Effective Objective-C 2.0:Item 14 Class Object

Class hierarchy 这个给我理清楚了 哈哈

Item 14: Understand What a Class Object Is

Objective-C is extremely dynamic in nature. Item 11 explains how the implementation for a given method call is looked up at runtime, and Item 12 explains how forwarding works when a class does not immediately respond to a certain selector. But what about the receiver of a message: the object itself? How does the runtime know what type an object is? The type of an object is not bound at compile time but rather is looked up at runtime. Moreover, a special type, id, can be used to denote any Objective-C object type. In general, though, you specify a type whenever possible so that the compiler can warn about sending messages that it thinks the receiver doesn’t understand. Conversely, any object that is of type id will be assumed to respond to all messages.

As you’ll know from Item 12, though, the compiler cannot actually know all the selectors a certain type understands, since they can be dynamically inserted at runtime. However, even if this technique is used, the compiler expects to see the method prototype defined in a header somewhere such that it can know the full method signature to be able to emit the correct code to perform the message dispatch.

Inspecting the type of an object at runtime is known as introspection and is a powerful and useful feature baked into the Foundation framework as part of theNSObject protocol, to which all objects that inherit from the common root classes (NSObject and NSProxy) conform. Using these methods rather than directly comparing classes of objects is prudent, as I will explain. However, before looking at introspection techniques, here is some background as to what an Objective-C object is.

Every Objective-C object instance is a pointer to a blob of memory. That’s why you see the * next to the type when declaring a variable:

NSString *pointerVariable = @"Some string";

If you’ve come from a C world of programming, you’ll understand exactly what this means. For the non-C programmers among you, this means that pointerVariable is a variable holding a memory address, where the data stored at that memory address is the NSString itself. The variable therefore “points to” the NSString instance. This is how all Objective-C objects are referred to; if you tried to allocate the memory for an object on the stack instead, you would receive an error from the compiler:

NSString stackVariable = @"Some string";
// error: interface type cannot be statically allocated

The generic object type, id, is already a pointer in itself, so you use it like so:

id genericTypedString = @"Some string";

This definition is semantically the same as if the variable were of type NSString*. The only difference with specifying the type fully is that the compiler can help and warn if you attempt to call a method that doesn’t exist for instances of that class.

The data structure behind every object is defined in the runtime headers along with the definition of the id type itself:

typedef struct objc_object {
    Class isa;
} *id;

Therefore, each object contains as its first member a variable of type Class. This variable defines the class of the object and is often referred to as the “is a” pointer. For example, the object “is a” NSString. The Class object is also defined in the runtime headers:

typedef struct objc_class *Class;
struct objc_class {
    Class isa;
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;

This structure holds metadata about the class, such as what methods instances of the class implement and what instance variables instances have. The fact that this structure also has an isa pointer as its first variable means that a Class is itself an Objective-C object. This structure also has another variable, called super_class,which defines the class’s parent. The type of a class (i.e., the class the isa pointer points to) is another class, known as the metaclass, which is used to describe the metadata about instances of the class itself. This is where class methods are defined, since they can be thought of as instance methods of instances of a class. There is only ever one instance of a class, though, and only one instance of its associated metaclass.

The hierarchy of a class called SomeClass that inherits from NSObject looks like the one shown in Figure 2.6.


Figure 2.6 Class hierarchy for instances of SomeClass, which inherits fromNSObject, including the metaclass hierarchy

The super_class pointer creates the hierarchy, and the isa pointer describes the type of an instance. You can manipulate this layout to perform introspection. You can find out whether an object responds to a certain selector and conforms to a certain protocol and determine information about what part of the class hierarchy the object belongs to.

Inspecting the Class Hierarchy

The introspection methods can be used to inspect the class hierarchy. You can check whether an object is an instance of a certain class by using isMemberOfClass: orwhether an object is an instance of a certain class or any class that inherits from it by using isKindOfClass:. For example:

NSMutableDictionary *dict = [NSMutableDictionary new];
[dict isMemberOfClass:[NSDictionary class]]; ///< NO
[dict isMemberOfClass:[NSMutableDictionary class]]; ///< YES
[dict isKindOfClass:[NSDictionary class]]; ///< YES
[dict isKindOfClass:[NSArray class]]; ///< NO

This kind of introspection works by using the isa pointer to obtain the object’s class and then walking the inheritance hierarchy, using the super_class pointer. Given the dynamic nature of objects, this feature is extremely important. You cannot ever fully know the type without introspection, unlike other languages with which you might be familiar.

Introspection of the class of an object is extremely useful given the dynamic typing used by Objective-C. Introspection is commonly used when retrieving objects from collections, since they are not strongly typed, meaning that when objects are retrieved from collections, they are usually of type id. Introspection can then be used if the type needs to be known: for example, when needing to generate a comma-separated string from objects stored in an array to be saved to a text file. The following code could be used in that scenario:

- (NSString*)commaSeparatedStringFromObjects:(NSArray*)array {
    NSMutableString *string = [NSMutableString new];
    for (id object in array) {
        if ([object isKindOfClass:[NSString class]]) {
            [string appendFormat:@"%@,", object];
        } else if ([object isKindOfClass:[NSNumber class]]) {
            [string appendFormat:@"%d,", [object intValue]];
        } else if ([object isKindOfClass:[NSData class]]) {
            NSString *base64Encoded = /* base64 encoded data */;
            [string appendFormat:@"%@,", base64Encoded];
        } else {
            // Type not supported
    return string;

It is also possible to check class objects for equality. You can do so by using the ==operator rather than an isEqual: method, as you would usually use when comparing Objective-C objects (see Item 8). The reason is that classes are singletons, and so only a single instance of each class’s Class object is within an application. Thus, another way to test whether an object is exactly an instance of a class would be to do the following:

id object = /* ... */;
if ([object class] == [EOCSomeClass class]) {
    // 'object' is an instance of EOCSomeClass

However, you should always prefer the introspection methods to direct equality of class object, because the introspection methods are able to take into account objects that make use of message forwarding (see Item 12). Consider an object that forwards all selectors to another. Such an object is called a proxy, and NSProxy is a root class specifically for objects like that.

Usually, such proxy objects will return the proxy class (i.e., subclass of NSProxy) if the method class is called rather than the class of the object being proxied. However, if introspection methods, such as isKindOfClass:, are called on them, they will proxy the message through to the proxied object. This means that the answer to the message will be as though the proxied object is being inspected. Therefore, this will yield a result different from inspecting the class object returned from calling the class method, since that will be the proxy class itself rather than the class of the proxied object.

Things to Remember

Image The class hierarchy is modeled through Class objects that each instance has a pointer to in order to define its type.

Image Introspection should be used when the type of the object cannot be known for certain at compile time.

Image Always prefer introspection methods where possible, rather than direct comparison of class objects, since the object may implement message forwarding.

想对作者说点什么? 我来说一句

Effective Objective-C 2.0 epub版

2015年10月27日 8.1MB 下载