Chapter     8

Runtime Architecture

The runtime system is a key element of the Objective-C platform. It’s what implements the dynamic features and object-oriented capabilities of the language. Its structure enables you to develop Objective-C code without being exposed to the internals of the runtime, yet also provides a public API that enables you to write code to directly invoke runtime services.

In the last chapter, you reviewed the dynamic features of Objective-C; in this one, you’ll explore the architecture and design of the runtime system and how it implements these features. You’ll identify the runtime’s major components, examine key implementation details, and then look at how your code interacts with the runtime, both at compile time and during program execution. By the end of this chapter, you’ll have a thorough understanding of the function of the runtime system in the Objective-C language.

Runtime Components

The Objective-C runtime system has two main components: the compiler and the runtime library. Let’s take out a magnifying glass to examine these in more detail and see how they are used to implement the runtime system.

Compiler

In Chapter 5, you briefly reviewed the general process of Objective-C source code compilation. As depicted in Figure 5-1, the compilation process takes input Objective-C source files and proceeds, in multiple phases (consisting of lexical analysis, syntax analysis, code generation and optimization, assembly, and linking operations), to produce the output binaries that form an executable program.

Just as the C standard library provides a standard API and implementation for the C programming language, the runtime library provides a standard API and implementation for the object-oriented features of Objective-C. This library is linked (during the linking phase) to all Objective-C programs. It’s the job of the compiler to take your input source code and generate code that uses the runtime library to produce a valid, executable Objective-C program.

Both the object-oriented elements and dynamic features of the Objective-C language are implemented by the runtime system. Together, this consists of the following:

  • Class elements (interface, implementation, protocols, categories, methods, properties, instance variables)
  • Class instances (objects)
  • Object messaging (including dynamic typing and binding)
  • Dynamic method resolution
  • Dynamic loading
  • Object introspection

In summary, when the compiler parses Objective-C source code that uses these language elements and features, it generates code with the appropriate runtime library data structures and function calls to implement the language-specified behaviors. To clarify how this works, let’s look at a few examples that demonstrate how the compiler generates code for Objective-C classes and objects, and how it performs object messaging.

Object Messaging Code Generation

When the compiler parses an object message (a message send expression), such as

[receiver message]

for example, it generates code that calls the runtime library function objc_msgSend(). This function takes as its input parameters the receiver and the selector of the message, along with any parameters passed in the message. Thus the compiler converts each messaging expression in the source code (i.e., of the form [receiver message]) to a call on the runtime library messaging function objc_msgSend(...), passing along any supplied parameters to the call. Each message is dynamically resolved, meaning that the message receiver type and the actual method implementation to be invoked are determined at runtime. For each class and object in the source code, the compiler builds the data structures required to perform object messaging.

Class and Object Code Generation

When the compiler parses Objective-C code containing class definitions and objects, it generates corresponding runtime data structures. An Objective-C class corresponds to a runtime library Class data type. According to the Apple Runtime Reference, the Class data type is a pointer to an opaque type with an identifier of objc_class.

typedef struct objc_class *Class;

An opaque data typeis a C struct type that is incompletely defined in its interface. Opaque types provide a form of data hiding, in that their variables can only be accessed through functions designed specifically for them. Runtime library functions are used to access the variables of the Class (i.e., objc_class) data type.

Just as Objective-C classes have a runtime data type, Objective-C objects also have a corresponding runtime data type. When the compiler parses Objective-C code for objects, it generates code that creates a runtime object type. This data type, defined in Listing 8-1, is a C struct with an identifier of objc_object.

Listing 8-1.  objc_object Data Type

struct objc_object
{
  Class isa;
  /* ...variable length data containing instance variable values...  */
};

When you create an object, memory is allocated for an objc_object type that consists of an isa pointer directly followed by the data for the instance variables of the object.

Note that, as with the Class data type, the objc_object type contains a variable named isa of type Class; in other words, a pointer to a variable of type objc_class. In fact, the runtime data type for all Objective-C objects and classes begin with an isa pointer. The runtime equivalent for the Objective-C id type is a C struct defined as a pointer to an objc_object, for example.

Listing 8-2.  id Type Definition

typedef struct objc_object
{
  Class isa;
} *id;

In other words, an id is just a pointer to a C struct with the identifier objc_object. The runtime data structure for Objective-C block objects follows the same convention, thereby enabling them to also be properly managed by the runtime system.

Viewing Runtime Data Structures

Before you go any further with these concepts, let’s create an example that will allow you to see how Objective-C objects and classes map to the runtime data structures discussed earlier. In Xcode, create a new project by selecting New image Project … from the Xcode File menu. In the New Project Assistant pane, create a command-line application (choose Command Line Tool from the Mac OS X Application selection) and click Next. In the Project Options window, specify Runspector for the Product Name, choose Foundation for the Project Type, select ARC memory management by checking the Use Automatic Reference Counting check box, and then click Next.

Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

Now let’s implement the code. Select the main.m file in the navigator pane, and then add the code shown in Listing 8-3.

Listing 8-3.  Runspector main.m File

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

// Test class 1
@interface TestClass1 : NSObject { @public int myInt; }
@end
@implementation TestClass1
@end

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Create a few instances of one class and display its data
    TestClass1 *tc1A = [[TestClass1 alloc] init];
    tc1A->myInt = 0xa5a5a5a5;
    TestClass1 *tc1B = [[TestClass1 alloc] init];
    tc1B->myInt = 0xc3c3c3c3;
    long tc1Size = class_getInstanceSize([TestClass1 class]);
    NSData *obj1Data = [NSData dataWithBytes:(__bridge const void *)(tc1A)
                                     length:tc1Size];
    NSData *obj2Data = [NSData dataWithBytes:(__bridge const void *)(tc1B)
                                      length:tc1Size];
    NSLog(@"TestClass1 object tc1 contains %@", obj1Data);
    NSLog(@"TestClass1 object tc2 contains %@", obj2Data);
    NSLog(@"TestClass1 memory address = %p", [TestClass1 class]);
  }
  return 0;
}

OK, let’s review this code. At the beginning of the main.m file, observe the import statement

#import <objc/runtime.h>

This statement is required to include the runtime library APIs in the file. Immediately after the import statements, the code defines the class TestClass1. This simple class has no methods and a single public instance variable. In the main() function, two TestClass1 objects are created and initialized, with the instance variable for each assigned a value. After the objects are created, the Foundation Framework NSData class is used to retrieve the data (in bytes) for each object. The Runtime Library function class_getInstanceSize() is used to retrieve the size (in bytes) of a class instance. Once the data is retrieved, it is displayed in the output pane using the Foundation Framework NSLog function. When you compile and run the program, you should observe output similar to that shown in Figure 8-1.

9781430250500_Fig08-01.jpg

Figure 8-1. Runspector program, object inspection

In the output pane, the TestClass1 objects contain the data in Listing 8-4.

Listing 8-4.  TestClass1 Output from Runspector Program

TestClass1 object tc1 contains <78110000 01000000 a5a5a5a5 00000000>
TestClass1 object tc2 contains <78110000 01000000 c3c3c3c3 00000000>
TestClass1 memory address = 0x100001178

Let’s analyze this data. It was shown in Listing 8-2 that when the compiler parses an object, it generates an instance of an objc_object type whose contents consist of an isa pointer and the values of the object’s instance variables. So, looking at the output from the program (see Listing 8-4), the data for TestClass1 object 1 (tc1) contains two items: an isa pointer (78110000 01000000) and the value assigned to its instance variable (a5a5a5a5 00000000). Similarly, the data for TestClass1 object 2 (tc2) contains an isa pointer (78110000 01000000) and the value assigned to its instance variable (c3c3c3c3 00000000). Notice that the first item in the object’s objc_object data structure is its isa pointer. Also notice that the isa pointer for both instances is the same. This is as expected since they are instances of the same class and hence should have the same pointer value. Now an isa pointer points to the address in memory of a class, as shown by the last line of Listing 8-4.

TestClass1 memory address = 0x100001178

You may be thinking, “That memory address isn’t the same as the value shown for the isa pointer!” Well actually it is: the Mac Pro computer on which this program was run is little-endian, which means that it reverses the order of bytes as stored in memory. Thus, the byte order of the address displayed by the isa pointer (0x78110000 01) is the reverse of the actual memory address. Well, hopefully this is all starting to make more sense. Let’s go a little further. Update the main() function by adding the code shown in Listing 8-5.

Listing 8-5.  Runspector main() Function, Class Data Type

// Retrieve and display the data for the TestClass1 class object
id testClz = objc_getClass("TestClass1");
long tcSize = class_getInstanceSize([testClz class]);
NSData *tcData = [NSData dataWithBytes:(__bridge const void *)(testClz)
                                 length:tcSize];
NSLog(@"TestClass1 class contains %@", tcData);
NSLog(@"TestClass1 superclass memory address = %p", [TestClass1 superclass]);

In Listing 8-5, you added code to retrieve and display the data bytes for the TestClass1 class object. The runtime objc_getClass() function is used to retrieve the TestClass1 class, and then its data is displayed with an NSData instance and the NSLog function. The code also displays the memory address for the TestClass1 superclass. When you compile and run the program, you should observe output similar to that shown in Figure 8-2.

9781430250500_Fig08-02.jpg

Figure 8-2. Runspector program, object and class inspection

The output pane displays the results shown in Listing 8-6.

Listing 8-6.  TestClass1 Output from Runspector Program

TestClass1 object tc1 contains <88110000 01000000 a5a5a5a5 00000000>
TestClass1 object tc2 contains <88110000 01000000 c3c3c3c3 00000000>
TestClass1 memory address = 0x100001188
TestClass class contains <60110000 01000000 4028a271 ff7f0000>
TestClass1 superclass memory address = 0x7fff71a22840

The data of TestClass1 objects are consistent with what you observed before—each contains an isa pointer and the value assigned to its instance variable. The data for the TestClass1 class object consists of an isa pointer (60110000 01000000) and another value (4028a271 ff7f0000). This additional value is actually the pointer to the object’s superclass. Earlier, you learned that the data structure for a class has an isa pointer (refer back to Listing 8-1), so this result is also consistent with the expected values. The memory address listed for the superclass confirms what you see in the data for the class object.

Well, you should now have a good understanding of the role of the compiler within the Objective-C runtime. You also implemented a program that used a few of the runtime APIs to inspect the data structures generated by the compiler. Great job! Now let’s move on to runtime library and examine some of its implementation details.

Runtime Library

The Apple Objective-C runtime library implements the object-oriented features and dynamic properties of the Objective-C language. For the most part, the runtime library acts behind the scenes, but it also includes a public API that enables it to be used directly in your code.

This API is expressed in C and consists of a set of functions, data types, and language constants. You learned about some of the key runtime data types (e.g., objc_object, objc_class) and used a few of these functions in the previous section. Overall, the runtime library data types can be grouped as follows:

  • Class definition data structures (class, method, ivar, category, IMP, SEL, etc.)
  • Instance data types (id, objc_object, objc_super)
  • Values (BOOL)

The functions fall into the following categories:

  • Object messaging
  • Class functions
  • Instance functions
  • Protocol functions
  • Method functions
  • Property functions
  • Selector functions

The runtime library also defines several Boolean constants (YES, NO) and null values (NULL, nil, Nil).

The runtime library public API is declared in the runtime.h header file. Apple’s runtime library is available for examination at http://opensource.apple.com . It incorporates a variety of design elements and system services to provide excellent performance and extensibility as the Objective-C language evolves over time. Later in this chapter, you’ll look at elements of the runtime library implementation. For now, let’s get a little hands-on experience by developing a program that uses these APIs.

Creating a Class Using the Runtime Library APIs

Now you are going to create a program that dynamically creates a class using the runtime APIs. In Xcode, create a new project by selecting New image Project … from the Xcode File menu. In the New Project Assistant pane, create a command-line application (choose Command Line Tool from the Mac OS X Application selection) and click Next. In the Project Options window, specify DynaClass for the Product Name, choose Foundation for the Project Type, select ARC memory management by checking the Use Automatic Reference Counting check box, and then click Next.

Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

OK, now let’s implement the code. Select the main.m file in the navigator pane, and then add the code shown in Listing 8-7.

Listing 8-7.  DynaClass main.m File

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>

NSString *greeting(id self, SEL _cmd)
{
  return [NSString  stringWithFormat: @"Hello, World!"];
}

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    // Dynamically create a class
    Class dynaClass = objc_allocateClassPair([NSObject class], "DynaClass", 0);
    
    // Dynamically add a method, use existing method (description) to retrieve signature
    Method description = class_getInstanceMethod([NSObject class], @selector(description));
    const char *types = method_getTypeEncoding(description);
    class_addMethod(dynaClass, @selector(greeting), (IMP)greeting, types);
    
    // Register the class
    objc_registerClassPair(dynaClass);
    
    // Now use the class - create an instance and send it a message
    id dynaObj = [[dynaClass alloc] init];
    NSLog(@"%@", objc_msgSend(dynaObj, NSSelectorFromString(@"greeting")));

  }
  return 0;
}

Note this additional import statement at the top of the file:

#import <objc/message.h>

This statement is required to include the runtime library messaging API (e.g., objc_msgSend()) in the file. Immediately after the import statements, the code defines the function greeting() that returns a simple greeting. This function will be used for the method implementation that will be dynamically added to the class. The main() function contains the logic for creating a new class using the runtime API. It creates a class pair (the class and its metaclass), adds a method to it that points to the greeting() function defined earlier, and then registers the class pair in the runtime, thereby enabling you to create instances of the class. Notice that the method signature is obtained by using that of a method (the NSObject description method) with the same signature. The next few lines create an instance of the class and send it a message, with the output from the method invocation logged to the output pane. When you compile and run the program, you should observe output similar to that shown in Figure 8-3.

9781430250500_Fig08-03.jpg

Figure 8-3. DynaClass program, dynamic class creation

Great job! You have now completed your overview of the runtime APIs and their usage. Please refer to the Apple Runtime Reference for a complete definition of these APIs. Now let’s examine some of the runtime library design and implementation details.

Implementing Runtime Object Messaging

The runtime library includes functions that provide access to the following information (the functions are identified in parentheses):

  • The class definition of an object (objc_getClass)
  • The superclass for a class (class_getSuperclass)
  • The metaclass definition of an object (objc_getMetaClass)
  • The name of a class (class_getName)
  • The version information for a class (class_getVersion)
  • The size of a class in bytes (class_getInstanceSize)
  • The list of instance variables for a class (class_copyIvarList)
  • The list of methods for a class (class_copyMethodList)
  • The list of protocols for a class (class_copyProtocolList)
  • The list of properties for a class (class_copyProperyList)

Taken together, the runtime data types and functions give the runtime library the information it requires to implement various Objective-C features, such as object messaging (see Figure 8-4).

9781430250500_Fig08-04.jpg

Figure 8-4. Runtime system messaging operation

When you send a message to an object, the runtime looks up the instance methods for the class via custom code that leverages the class method cache and its vtable. It searches the entire class hierarchy for the corresponding method, and when found, jumps to the method implementation. The runtime library includes numerous design mechanisms to implement object messaging. You’ll look at a few of these, beginning with vtables.

Method Lookup via vtables

The runtime library defines a method data type (objc_method), as shown in Listing 8-7.

Listing 8-7.  Runtime Library Method Data Type

struct objc_method
{
  SEL method_name;
  char * method_types;
  IMP method_imp;
};
typedef objc_method Method;

method_name is a variable of type SEL that describes the method name; method_types describe the data types of the parameters of the method; and method_imp is a variable of type IMP that provides the address of the function invoked when the method is selected for invocation (recall that Objective-C methods are, in fact, C functions that take at least two arguments: self and _cmd). Because method invocation occurs potentially millions of times during the execution of a program, the runtime system requires a fast, efficient mechanism for method lookup and invocation. A vtable, also called a dispatch table, is a mechanism commonly used in programming languages to support dynamic binding. The Objective-C runtime library implements a custom vtable dispatching mechanism designed to maximize both performance and flexibility. A vtable is an array of IMPs (Objective-C method implementations). Every runtime Class instance (objc_class) has a pointer to a vtable.

Each Class instance also includes a cache of pointers to recently used methods. This provides performance optimization for method calls. The logic implemented by the runtime library to perform method lookup is shown in Figure 8-5.

9781430250500_Fig08-05.jpg

Figure 8-5. Runtime library method lookup

In Figure 8-5, the runtime library first searches the cache for the method IMP (pointer to the start of the method implementation). If not found, it then looks for the method IMP in its vtable, and if found, the IMP is then stored in the cache for future lookups. This design enables the runtime to perform a fast and an efficient method lookup.

Selector Uniquing via the dyld Shared Cache

The startup overhead for an Objective-C program is directly proportional to the amount of time required for selector uniquing. A selector name must be unique across an executable program, however, your custom classes and every shared library in your program contain their own copies of selector names, many of which may be duplicates (i.e., alloc, init, etc.). The runtime needs to choose a single canonical SEL pointer value for each selector name, and then update the metadata for every call site and method list to use the unique value. This process must be performed at application startup and uses up system resources (memory and program startup time). To make this process more efficient, the runtime performs selector uniquing through use of the dyld shared cache. dyld is a system service that locates and loads dynamic libraries. It includes a shared cache that enables these libraries to be shared across processes. The dyld shared cache also includes a selector table, thereby enabling selectors for shared libraries and your custom classes to be accessed from the cache. Thus, the runtime is able to retrieve selectors for shared libraries from the dyld shared cache, and only needs to update selectors for your app’s custom classes.

Message Dispatching

Message invocation is ubiquitous in an Objective-C program. Just for application startup, objc_msgSend(), the runtime library function that performs object messaging, is called millions of times. Therefore, it is critical to optimize this code because even a slight change in its performance has a significant impact on the overall application performance. This method must look up the IMP corresponding to the message receiver and selector, and then jump to the IMP to begin its execution. In the last few sections, you learned how method caches and vtables are used to retrieve the IMP. Once this is accomplished, the runtime then dispatches the method using custom, optimized code written in assembly language. This code, referred to sometimes as a trampoline, looks up the right code and then jumps directly to it. This trampoline code works with any combination of parameters passed to it because they are just passed along for the method IMP to read. To handle the possible return value types, the runtime provides several different objc_msgSend() implementations.

Accessing Class Instance Methods

Now that you know how the runtime library looks up (and invokes) instance methods, you may be wondering how it does this for class methods (i.e., perform object messaging for class methods). Well, every Objective-C class is also, in fact, an object, hence it is capable of receiving messages, such as

[NSObject alloc]

So that explains how you can send messages to classes, but how does the runtime find and invoke class methods? Well, the runtime library implements this capability with metaclasses.

A metaclassis a special type of class object that stores information that enables the runtime to look up and invoke the class methods of an Objective-C class. There’s a unique metaclass for every Objective-C class because every class potentially has a unique list of class methods. The runtime API provides functions for accessing metaclasses.

Class hierarchies are observed with metaclasses, as with regular classes. In the same way that a class points to a superclass with its superclass pointer, a metaclass points to the metaclass of a class’s superclass using its own superclass pointer (refer back to Figure 8-4).

The base class’s metaclass sets its superclass to the base class itself. The result of this inheritance hierarchy is that all instances, classes, and metaclasses in the hierarchy inherit from the hierarchy’s base class.

The isa variable for an object points to a class that describes that instance, and can thus be used to access its instance methods, ivars, and so forth. The isa variable for an Objective-C class (object) points to a metaclass that describes the class (its class methods, etc.).

Putting this all together, the runtime performs messaging for both instance and class methods, as follows:

  • When your source code sends a message to an object, the runtime retrieves the appropriate instance method implementation (via the corresponding class instance method vtable) and jumps to that method.
  • When your source code sends a message to a class, the runtime retrieves the appropriate class method implementation (via its metaclass class method vtable) and jumps to that method.

Whew, that was a mouthful!! If necessary, take a moment to go over these concepts, and when you’re ready, we’ll attempt to clarify this with an example.

Examining Class Methods

Now you’re going to code an example to see how the runtime data structures utilize metaclasses and the place of metaclasses in the overall class hierarchy. You’ll do this by updating the Runspector program (developed earlier in this chapter) to retrieve metaclass information. Start Xcode (if not already started) and open the Runspector project by selecting Open Recent image Runspector.xcodeproj from the Xcode File menu. Select the main.m file in the navigator pane and then add the code shown in Listing 8-8.

Listing 8-8.  Runspector main() Function, Metaclass Data

// Retrieve and display metaclass data
id metaClass = objc_getMetaClass("TestClass1");
long mclzSize = class_getInstanceSize([metaClass class]);
NSData *mclzData = [NSData dataWithBytes:(__bridge const void *)(metaClass)
                                  length:mclzSize];
NSLog(@"TestClass1 metaclass contains %@", mclzData);
class_isMetaClass(metaClass) ?
  NSLog(@"Class %s is a metaclass", class_getName(metaClass)) :
  NSLog(@"Class %s is not a metaclass", class_getName(metaClass));

This code uses the runtime function objc_getMetaClass() to retrieve the metaclass definition for the named class, and then displays its associated data bytes. Next, the code uses the runtime function class_isMetaClass() to test whether the object is a metaclass and then logs a corresponding message to the console. You may be wondering about the use of the bridged cast (__bridge). Well, the NSData dataWithBytes:length: method takes as its first parameter a variable of type (const void *). You may recall from Chapter 4 that ARC memory management prohibits casts directly from Objective-C objects and (void *) types. A bridged cast (discussed in Chapter 6) enables the compiler to manage these scenarios. In this case, because there will be no transfer of ownership (i.e., the object assigned to the variable metaClass will still be managed by ARC), the __bridge annotation is used. On compiling and running the updated program, you should observe output similar to that shown in Figure 8-6.

9781430250500_Fig08-06.jpg

Figure 8-6. Runspector program, metaclass inspection

Note the metaclass information at the bottom of the output pane, as shown in Listing 8-9.

Listing 8-9.  Runspector Program Metaclass Information

TestClass1 metaclass contains
<6828a271 ff7f0000 6828a271 ff7f0000 d0821000 01000000 703d1000 01000000 60821000 01000000>
Class TestClass1 is a metaclass

The metaclass data contains the isa pointer, the superclass pointer, and additional information. The superclass of TestClass1 is NSObject. Because this class defines no custom class methods, its isa pointer is also NSObject. Hence the metaclass isa pointer and superclass pointer should be identical. This is confirmed with the results displayed in Listing 8-9 (the value for each is 6828a271 ff7f0000).

This completes your review of the runtime library API and its implementation. In the next section, you’ll look at the Objective-C APIs that you can use for directly interacting with the runtime system.

Interacting with the Runtime

Objective-C programs interact with the runtime system to implement the dynamic features of the language. In Figure 8-7, this interaction occurs at three levels:

  • Objective-C source code
  • Foundation Framework NSObject methods
  • Runtime library API

9781430250500_Fig08-07.jpg

Figure 8-7. Interacting with the runtime system

In the preceding sections, you discussed the role of the compiler and the runtime library. Now you’ll spend time looking at the runtime features of the Foundation Framework NSObject class.

NSObject Runtime Methods

As discussed throughout this chapter, the Objective-C language provides many dynamic programming capabilities. The runtime system provides a set of APIs that enable you to directly interact with the runtime; however, these are coded in C and thus mandate a procedural programming approach. As an alternative, the Foundation Framework NSObject class provides a set of methods that duplicate much of the functionality available from the runtime APIs. As your custom classes (and nearly all of the Cocoa framework classes) descend from NSObject, your code inherits these methods and thus can use them directly. The functionality provided by the NSObject runtime methods includes:

  • Object introspection
  • Message forwarding
  • Dynamic method resolution
  • Dynamic loading

Next, you’ll demonstrate this with an example that uses NSObject runtime methods to perform object introspection.

Performing Object Introspection

You are now going to create a program that dynamically creates a class using the runtime APIs. In Xcode, create a new project by selecting New image Project … from the Xcode File menu. In the New Project Assistant pane, create a command-line application (choose Command Line Tool from the Mac OS X Application selection) and click Next. In the Project Options window, specify Introspector for the Product Name, choose Foundation for the Project Type, select ARC memory management by checking the Use Automatic Reference Counting check box, and then click Next.

Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

Now let’s implement the code. Select the main.m file in the navigator pane, and then add the code shown in Listing 8-10.

Listing 8-10.  Introspector main.m File

#import <Foundation/Foundation.h>

// Test class 1
@interface Greeter : NSObject
@property (readwrite, strong) NSString *salutation;
- (NSString *)greeting:(NSString *)recipient;
@end
@implementation Greeter
- (NSString *)greeting:(NSString *)recipient
{
  return [NSString stringWithFormat:@"%@, %@", [self salutation], recipient];
}
@end

int main(int argc, const char * argv[])
{
  @autoreleasepool
  {
    Greeter *greeter = [[Greeter alloc] init];
    [greeter setSalutation:@"Hello"];
    
    if ([greeter respondsToSelector:@selector(greeting:)] &&
        [greeter conformsToProtocol:@protocol(NSObject)])
    {
      id result = [greeter performSelector:@selector(greeting:) withObject:@"Monster!"];
      NSLog(@"%@", result);
    }
  }
  return 0;
}

Immediately after the import statement, the code defines the Greeter class. This class defines a property and a method that returns a simple greeting. In the main() function, you first create a Greeter instance and set the value of the property. Next, you use the NSObject runtime methods to perform object introspection. Specifically, you test the NSObject respondsToSelector: and conformsToProtocol: methods. If the result returned from these two conditional expressions is YES, the code sends a message to the Greeter instance using the NSObject runtime method performSelector:withObject:. Finally, the result returned from this method is logged to the output pane. When you compile and run the program, you should observe output similar to that shown in Figure 8-8.

9781430250500_Fig08-08.jpg

Figure 8-8. Introspector program output

The complete list of NSObject runtime methods is defined in the NSObject class reference and the NSObject protocol reference. These are found in the Foundation Framework Reference Guide.

Roundup

In this chapter, you examined in detail the key components of the runtime system architecture. You should now have a good understanding of how the runtime system implements the object-oriented and dynamic features of the language. To recap, the following are the key takeaways:

  • The Objective-C runtime system has two main components: the compiler and the runtime library. The compiler takes input Objective-C source code and generates code that is executed by the runtime library. This library is linked (during the linking phase) to all Objective-C programs. Together, they implement all of the object-oriented and dynamic features of the language.
  • The runtime library APIs define a set of data types, functions, and constants. Many of these data types and functions map to corresponding Objective-C language elements (i.e., objects, classes, protocols, methods, instance variables, etc.). The runtime library public API is expressed in C.
  • The runtime library implementation includes a variety of features and mechanisms to enhance application performance and extensibility. Examples of these include the method cache, vtable, and use of the dyld shared cache.
  • The runtime library utilizes metaclasses to look up and invoke class methods. A metaclass is a special type of class object that stores information enabling the runtime to look up and invoke the class methods of an Objective-C class.
  • The Foundation Framework NSObject class provides a set of methods for invoking runtime system functionality and behaviors. These methods perform object introspection, dynamic method resolution, dynamic loading, and message forwarding.

This completes your deep dive into the runtime system. Please feel free to review what you have gone over in the last two chapters and experiment with the example programs. Understanding the runtime system is very important for becoming proficient at developing Objective-C programs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.197.95