1. Review

In previous posts, we have focused on class loading, including the low-level exploration of classification loading. This time, we will look at class extension and associated objects.

Loading iOS Low-level Exploration and the like (3): attachCategories analysis

2. The extension

2.1 What are classification and extension

First let’s look at what is classification and extension

category: Category/classification

  • Used specifically to add new methods to a class
  • You cannot add a member attribute to a class. If you add a member variable, you cannot fetch it
  • Pay attention to: Yes, actuallyruntimeAdd attributes to the category
  • Classification using@propertyIf you define variables, only variables will be generatedgetter,setterMethod declaration, cannot generate method implementation and underlineMember variables.

extension: class extensions

  • It’s kind of a special category, it’s also calledAnonymous classification
  • You can add to a classMember attribute, but isPrivate variables
  • You can add methods to classes, againPrivate methods

We are already familiar with the categories, so we don’t need to talk about them too much

2.2 extensions

Class extension, we usually use is very much, as follows

what ? What, is this an extension? Use every day unexpectedly do not know!

Yes, this is the extension, usually used a lot, but a lot of people don’t know.

Note: The class extension should come after the declaration and before the implementation, otherwise an error will be reported.

Add attributes and methods to extensions, and let’s take a look at what low-level C++ looks like.

As you can see from the underlying C++ code, properties of class extensions are added to the list of member variables, and methods are added to methods.

Thought: Does an extension affect the loading of the main class as well as the classification?

  • Create a separate extension file

  • LGPerson.mThe import#import "LGPerson+Ext.h"Header file that implements the methods inside the extension
- (void)ext_sayHello {
	NSLog(@"%s",__func__);
}
+ (void)ext_classMehod{
	NSLog(@"%s",__func__);
}
Copy the code
  • Breakpoints inobjcThe source code insiderealizeClassWithoutSwift
  • lldbprintroMethods list
(lldb) p ro.baseMethods()
(method_list_t *) $0 = 0x0000000100004190
  Fix-it applied, fixed expression was: 
    ro->baseMethods()
(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 "@ @ 0:8 16"
  imp = 0x0000000100003930 (ObjcBuild`-[LGPerson name])
}
(lldb) 
Copy the code

As you can see from the mode information, $1.get(5).big() prints the extended ext_sayHello method, which proves that the extended information of the class is also loaded as part of the class.

3. Associated objects

It is not possible to add member variables directly in a category, but we can add them indirectly, which involves the knowledge of the associated object.

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

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

// Remove the associated object
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object, /*deallocating*/false); }}Copy the code
  • Objc_getAssociatedObject Gets the associated object
  • Objc_setAssociatedObject Sets the associated object
  • Objc_removeAssociatedObjects Removes associated objects

3.1 Setting process

Take a look at the objc_setAssociatedObject method, which calls the _object_set_associative_Reference method

  • **_object_set_associative_reference阿鲁纳恰尔邦
void
_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.
    association.acquireValue();

    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) {
                association.swap(result.first->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()) { association.swap(it->second); refs.erase(it);if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // 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)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}
Copy the code

The four main parameters are:

  • objc: The object to be associated, that is, to whom the associated attribute is added
  • key: Identifier for easy search next time
  • value: The value to be stored
  • policy: Association policy

The DisguisedPtr method is a packaging strategy, just like express, packed into parcels

class DisguisedPtr {
    uintptr_t value;

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

    static T* undisguise(uintptr_t val) {
        return (T*)-val;
    }
Copy the code

We’re processing the PTR, we’re processing the value, we’re wrapping the object, we’re wrapping it into a unified data structure.

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

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

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

    static voidinit() { _mapStorage.init(); }}; AssociationsManager::Storage AssociationsManager::_mapStorage; }Copy the code
  • Many people are seeing it for the first timeAssociationsManagerMust have thought it was.The singleton“, but this is not a singleton, but passesThe constructor is locked,The destructor unlocksTo achieve thread safety.
  • AssociationsManagerIt’s just for callingAssociationsHashMapButAssociationsHashMapIs a singleton as it passes_mapStorage.get()To obtain,_mapStorageIs a global static variable that is unique wherever it is placed.

Create a new category and write several properties and methods in it

@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;

@end


- (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");
}
Copy the code

Assign the property, let’s see the breakpoint

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

You can see the associations structure and the refs_result structure, which may not be clear, so take a look at LLDB

What? What the hell! Refs_result what is that?

Pretty boy, don’t panic, calm down, please look at the following value

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

Although refs_result is very long, very creepy, the first so long is just the type, equivalent to NSObject, LGPerson, but the real content is only a few, and here only used second, the rest don’t care, this wave is not very comfortable, ha ha 😁

Refs_result is from associ.try_emplace (Overlap, ObjectAssociationMap{})

 auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
Copy the code
  • *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),
             true);
  }
Copy the code

So we’re going to create a new bucket, BucketT, where the key is the address of LGPerson. The first step is to go into LookupBucketFor and look for a bucket, and see two implementations, because in try_emplace BucketT has no const, so go to the following implementation. The following implementation calls the above implementation.

Here we have two methods with the same name, one of which takes a different argument, and here we call the above method, passing in an address, which is basically a pointer. I’ve also marked it in the picture.

  • Get the hash index
  • Start an endless loopbucket
  • The matching
  • If you don’t find it, hash it again

When I first came in, the LookupBucketFor(Key, TheBucket) couldn’t be found, so I went below.

This is where you insert an empty bucket, and you double the capacity by three quarters.

To be continued… More to come

🌹 just like it 👍🌹

🌹 feel learned, can come a wave, collect + concern, comment + forward, lest you can’t find me next 😁🌹

🌹 welcome everyone to leave a message to exchange, criticize and correct, learn from each other 😁, improve self 🌹