Accustoming Yourself to Objective-C
- Item 1: Familiarize Yourself with Objective-C's Roots
- Item 2: Minimize Importing Headers in Headers
- Item 3: Prefer Literal Syntax over the Equivalent Methods
- Item 4: Prefer Typed Constants to Preprocessor #define
- Item 5: Use Enumerations for States, Options, and Status Codes
Objective-C brings object-oriented features to C through an entirely new syntax. Often described as verbose, Objective-C syntax makes use of a lot of square brackets and isn’t shy about using extremely long method names. The resulting source code is very readable but is often difficult for C++ or Java developers to master.
This chapter covers fundamental topics; subsequent chapters cover specific areas of the language and associated frameworks.
Item 1: Familiarize Yourself with Objective-C’s Roots
Objective-C is similar to other object-oriented languages, such as C++ and Java, but also differs in many ways. If you have experience in another object-oriented language, you’ll understand many of the paradigms and patterns used. However, the syntax may appear alien because it uses a messaging structure rather than function calling. Objective-C evolved from Smalltalk, the origin of messaging. The difference between messaging and function calling looks like this:
// Messaging (Objective-C)
Object*obj = [
// Function calling (C++)
Object*obj = new
The key difference is that in the messaging structure, the runtime decides which code gets executed. With function calling, the compiler decides which code will be executed. When polymorphism is introduced to the function-calling example, a form of runtime lookup is involved through what is known as a virtual table. But with messaging, the lookup is always at runtime. In fact, the compiler doesn’t even care about the type of the object being messaged. That is looked up at runtime as well, through a process known as dynamic binding, covered in more detail in Item 11.
The Objective-C runtime component, rather than the compiler, does most of the heavy lifting. The runtime contains all the data structures and functions that are required for the object-oriented features of Objective-C to work. For example, the runtime includes all the memory-management methods. Essentially, the runtime is the set of code that glues together all your code and comes in the form of a dynamic library to which your code is linked. Thus, whenever the runtime is updated, your application benefits from the performance improvements. A language that does more work at compile time needs to be recompiled to benefit from such performance improvements.
Objective-C is a superset of C, so all the features in the C language are available when writing Objective-C. Therefore, to write effective Objective-C, you need to understand the core concepts of both C and Objective-C. In particular, understanding the memory model of C will help you to understand the memory model of Objective-C and why reference counting works the way it does. This involves understanding that a pointer is used to denote an object in Objective-C. When you declare a variable that is to hold a reference to an object, the syntax looks like this:
This syntax, mostly lifted straight from C, declares a variable called someString whose type is NSString*. This means that it is a pointer to an NSString. All Objective-C objects must be declared in this way because the memory for objects is always allocated in heap space and never on the stack. It is illegal to declare a stack-allocated Objective-C object:
// error: interface type cannot be statically allocated
The someString variable points to some memory, allocated in the heap, containing an NSString object. This means that creating another variable pointing to the same location does not create a copy but rather yields two variables pointing to the same object:
NSString*anotherString = someString;
There is only one NSString instance here, but two variables are pointing to the same instance. These two variables are of type NSString*, meaning that the current stack frame has allocated 2 bits of memory the size of a pointer (4 bytes for a 32-bit architecture, 8 bytes for a 64-bit architecture). These bits of memory will contain the same value: the memory address of the NSString instance.
Figure 1.1 illustrates this layout. The data stored for the NSString instance includes the bytes needed to represent the actual string.
Figure 1.1 . Memory layout showing a heap-allocated NSString instance and two stack-allocated pointers to it
The memory allocated in the heap has to be managed directly, whereas the stack-allocated memory to hold the variables is automatically cleaned up when the stack frame on which they are allocated is popped.
Memory management of the heap memory is abstracted away by Objective-C. You do not need to use malloc and free to allocate and deallocate the memory for objects. The Objective-C runtime abstracts this out of the way through a memory-management architecture known as reference counting (see Item 29).
Sometimes in Objective-C, you will encounter variables that don’t have a * in the definition and might use stack space. These variables are not holding Objective-C objects. An example is CGRect, from the CoreGraphics framework:
A CGRect is a C structure, defined like so:
These types of structures are used throughout the system frameworks, where the overhead of using Objective-C objects could affect performance. Creating objects incurs overhead that using structures does not, such as allocating and deallocating heap memory. When nonobject types (int, float, double, char, etc.) are the only data to be held, a structure, such as CGRect, is usually used.
Before embarking on writing anything in Objective-C, I encourage you to read texts about the C language and become familiar with the syntax. If you dive straight into Objective-C, you may find certain parts of the syntax confusing.
Things to Remember
- Objective-C is a superset of C, adding object-oriented features. Objective-C uses a messaging structure with dynamic binding, meaning that the type of an object is discovered at runtime. The runtime, rather than the compiler, works out what code to run for a given message.
- Understanding the core concepts of C will help you write effective Objective-C. In particular, you need to understand the memory model and pointers.