Item 1: 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 = [Object new];
[obj performWith:parameter1 and:parameter2];
// Function calling (C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);
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:
NSString *someString = @"The string";
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:
NSString stackString;
// 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 *someString = @"The string";
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.
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:
CGRect frame;
frame.origin.x = 0.0f;
frame.origin.y = 10.0f;
frame.size.width = 100.0f;
frame.size.height = 150.0f;
A CGRect
is a C structure, defined like so:
struct CGRect {
CGPoint origin;
CGSize size;
};
typedef struct CGRect CGRect;
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.