Loading of iOS underlying exploration: class association object AssociatedObject

Posted by JeremyTiki on Thu, 13 Jan 2022 20:34:20 +0100

1. Review

In the previous blogs, I mainly talked about class loading, including the underlying exploration of classification loading. This time, I will analyze the class extension and associated objects.

Loading of iOS underlying exploration (III): attachCategories analysis

2. Expansion

2.1 what is classification and extension

First, let's look at what is classification and extension

Category: category / classification

  • Specifically used to add new methods to classes
  • You cannot add a member attribute to a class, add a member variable, and cannot get it
  • Note: you can actually add attributes to the classification through runtime
  • Defining variables with @ property in the classification will only generate the declaration of getter and setter methods of variables, and cannot generate method implementation and underlined member variables.

Extension: class extension

  • It can be said to be a special classification, also known as anonymous classification
  • You can add member properties to a class, but they are private variables
  • You can add methods to classes, which are also private methods

We are already familiar with classification, so we don't need to repeat it here. Here is the extension extension

2.2 expansion

We usually use a lot of class extensions, as follows

what ? What, is this an extension? I don't know how to use it every day!
Yes, this is the extension. It is used a lot at ordinary times, but many people don't know.

Note: class extensions should be placed after the declaration and before the implementation, otherwise an error will be reported.

Add some attributes and methods to the extension. Let's see what the underlying C + + looks like.

It can be found from the underlying C + + code that the extended attributes of the class will be added to the member variable list, and the methods will also be in the house methods.

Think: will extension affect the loading of main classes like classification?

  • Create a separate extension file
  • LGPerson.m import #import "LGPerson+Ext.h" header file to implement the methods in the extension
- (void)ext_sayHello {
+ (void)ext_classMehod{
  • Breakpoints in objc source code realizeClassWithoutSwift
  • lldb print ro method list
(lldb) p ro.baseMethods()
(method_list_t *) $0 = 0x0000000100004190
  Fix-it applied, fixed expression was: 
(lldb) p *$0
(method_list_t) $1 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 8)
(lldb) p $1.get(0).big()
(method_t::big) $2 = {
  name = "saySomething"
  types = 0x0000000100003e09 "v16@0:8"
  imp = 0x00000001000039f0 (ObjcBuild`-[LGPerson(LGA) saySomething])
(lldb) p $1.get(1).big()
(method_t::big) $3 = {
  name = "cateA_instanceMethod1"
  types = 0x0000000100003e09 "v16@0:8"
  imp = 0x0000000100003a20 (ObjcBuild`-[LGPerson(LGA) cateA_instanceMethod1])
(lldb) p $1.get(2).big()
(method_t::big) $4 = {
  name = "cateA_instanceMethod2"
  types = 0x0000000100003e09 "v16@0:8"
  imp = 0x0000000100003a50 (ObjcBuild`-[LGPerson(LGA) cateA_instanceMethod2])
(lldb) p $1.get(3).big()
(method_t::big) $5 = {
  name = "saySomething"
  types = 0x0000000100003e09 "v16@0:8"
  imp = 0x00000001000038a0 (ObjcBuild`-[LGPerson saySomething])
(lldb) p $1.get(4).big()
(method_t::big) $6 = {
  name = "sayHello1"
  types = 0x0000000100003e09 "v16@0:8"
  imp = 0x00000001000038d0 (ObjcBuild`-[LGPerson sayHello1])
(lldb) p $1.get(5).big()
(method_t::big) $7 = {
  name = "ext_sayHello"
  types = 0x0000000100003e09 "v16@0:8"
  imp = 0x0000000100003900 (ObjcBuild`-[LGPerson ext_sayHello])
(lldb) p $1.get(6).big()
(method_t::big) $8 = {
  name = "name"
  types = 0x0000000100003de3 "@16@0:8"
  imp = 0x0000000100003930 (ObjcBuild`-[LGPerson name])

As you can see from the mode information, at $1 get(5). Big () prints the extended ext_sayHello method, which proves that the extension information of the class will also be loaded into the class as part of the class.

3. Associated object

Member variables cannot be added directly in the classification, but we can add them indirectly, which involves the knowledge of associated objects.

// Get associated object
objc_getAssociatedObject(id object, const void *key)
    return _object_get_associative_reference(object, key);

// Set associated objects
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    _object_set_associative_reference(object, key, value, policy);

// Remove associated objects
void objc_removeAssociatedObjects(id object) 
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object, /*deallocating*/false);
  • objc_getAssociatedObject gets the associated object
  • objc_setAssociatedObject sets the associated object
  • objc_removeAssociatedObjects removes associated objects

3.1 set value process

Let's take a look at objc_setAssociatedObject sets the associated object method, which calls_ object_set_associative_reference method

  • **_object_set_associative_reference**
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;

    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.

    bool isFirstAssociation = false;
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;

            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
        } else {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    if (refs.size() == 0) {


    // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation)

    // release the old value (outside of the lock).

The four main parameters are:

  • objc: the object to be associated, that is, to whom to add the associated attribute
  • key: identifier to facilitate the next search
  • Value: value to save
  • Policy: Association policy

The distinguishedptr method is a packaging strategy, like express delivery, which is packaged into packages

class DisguisedPtr {
    uintptr_t value;

    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;

    static T* undisguise(uintptr_t val) {
        return (T*)-val;

ptr is processed, that is, value is processed, that is, object is packaged into a unified data structure.

  • AssociationsManager
class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    AssociationsHashMap &get() {
        return _mapStorage.get();

    static void init() {

AssociationsManager::Storage AssociationsManager::_mapStorage;

  • Many people must think that the AssociationsManager is a singleton when they see it for the first time, but it is not a singleton. Instead, it is locked by the constructor and unlocked by the destructor to achieve thread safety.
  • AssociationsManager is only used to call AssociationsHashMap, but AssociationsHashMap is a singleton because it passes through_ mapStorage.get()_ Mapstorage is a global static variable that is unique anywhere.

Create a new category with several properties and methods

@interface LGPerson (LGA)

@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, copy) NSString *cate_age;

- (void)saySomething;

- (void)cateA_instanceMethod1;
- (void)cateA_instanceMethod2;

+ (void)cateA_classMethod1;
+ (void)cateA_classMethod2;


- (void)setCate_name:(NSString *)cate_name{

    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);

- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");

- (void)setCate_age:(NSString *)cate_age{
    objc_setAssociatedObject(self, "cate_age", cate_age, OBJC_ASSOCIATION_COPY_NONATOMIC);

- (NSString *)cate_age{
    return objc_getAssociatedObject(self, "cate_age");

Then assign a value to the attribute and have a look at the breakpoint

LGPerson * person = [LGPerson alloc];
 person.cate_name  = @"jp";
 person.cate_age   = @"20";
 [person saySomething];

You can see the structure of associations and refs_ The result structure may not be very clear. Let's lldb look again

what? What the hell! refs_result what the hell is this!

Pretty boy, don't panic. Calm down. Please take a look at the following values

if (refs_result.second) {
                /* it's the first association we make */
  isFirstAssociation = true;

Although refs_ The result is very long and abnormal. The length in the front is just a type, equivalent to NSObject and LGPerson. However, the real content is only the last few, and only second is used here. You don't have to care about the rest at all. Is this wave very comfortable, ha ha 😁

refs_result is from associations try_ Empty (distinguished, object association map {})

 auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
  • *try_emplace
template <typename... Ts>
  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    if (LookupBucketFor(Key, TheBucket))
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // Otherwise, insert the new element.
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),

A new bucket bucket T will be created here. The key here is the address of LGPerson.
First, you will enter LookupBucketFor to find that there is already a bucket. You can see two implementations because you are trying_ In the empty, bucket t does not have const, so the following implementation is followed. The following implementation calls the above implementation.

Here are two methods with the same name. One of the parameters is different. The following calls the above method and passes in an address. In other words, it is the transfer of a pointer. I also marked it in the picture.

  • Get hash subscript
  • Start dead cycle to find bucket
  • Matching processing
  • If you don't find it, hash it again

When I first came in, I couldn't find the lookup bucket for (key, the bucket), so I went down.

Here, an empty bucket is inserted, and the operation of 3 / 4 capacity expansion is doubled.

Unfinished to be continued.........
More content is constantly updated

🌹 Just like it 👍🌹

🌹 If you think you have learned, you can have a wave, collect + pay attention, comment + forward, so that you won't find me next time 😁🌹

🌹 Welcome to exchange messages, criticize and correct each other 😁, Self improvement 🌹

Topics: iOS