preface

If you want to be an iOS developer, you have to read source code. The following is a small series that the author combed in OC source code exploration — Class and Object, welcome you to read and correct, but also hope to help you.

  1. Object creation for OC source analysis
  2. OC source code analysis of ISA
  3. OC source code analysis and such structural interpretation
  4. Caching principle of OC source analysis method
  5. OC source code analysis method search principle
  6. Analysis and forwarding principle of OC source code analysis method

1. Class structure

If you’ve ever developed an application in Objective-C (OC), you’re no stranger to NSObject. There are two NSObject in OC, one is known as the NSObject class, and the other is the NSObject protocol. The NSObject protocol is similar to the interfaces of other object-oriented languages (such as Java and C++). The NSObject protocol defines some properties and methods but does not implement them. The NSObject class follows the NSObject protocol, so the NSObject class implements these methods.

We’ve been dealing with NSObject a lot, but we don’t know everything about it, so TODAY I’m going to take you through the structure of NSObject.

Note:

  • All the source code I used for this article is based on Apple open sourceObjc4-756.2 - the source code, will be attached at the end of the articlegithubThe address.
  • This paper adoptsx86_64CPU architecture, andarm64The difference is not big, the place that has difference will indicate.

1.1 Understanding the nature of classes

Let’s start with the definition of the NSObject class

OBJC_AVAILABLE(10.0.2.0.9.0.1.0.2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
Copy the code

You can see that NSObject has an ISA member variable of type Class, so pay attention here.

Next, compile main.m with Clang, print the.cpp file, and look at the underlying definition of the NSObject class

clang -rewrite-objc main.m -o main.cpp
Copy the code

Open the main. CPP file and find NSObject

#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif

struct NSObject_IMPL {
	Class isa;
};
Copy the code

Discovering that the NSObject class is essentially an objc_Object structure with an NSObject_IMPL structure defined (IMPL stands for implementation), There are isa member variables of the NSObject class (corresponding to isa member variables in the NSObject class definition at OC).

At this point, I’m particularly curious about what our own class will look like when compiled by Clang. Let’s take a look

@interface Person : NSObject

@property (nonatomic) NSInteger age;

- (void)run;

@end

@implementation Person

- (void)run {
    NSLog(@"I am running.");
}

@end
Copy the code

A simple Person class with an age property and a run method, which is compiled

#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif

extern "C" unsigned long OBJC_IVAR_$_Person$_age;
struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSInteger _age;
};

static void _I_Person_run(Person * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_mc_9fhhprrj4k92vxzqm3g127z40000gn_T_main_09fc70_mi_0);
}

static NSInteger _I_Person_age(Person * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_Person$_age)); }
static void _I_Person_setAge_(Person * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_Person$_age)) = age; }
Copy the code

So, the Person class is essentially an objC_Object structure type, and the only thing that shows inheritance from NSObject is, The Person_IMPL structure contains a struct NSObject_IMPL NSObject_IVARS member variable — that is, all classes that inherit from NSObject have an ISA member variable of type Class.

1.2 objc_objectstructure

If you are familiar with ISA, or have read my OC source code analysis isa article, objc_Object must be familiar.

Here directly on the source code

struct objc_object {
private:
    isa_tisa; .// Some functions
};

union isa_t {
    isa_t() {}isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
Copy the code
  • aboutisa_tAnalysis please stampOC source code analysis of ISAIt has been very detailed, so I will not repeat it here.
  • objc_objectThere are about fifty methods inside the structure, which can be roughly divided into the following categories
    • A little bit aboutisaIs a function of, e.ginitIsa(),getIsa(),changeIsa()Etc.
    • Some weakly referenced functions such asisWeaklyReferenced(),setWeaklyReferenced_nolock()Etc.
    • Some memory management functions such asretain(),release(),autorelease()Etc.
    • Two associated object functions, respectivelyhasAssociatedObjects()andsetHasAssociatedObjects

1.3 ClassIntroduction of the structure

Also first on the source code

typedef struct objc_class *Class;
typedef struct objc_object *id;

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags.// Some functions
};
Copy the code

Class is a pointer variable of type objC_class, inherited from objC_Object. That is, Class has four member variables that are stored in memory in order:

  1. isaIs a type:isa_t.A 64 - bitThe lower length is 8 bytes, which is skipped because of the analysis in the previous post.
  2. superclassIs a type:Class, indicating the inheritance relationship, pointing to the parent of the current class, also 8 bytes;
  3. cacheIs a type:cache_tRepresents cache, which is used to cache called methods and speed up method calls. Its specific structure is as follows
struct cache_t {
    struct bucket_t* _buckets;  // 64-bit is 8 bytes
    mask_t _mask;               // 64-bit is 4 bytes
    mask_t _occupied;           // 64-bit is 4 bytes.// Some functions
};

#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

typedef unsigned int uint32_t;
Copy the code

As you can see, cache is 16 bytes long.

Cache is more important, about its analysis can poke OC source analysis method of cache principle.

  1. bitsIs a type:class_data_bits_tIs used to store the data of a class (information about the methods, attributes, protocols that the class follows, etc.), and is structured as follows
struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;     // unsigned long.// Some functions
};
Copy the code

Its length is also 8 bytes. As the bits member variable is described in the objc_object structure, it is essentially class_rw_T * with the custom RR /alloc flag, that is, the most important is class_rw_t — which I’ll focus on next.

2. class_rw_t & class_ro_tAnalysis of the

OC attributes, methods, and protocols are stored in class_rw_t.

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;
    
    method_array_t methods;         // List of methods
    property_array_t properties;    // Attribute list
    protocol_array_t protocols;     // Protocol list

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif.// Some functions
};

#if__ARM_ARCH_7K__ >= 2 || (__arm64__ && ! __LP64__)
#   define SUPPORT_INDEXED_ISA 1    // armv7k or arm64_32
#else
#   define SUPPORT_INDEXED_ISA 0
#endif
Copy the code

Ro is a pointer to the class_ro_t structure. Ro is a pointer to the class_ro_t structure. Ro is a pointer to the class_ro_t structure, and ro is a pointer to the class_ro_t structure.

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;     // List of methods
    protocol_list_t * baseProtocols;    // Protocol list
    const ivar_list_t * ivars;          // List of member variables

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;    // Attribute list

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0]; .// Some functions
};
Copy the code

Description:

Rw stands for readwrite, while ro stands for readonly.

2.1 getclass_rw_t

To obtain the address of the class_rw_t pointer, we need to know the address of the bits pointer of objc_class. (isa + superclass + cache = 32 bytes) (isa + superclass + cache = 32 bytes)

How to get the class_rw_t pointer

// in the objc_class structure
class_rw_t *data() { 
    return bits.data();     // bits is class_datA_bits_t
}

// in the class_data_bits_t structure
class_rw_t* data() {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}

/ / 64
#define FAST_DATA_MASK          0x00007ffffffffff8UL
Copy the code

In 64-bit, the class_rw_t pointer address is in the [3, 46] data segment, so we can also calculate the class_rw_T pointer address with bits & FAST_DATA_MASK.

Next, I’ll use an example to verify that class_rw_t and class_ro_t store class information

2.2 Preparations

Add attributes, methods, and protocols to the Person class as follows

@protocol PersonProtocol <NSObject>

- (void)walk;

@end

@interface Person : NSObject <PersonProtocol> {
    NSInteger _gender;
}

@property (nonatomic) NSString *name;
@property (nonatomic) NSInteger age;

+ (void)printMyClassName;
- (void)run;

@end

@implementation Person

+ (void)printMyClassName {
    NSLog(@"my class name is Person");
}

- (void)run {
    NSLog(@"I am running.");
}

- (void)walk {
    NSLog(@"I am walking.");
}

@end
Copy the code

Then hit a break point in the right place

All right, we’re done. Let’s start the verification

2.3 class_rw_tThe validation process

  1. printPersonclass
(lldb) x/5gx pcls
0x100002820: 0x001d8001000027f9 0x0000000100b39140
0x100002830: 0x00000001003dc250 0x0000000000000000
0x100002840: 0x0000000102237404
Copy the code

Description:

  • PersonThe class header address is0x100002820And, therefore,0x100002840Is itbitsThe address (32 bytes is0x20.0x100002840 = 0x100002820 + 0x20),bitsThe content is0x0000000102237404
  • 0x001d8001000027f9isPersonOf the classisaAddress, point toThe Person metaclass
  • 0x0000000100b39140isPersonOf the classsuperclassThat’s the addressNSObjectClass the first address
  • 0x00000001003dc250 0x0000000000000000It isPersonOf the classcachePeriod of
  1. printclass_rw_t
// bits & FAST_DATA_MASK
(lldb) p (class_rw_t(*)0x0000000102237404 & 0x00007ffffffffff8)
(class_rw_t *) $1 = 0x0000000102237400
(lldb) p *$1
(class_rw_t) $2 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002788
  methods = {
    list_array_tt<method_t.method_list_t> = {= {list = 0x0000000100002608
        arrayAndFlag = 4294977032
      }
    }
  }
  properties = {
    list_array_tt<property_t.property_list_t> = {= {list = 0x0000000100002720
        arrayAndFlag = 4294977312
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long.protocol_list_t> = {= {list = 0x00000001000025a8
        arrayAndFlag = 4294976936
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000000000000
}
Copy the code

Take a look at the key member variables of class_rw_t:

  • roThe address is0x0000000100002788
  • methodsthelistThe address is0x0000000100002608
  • propertiesthelistThe address is0x0000000100002720
  • protocolsthelistThe address is0x0000000100002608
  1. validationmethods

Currently, the Person class has at least six instance methods: Run, walk, getters and setters for name and age, and one class method, printMyClassName, for a total of seven methods.

(lldb) p (method_list_t *)0x0000000100002608    // RW methods list address
(method_list_t *) $7 = 0x0000000100002608
(lldb) p *$7
(method_list_t) $8 = {
  entsize_list_tt<method_t.method_list_t.3> = {
    entsizeAndFlags = 26
    count = 7
    first = {
      name = "walk"
      types = 0x0000000100001e96 "v16@0:8"
      imp = 0x0000000100001530 (CCTest`-[Person walk] at main.m:45)}}}Copy the code

There are exactly seven methods, so let’s see what they are. (Since method_list_t inherits from entsize_list_tt, we can print one by one from entsize_list_tt’s get() function.)

(lldb) p $8.get(0)
(method_t) $9 = {
  name = "walk"
  types = 0x0000000100001e96 "v16@0:8"
  imp = 0x0000000100001530 (CCTest`-[Person walk] at main.m:45)
}
(lldb) p $8.get(1)
(method_t) $10 = {
  name = ".cxx_destruct"
  types = 0x0000000100001e96 "v16@0:8"
  imp = 0x0000000100001600 (CCTest`-[Person .cxx_destruct] at main.m:35)
}
(lldb) p $8.get(2)
(method_t) $11 = {
  name = "name"
  types = 0x0000000100001eb1 "@ @ 0:8 16"
  imp = 0x0000000100001560 (CCTest`-[Person name] at main.m:27)
}
(lldb) p $8.get(3)
(method_t) $12 = {
  name = "setName:"
  types = 0x0000000100001f4b "v24@0:8@16"
  imp = 0x0000000100001580 (CCTest`-[Person setName:] at main.m:27)
}
(lldb) p $8.get(4)
(method_t) $13 = {
  name = "age"
  types = 0x0000000100001f56 "q16@0:8"
  imp = 0x00000001000015c0 (CCTest`-[Person age] at main.m:28)
}
(lldb) p $8.get(5)
(method_t) $14 = {
  name = "run"
  types = 0x0000000100001e96 "v16@0:8"
  imp = 0x0000000100001500 (CCTest`-[Person run] at main.m:41)
}
(lldb) p $8.get(6)
(method_t) $15 = {
  name = "setAge:"
  types = 0x0000000100001f5e "v24@0:8q16"
  imp = 0x00000001000015e0 (CCTest`-[Person setAge:] at main.m:28)}Copy the code

Obviously, class_rw_t’s methods do contain all instance methods of the Person class, with the addition of the.cxx_destruct method. The.cxx_destruct method was originally used for C++ object destruct, and ARC uses this method to insert code for automatic memory release.

Think: Where is the class method printMyClassName?

  1. validationproperties

Similarly, the Person class has at least two attributes, name and age

(lldb) p (property_list_t *)0x0000000100002720  // RW properties list address
(property_list_t *) $18 = 0x0000000100002720
(lldb) p *$18
(property_list_t) $19 = {
  entsize_list_tt<property_t.property_list_t.0> = {
    entsizeAndFlags = 16
    count = 6
    first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
  }
}
(lldb) p $19.get(0)
(property_t) $20 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $19.get(1)
(property_t) $21 = (name = "age", attributes = "Tq,N,V_age")
(lldb) p $19.get(2)
(property_t) $22 = (name = "hash", attributes = "TQ,R")
(lldb) p $19.get(3)
(property_t) $23 = (name = "superclass", attributes = "T#,R")
(lldb) p $19.get(4)
(property_t) $24 = (name = "description", attributes = "T@\"NSString\",R,C")
(lldb) p $19.get(5)
(property_t) $25 = (name = "debugDescription", attributes = "T@\"NSString\",R,C")
Copy the code

Obviously name and age are stored in properties.

Superfluous attributes will not be described.

  1. validationprotocols

Before verifying, check the protocol_list_t structure, which does not inherit from entsize_list_tt

struct protocol_list_t {
    // count is 64-bit by accident. 
    uintptr_t count;
    protocol_ref_t list[0]; // variable-size.// Some functions
}
Copy the code

Notice the variable-size part of the comment, and it looks promising

typedef uintptr_t protocol_ref_t;  // protocol_t *, but unremapped

struct protocol_t : objc_object {
    const char *mangledName;
    struct protocol_list_t *protocols;
    method_list_t *instanceMethods;
    method_list_t *classMethods;
    method_list_t *optionalInstanceMethods;
    method_list_t *optionalClassMethods;
    property_list_t *instanceProperties;
    uint32_t size;   // sizeof(protocol_t)
    uint32_t flags;
    // Fields below this point are not always present on disk.
    const char **_extendedMethodTypes;
    const char *_demangledName;
    property_list_t *_classProperties;

    const char *demangledName(a); .// Some functions
};
Copy the code

Protocol_ref_t does not map to PROTOCOL_t *, but it should be considered strong, so experiment (this time finding the PersonProtocol protocol).

(lldb) p (protocol_list_t *)0x00000001000025a8  // RW protocols' list address
(protocol_list_t *) $26 = 0x00000001000025a8
(lldb) p *$26
(protocol_list_t) $27 = (count = 1.list = protocol_ref_t [] @ 0x00007fb5decb30f8)

(lldb) p (protocol_t*) $26->list[0]
(protocol_t *) $32 = 0x00000001000028a8
(lldb) p *$32
(protocol_t) $33 = {
  objc_object = {
    isa = {
      cls = Protocol
      bits = 4306735304
       = {
        nonpointer = 0
        has_assoc = 0
        has_cxx_dtor = 0
        shiftcls = 538341913
        magic = 0
        weakly_referenced = 0
        deallocating = 0
        has_sidetable_rc = 0
        extra_rc = 0
      }
    }
  }
  mangledName = 0x0000000100001d16 "PersonProtocol" // there it is!!
  protocols = 0x0000000100002568
  instanceMethods = 0x0000000100002580
  classMethods = 0x0000000000000000
  optionalInstanceMethods = 0x0000000000000000
  optionalClassMethods = 0x0000000000000000
  instanceProperties = 0x0000000000000000
  size = 96
  flags = 0
  _extendedMethodTypes = 0x00000001000025a0
  _demangledName = 0x0000000000000000
  _classProperties = 0x0000000000000000
}
Copy the code

Finally, we verify that the Class_rw_T protocols contain the PersonProtocol that Person follows.

At this point, class_rw_t does store instance methods, attributes, and protocols for the class.

2.4 class_ro_tThe validation process

And now I’m left with ro

  1. printclass_ro_t
(lldb) p $1->ro
(const class_ro_t *) $38 = 0x0000000100002788
(lldb) p *$38
(const class_ro_t) $39 = {
  flags = 388
  instanceStart = 8
  instanceSize = 32
  reserved = 0
  ivarLayout = 0x0000000100001d2e "\x11"
  name = 0x0000000100001d0f "Person"
  baseMethodList = 0x0000000100002608
  baseProtocols = 0x00000001000025a8
  ivars = 0x00000001000026b8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100002720
  _swiftMetadataInitializer_NEVER_USE = {}
}
Copy the code

Did you find anything?! The addresses of class_ro_t’s methods, attributes, and protocols are the same as those of class_rw_t. Since class_ro_t refers to the same memory space, it is obvious that class_ro_t also stores instance methods, attributes, and protocols of the Person class.

Instead of class_rw_t, class_ro_t has an ivars list that should hold member variables of the Person class.

  1. validationivars

The member variables of the Person class are _gender, _NAME, and _age

Fortunately, ivar_list_t inherits from entsize_list_tt, so get() is available again.

(lldb) p $39.ivars
(const ivar_list_t *const) $40 = 0x00000001000026b8
(lldb) p *$40
(const ivar_list_t) $41 = {
  entsize_list_tt<ivar_t.ivar_list_t.0> = {
    entsizeAndFlags = 32
    count = 3
    first = {
      offset = 0x00000001000027e0
      name = 0x0000000100001e83 "_gender"
      type = 0x0000000100001f69 "q"
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $41.get(0)
(ivar_t) $42 = {
  offset = 0x00000001000027e0
  name = 0x0000000100001e83 "_gender"
  type = 0x0000000100001f69 "q"
  alignment_raw = 3
  size = 8
}
(lldb) p $41.get(1)
(ivar_t) $43 = {
  offset = 0x00000001000027e8
  name = 0x0000000100001e8b "_name"
  type = 0x0000000100001f6b "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $41.get(2)
(ivar_t) $44 = {
  offset = 0x00000001000027f0
  name = 0x0000000100001e91 "_age"
  type = 0x0000000100001f69 "q"
  alignment_raw = 3
  size = 8
}
Copy the code

As expected, class_ro_t does store member variables of the Person class.

2.5 rwandroThe link

Why are the methods, attributes and protocol addresses of class_rw_t and class_ro_t the same? A clue is found in the safe_ro() function in the class_data_bits_t structure

const class_ro_t *safe_ro(a) {
    class_rw_t *maybe_rw = data();
    if (maybe_rw->flags & RW_REALIZED) {
        // maybe_rw is rw
        return maybe_rw->ro;
    } else {
        // maybe_rw is actually ro
        return (class_ro_t*)maybe_rw; }}Copy the code

So, rw doesn’t have to be Rw, it could be ro. In fact, at compile time, the class’s class_datA_bits_t *bits pointer points to class_ro_t *, The OC runtime then calls realizeClassWithoutSwift() (apple’s objC4-756.2 source code is realizeClassWithoutSwift(), the previous version was the realizeClass() method), The main thing this method does is initialize the RW with the ro determined at compile time:

ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    rw = cls->data();
    ro = cls->data()->ro;
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // Usually go here
    // Normal class. Allocate writeable class data.
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);   // Allocate memory for rW
    rw->ro = ro;    // Set rw's ro
    rw->flags = RW_REALIZED|RW_REALIZING;   / / set the flags
    cls->setData(rw);   // Set the correct RW for CLS}...// Initialize other fields of rW, update superclass, meta Class

// Attach categories
methodizeClass(cls);
Copy the code

MethodizeClass () is also called at the end of the code, with the source code below

static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    autoro = rw->ro; .// Prints information

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list.1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list.1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    if (cls->isRootMetaclass()) {
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/); .// Prints information
    if (cats) free(cats); .// Prints information
}
Copy the code

In this method, the classes themselves implement methods (including classes), properties, and protocols that follow into the list of methods, Properties, and Protocols.

This perfectly explains why rW and RO have the same methods, properties, and protocols at run time.

2.6 rwandroDifferences at run time

The validation so far is based on the existing structure of the Person class, which is determined at compile time and does not highlight the difference between class_rw_t and class_ro_t. Next, I’ll use the Runtime API to dynamically add a fly() method to Person at runtime and try again.

  1. Add methods

The specific code is as follows:

void fly(id obj, SEL sel) {
    NSLog(@"I am flying");
}

class_addMethod([Person class], NSSelectorFromString(@"fly"), (IMP)fly, "v@:");
Copy the code

Add a print method to print the class’s methods

void printMethods(Class cls) {
    if (cls == nil) {
        return ;
    }
    CCNSLog(@"------------ print %@ methods ------------", NSStringFromClass(cls));
    uint32_t count;
    Method *methods = class_copyMethodList(cls, &count);
    for (uint32_t i = 0; i < count; i++) {
        Method method = methods[i];
        CCNSLog(@"Name: %@ -- type: %s", NSStringFromSelector(method_getName(method)), method_getTypeEncoding(method)); }}Copy the code

Run it to see the effect and find that it is successfully added, as shown in the figure

  1. The validation process

First print class_rw_t, that is

There are class_ro_t

After comparison, it is found that the addresses of the attributes and protocol Pointers are not changed, but the addresses of method Pointers are different. Since class_rw_t is initialized at runtime and class_ro_t is determined at compile time, we can assume that the new fly method is stored on the methods pointer to class_rw_t. The baseMethodList pointer to class_ro_t has not changed since compile time.

To continue the validation, look first at the list of class_ro_t methods

OK, all the methods identified at compile time are there, and there is no fly method, which means that the list of class_ro_t methods is basically unchanged at runtime.

The class_ro_t property list, member variable list, and protocol are not changed at runtime. Those of you who are interested can try it out for yourself.

Then look at the list of class_rw_t methods

[root@class_rw_t] [root@class_rw_t] No way, here temporarily leave a pit, the author also do not know the reason.

3. Summary

3.1 Structure summary of class

We have learned about the structure of the class:

  • Classes are essentiallyobjc_objectStructures, that is, classes are objects, that is, everything is objects.
  • Each class contains oneClassType of a member variableisa.Classisobjc_classA pointer variable of structure type with four internal member variables, i.e
    • isaIs a type:isa_t, please stamp for detailsOC source code analysis of ISA
    • superclassIs a type:Class, indicating inheritance, pointing to the parent class of a class
    • cacheIs a type:cache_tRepresents cache, used to cache Pointers andvtableTo speed up method calls
    • bitsIs a type:class_data_bits_t, is used to store the data of the class (information about the methods, attributes, protocols followed, etc.), and its length is inA 64 - bitIt’s 8 bytes on the CPU. It’s a pointer toclass_rw_t *

3.2 class_rw_tandclass_ro_tconclusion

  • class_ro_tIt stores information such as methods (including their classification), member variables, attributes, and protocols that a class determines at compile time, unchanged at run time. Compile time, classbitsThe pointer points to thetaclass_ro_tPointer (i.eclass_rw_t *Is, in fact,class_ro_t *).
    • Instance methods are stored in classes
    • Class methods are stored in metaclasses ([4.1] will prove)
  • inrealizeClassWithoutSwift()After execution,class_rw_tIs initialized, and stores the methods, properties, and protocols that the class follows. In fact,class_rw_tandclass_ro_tThe pointer to the method list (or property list, or protocol list) is the same for both.
  • Changes when attributes and methods are dynamically added to a class at runtimeclass_rw_tProperty list, method list pointer, butclass_ro_tThe corresponding property list and method list remain unchanged.

A pit to be solved: The pointer to the class_rw_t method list (or attribute list, protocol list) is changed by adding a method (or attribute list, protocol list) at runtime, but the new method (or attribute list, protocol list) is not found in class_rw_t method list (or attribute list, protocol list). This question has puzzled the author for a long time. Welcome students to leave comments and discuss in the comments section.

Added 4.

4.1 Storage location of class methods

(Person class method printMyClassName())

1. Get the Person metaclass
(lldb) x/4gx pcls
0x100002820: 0x001d8001000027f9 0x0000000100b39140
0x100002830: 0x00000001003dc250 0x0000000000000000
(lldb) p/x 0x001d8001000027f9 & 0x00007ffffffffff8
(long) $50 = 0x00000001000027f8
(lldb) po 0x00000001000027f8
Person  / / Person metaclass

// 2. Get bits of the Person metaclass
(lldb) x/5gx 0x00000001000027f8
0x1000027f8: 0x001d800100b390f1 0x0000000100b390f0
0x100002808: 0x0000000102237440 0x0000000100000003
0x100002818: 0x00000001022373a0 // Bits of the Person metaclass

Get the class_rw_t of the Person metaclass
(lldb) p (class_rw_t(*)0x00000001022373a0 & 0x00007ffffffffff8)
(class_rw_t *) $52 = 0x00000001022373a0

// 4. Verify the methods of the Person metaclass
(lldb) p $52->methods
(method_array_t) $55 = {
  list_array_tt<method_t.method_list_t> = {= {list = 0x0000000100002270
      arrayAndFlag = 4294976112
    }
  }
}
(lldb) p (method_list_t *)0x0000000100002270
(method_list_t *) $56 = 0x0000000100002270
(lldb) p *$56
(method_list_t) $57 = {
  entsize_list_tt<method_t.method_list_t.3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "printMyClassName" // Successfully found the Person class method
      types = 0x0000000100001e96 "v16@0:8"
      imp = 0x00000001000014d0 (CCTest`+[Person printMyClassName] at main.m:37)}}}Copy the code

Conclusion: Class methods are stored on the metaclass of the class, on the baseMethodList pointer of the class_ro_T metaclass (or on the methods pointer of class_rw_T)

The resources

Deep parsing the structure of methods in ObjC (by Draveness)

PS

  • The source code project has been placedgithubThe stamp, pleaseObjc4-756.2 – the source code
  • You can also download apple’s official ObjC4 source code to study.
  • Reprint please indicate the source! Thank you very much!