This is the seventh day of my participation in the August More text Challenge. For details, see:August is more challenging

Hi 👋

  • Wechat: RyukieW
  • 📦 Technical article archive
  • 🐙 making
My personal project Mine Elic endless sky ladder Dream of books
type The game financial
AppStore Elic Umemi

preface

We’ve explored everything about categories in depth, and adding associated objects to categories is common, but do you really know anything about associated objects? Do you know how it’s set up and released?

This article is based on objC4-818.2 source code

1. Associated objects

Since the attributes of the class do not produce associated member variables, we need to implement the set/get methods ourselves. As follows:

// .h

@property (nonatomic.copy) NSString *cateName;


// .m
- (void)setCateName:(NSString *)cateName {
    objc_setAssociatedObject(self."cateName", cateName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cateName {
    return  objc_getAssociatedObject(self."cateName");
}
Copy the code

So, what do objc_setAssociatedObject and objc_getAssociatedObject do?

Second, the objc_setAssociatedObject

_object_set_associative_reference is actually called

2.1 AssociationsHashMap

Through the breakpoint we found an AssociationsHashMap.

You can see that it is a globally static HashMap of type Storage. Used to hold the association relationships of all associated objects at runtime.

2.2 try_emplace

If the table does not have one, the key-value pair is inserted into the table.

Inserts key,value pair into the map if the key isn’t already in the map. The value is constructed in-place if the key is not in the map, otherwise it is not moved.

One set calls try_emplace twice

  • auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
    • Only used to check whether an associated object existskey ObjectAssociationMap{}It’s empty and you don’t do anything about it
  • auto result = refs.try_emplace(key, std::move(association));
    • Here are the real Settingsvalue

Trigger the set

Lookup whether the corresponding associated object already exists in the table by calling LookupBucketFor.

The first call to the set method

Because the associated value does not exist, the insert process is performed

The set method is called the second time

Associated value exists, update

2.3 LookupBucketFor

The algorithm is basically the same as the one used to find the method cache.

Objc_msgSend source code and real machine debugging interpretation

2.4 InsertIntoBucket

Insert table

template <typename KeyArg, typename. ValueArgs>BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, ValueArgs &&... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);

// first 就是 key
TheBucket->getFirst() = std::forward<KeyArg>(Key);

/ / the second is the Value: :new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...) ;return TheBucket;
}
Copy the code

Release the associated object

Consider: Is the object dealloc automatically cleaned up and managed?

3.1 dealloc

We start with Dealloc to find:

  • dealloc
    • _objc_rootDealloc
      • rootDealloc
        • If there are associated objects
        • object_dispose
          • objc_destructInstance
            • _object_remove_assocations
              • To release the associated object
void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};
    
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if(i ! = associations.end()) {
            refs.swap(i->second);

            // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
            bool didReInsert = false;
            if(! deallocating) {for (auto &ref: refs) {
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        i->second.insert(ref);
                        didReInsert = true; }}}if(! didReInsert) associations.erase(i); }}// Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // release everything (outside of the lock).
    for (auto &i: refs) {
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                laterRefs.append(&i.second);
        } else {
            i.second.releaseHeldValue();
        }
    }
    for (auto *later: laterRefs) {
        later->releaseHeldValue();
    }
}
Copy the code

Four,

The associated objects are actually stored in a global HashMap. The data structure of the original Class is not changed directly