The Essence of Classes and Objects
When we create a MyClass in Objective-C and instantiate it in main, what does Objective-C actually look like underneath?
First, we need to understand the underlying structure of Objective-C objects. Objective-C code is essentially converted to C/C++ code. Use the following command to convert main.m to C++:
clang -rewrite-objc main.m
Opening the generated C++ file, we see that MyClass is actually defined as:
#ifndef _REWRITER_typedef_MyClass
#define _REWRITER_typedef_MyClass
typedef struct objc_object MyClass;
typedef struct {} _objc_exc_MyClass;
#endif
struct MyClass_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
From this we observe:
- The Objective-C class
MyClassbecomes a structMyClass_IMPL. - The inheritance
@interface MyClass : NSObjectis reflected astypedef struct objc_object MyClass;. - Every Objective-C method has two hidden parameters:
self(a pointer to the receiver) and_cmd(the selector). That's why we can useselfinside methods, though these parameters are not explicitly visible. - In getters and setters, values are accessed and stored by offsetting the base pointer with the member variable's offset, and then casting back to the appropriate type (e.g.,
(NSString **)). The process is: get the address of the member variable, then read the value stored at that address.
The implementation of struct NSObject_IMPL is found by searching the code:
#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif
struct NSObject_IMPL {
Class isa;
};
This struct contains only a Class isa field.
Further checking Class:
typedef struct objc_class *Class;
Class is just a pointer to a objc_class struct. We'll discuss objc_class in the class structure section.
Compiling reveals:
- The class
TestPersoncompiles toTestPerson_IMPLstruct. - The function
_I_TestPerson_nameis the getter, with default parametersselfand_cmd. - The function
_I_TestPerson_setName_is the setter, withself,_cmd, and a parametername.
From the compiled structure, we conclude:
- An object is essentially an
objc_objectstruct. - The difference between a property and an instance variable: a property consists of an instance variable, a getter, and a setter.
Structure of an Instance Object
An instance object is a struct containing an isa pointer pointing to its class and the instance variables.
Structure of a Class
Now let's look at objc_class. Searching the objc4 source code:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // method cache
class_data_bits_t bits; // used to retrieve class_rw_t and flags
};
Here we see that a class is a struct containing superclass, cache, and bits. objc_class inherits from objc_object, meaning a class is itself an object. The isa pointer is inherited from objc_object, hence the comment // Class ISA;.
Further searching objc_object:
struct objc_object {
private:
isa_t isa;
public:
//...
};
We find an isa_t type property isa.
isa
Looking at isa_t:
#include "isa.h"
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#if ISA_HAS_INLINE_RC
bool isDeallocating() const {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif // ISA_HAS_INLINE_RC
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated) const;
Class getDecodedClass(bool authenticated) const;
};
And the bitfield definition:
# if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
// armv7k or arm64_32
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t indexcls : 15; \
uintptr_t magic : 4; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 7
#endif
In this definition, isa_t is a union with:
bits: an unsigned integer representing the entireisavalue.cls: a class pointer.- A bitfield structure containing multiple fields, each representing part of the
isapointer.
What is isa?
The isa pointer holds the memory address of the class object. Since a class object is unique globally, every object created from that class has an isa property storing the class object's address. Through isa, runtime can query the object's properties, methods, protocols, etc.
The Role of isa
- Class pointer: The main function is to point to the object's class. This allows the runtime to find the class definition and call appropriate methods.
- Flags: In modern Objective-C runtime,
isaalso contains extra flags to store metadata. For example,nonpointerindicates whetherisaincludes extra metadata,has_associndicates whether the object has associated objects,extra_rcstores the reference count, etc.
Before arm64, isa was just a pointer holding the memory address. After arm64, Apple optimized isa into a union structure using bitfields to store more information.
Note: Nowadays, isa is a struct that stores the class address. Its no longer a simple pointer; saying "isa points to" is not strictly accurate, but it helps understanding.
isa Initialization Flow

bits
In objc_class, we have:
class_data_bits_t bits;
bits is of type class_data_bits_t, whose source code:
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
return maybe_rw->ro();
} else {
return (class_ro_t *)maybe_rw;
}
}
This code handles class object data with three main methods:
data(): Returns the class data by maskingbitswithFAST_DATA_MASK.setData(class_rw_t *newData): Sets the class data after validation, using atomic operation.safe_ro(): Safely returns read-only data. If realized, it returns thero()fromclass_rw_t; otherwise, it returns the data directly asclass_ro_t*.
The important types are class_rw_t and class_ro_t.
class_rw_t
This struct stores read-write data for the class, including instance method lists, protocol lists, property lists, etc. When you add methods or protocols to a class dynamically via runtime, these changes are stored in class_rw_t.

class_ro_t
This struct stores read-only data for the class, such as the class name, superclass, instance size, etc. This information is determined at class creation and cannot be changed at runtime, so it resides in class_ro_t.

cache_t
cache_t is a struct that stores the method cache for the class. Each class object has a cache_t member that caches recently used methods. When a message is sent to an object, the runtime first checks this cache. If found, the cached method is called directly, improving performance. If not found, the runtime searches the class's method list and adds the found method to the cache.
Class Inheritance
Suppose we have three classes: BaseClass, Sub1Class, Sub2Class; where Sub2Class inherits from Sub1Class, and Sub1Class inherits from BaseClass.
@interface BaseClass : NSObject
@end
@interface Sub1Class : BaseClass
@property (nonatomic, assign) int age;
@end
@interface Sub2Class : Sub1Class
@property (nonatomic, strong) NSString *name;
@end
Using clang -rewrite-objc main.m to convert to C++:
struct NSObject_IMPL {
Class isa;
};
struct BaseClass_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
struct Sub1Class_IMPL {
struct BaseClass_IMPL BaseClass_IVARS;
};
struct Sub2Class_IMPL {
struct Sub1Class_IMPL Sub1Class_IVARS;
};
Each subclass's struct contains the parent struct. Therefore, Objective-C inheritance is implemented using struct nesting at the underlying level.
Pointer Memory Translation
Pointer memory translation involves manipulating the pointer value to move the memory location pointed to. In Objective-C runtime, this technique is widely used to access instance variables, method lists, etc.
Why Categories Cannot Add Instance Variables
Because categories are added at runtime, they can only operate on the class_rw_t structure, but class_rw_t does not have an entry for adding instance variables. Instance variables are stored in class_ro_t, which cannot be modified. Hence, categories cannot add instance variables.
Relationship Among Object, Class, Superclass, and Metaclass
-
Instance
- Each instance is an instance of some class, containing the class's instance variables and methods.
- At runtime, an instance's
isapointer points to its class.
-
Class
- A class is the blueprint for objects, defining instance variables and methods.
- A class itself is an object, an instance of the
Classtype. - Each class has an
isapointer pointing to its metaclass.
-
Superclass
- A class can inherit from enother class, called its superclass.
- Inheritance allows subclasses to use the superclass's instance variables and methods, and also add new ones or override methods.
- Each class has a
super_classpointer pointing to its superclass.
-
Metaclass
- A metaclass is the class of a class, defining the behavior and methods of class objects.
- Each metaclass is also a class and has its own
isapointer pointing to the root metaclass. - A metaclass also has a
super_classpointer pointing to the metaclass of its superclass.
Metaclass Definition
The principle is that when an Objective-C object sends a message, the runtime follows the object's
isapointer to get the class. This class contains all instance methods applicable to instances and a pointer to its superclass to find inherited methods. The runtime checks the class and its superclass method lists to find the method corresponding to the message. The compiler converts the message into a call to the message functionobjc_msgSend.
As shown in the diagram: Every instance's isa points to its class; every class's isa points to its metaclass; the superclass's metaclass is also the parent of the subclass's metaclass; the root metaclass's isa points to itself, and its superclass is the root class (e.g., NSObject metaclass's superclass is NSObject class).

From the Message Sending Perspective:
- When you send a message to an instance, the message looks for the method list of that instance's class.
- When you send a message to a class, the message looks for the method list of that class's metaclass.
Class Object
All Objective-C objects can be categorized into three types: instance objects, class objects, and metaclass objects. Most objects we create inherit from NSObject and are instance objects. Instance objects point to class objects via isa. Class objects point to metaclass objects via isa. Both class objects and metaclass objects have the same structure, derived from objc_class.
The class Method
The class method is a special method. Its main purpose is to return the class object of the class that invoked the method. The class object is a special object containing metadata such as the class name, superclass, instance variables (ivars), etc.
Some important points:
Class objectClass = [[NSObject class] class];still returns the class object, not the metaclass object.- Both
- (Class)classand+ (Class)classreturn the class object.
Explanation:
Class objectClass = [[NSObject class] class];first gets the NSObject class object, then callsclassagain on it. In theory, this should return the metaclass. However, Objective-C is designed so that when you callclasson a class object, it returns the class object itself, not the metaclass. Both the instance method-classand the class method+classreturn the class object.
- Although metaclass objects and class objects have the same memory structure, their purposes differ.
Class objects contain metadata for instances: ivar layout, property info, instance method list, etc. These are used when creating instances. Metaclass objects mainly contain class method information. When a class method is called, that information is used. Other parts like ivar layout and property info are usually empty.
// Instance objects
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
// Class objects (all point to the same NSObject class)
Class class1 = [obj1 class];
Class class2 = [obj2 class];
Class class3 = [NSObject class];
Class class4 = object_getClass(obj1);
Class class5 = object_getClass(obj2);
// Metaclass objects
Class metaClass1 = object_getClass([NSObject class]);
Class metaClass2 = [[NSObject class] class];
// Check if it's a metaclass
NSLog(@"instance - %p %p", obj1, obj2);
NSLog(@"class - %p %p %p %p %p %d", class1, class2, class3, class4, class5, class_isMetaClass(class3));
NSLog(@"metaClass - %p %p %d", metaClass1, metaClass2, class_isMetaClass(metaClass1));
/* Output:
instance - 0x100511920 0x10050e840
class - 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0
metaClass - 0x7fff91da20f0 0x7fff91da2118 1
*/
No matter how many times you call class, you still get the class object.