Objc4 (git open source)

What is theCategory ?

Category is a language feature added after Object-C 2.0. The main purpose of Category is to add methods to existing classes.


What is theExtension ?

Extension is usually referred to as extension, extension, and anonymous classification. Extension looks a lot like an anonymous category, but extension and category are almost entirely different things. Unlike categories, extension can declare not only methods, but also attributes and member variables. Extension is generally used to declare private methods, private attributes, and private member variables.


CategoryExtensionWhat’s the difference?

Let’s answer this question from multiple perspectives.

  1. Form of expression:
  • Category is a.h and a.m.

  • Extension is a.h. (of course, it can also be associated with a. M class, which is written as @interface *** /… / @end without further ado.)

    Then again, when creating, select the corresponding type.

  1. Function mechanism
1 ️ ⃣ : the Extension
  • ExtensionIs part of a class that is inCompile timeAnd the header file@interfaceIn the implementation file@implementTogether to form a complete class,ExtensionWith the creation of the class came into being, but also with the extinction.
  • ExtensionYou can add instance variables.
  • ExtensionGenerally used to hide the private information of a class, it cannot provide extensions to a system class directly, but it can create subclasses of the system class and then add extensions.

An 🌰

p.objExtension = 28;
NSLog(@"%d",p.objExtension);
Copy the code

When used as above, a crash occurred.

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:'-[LBPerson setObjExtension:]: unrecognized selector sent to instance 0x6000008ed4e0'
Copy the code

Change NSObject to LBPerson and the result is fine.

2 ️ ⃣ : Category
  • Categories are determined at run time. At this point, the memory layout of the object is already determined, and adding instance variables would break the internal layout of the class, which would be disastrous in compiled languages.

  • Categories add categories to system classes.

  • Categories can add attributes, but they do not generate member variables and their corresponding getters and setters.

Again, let’s experiment: 🌰

// LBPerson+Category.h

#import "LBPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LBPerson (Category)
@property (nonatomic.assign) int ageCategory;
@end
Copy the code

Use:

LBPerson * p = [[LBPerson alloc] init];
p.ageCategory = 25;   // Error adding attribute to class
NSLog(@"%d",p.ageCategory);
Copy the code

Print result: Flash back.

-[LBPerson setAgeCategory:]: unrecognized selector sent to instance 0x6000021a72a0

Of course, we can solve this problem by setting up the associated object by the Runtime, as described in more detail below.


CategoryWhat’s the use?

  • Reduce the size of a single file
  • Organize different functions into different onesCategory
  • Feel free to load on demand
  • Declare private methods
  • theframework(when a subclass declares its parent class by reference, its private methods can be called)

Tips:

Please don’t mess around: Apple officially refuses to ship apps that use the system’s private apis, so even if you’ve learned how to call private methods, be careful when calling private methods of other classes and try to substitute them.


CategoryUnderlying Principle Analysis

1. Compile time

With all that said, it’s time to start looking at what it really is. Open the terminal /iterm2 to compile and convert to c++.

clang -rewrite-objc LBPerson+Category.m
Copy the code

Locate the compiled file, open it, and search for _category_t to find the structure definition.

struct _category_t {
	const char *name;  / / class name
	struct _class_t *cls;  // Class object with no value at compile time
	const struct _method_list_t *instance_methods; // List of instance methods
	const struct _method_list_t *class_methods; // List of class methods
	const struct _protocol_list_t *protocols;  // List of protocols implemented by classification
	const struct _prop_list_t *properties; // Category attribute list
};
Copy the code

Continue searching to find what this structure holds at compile time

static struct _category_t _OBJC_$_CATEGORY_LBPerson_$_Category __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"LBPerson".0.// &OBJC_CLASS_$_LBPerson,
	0.0.0,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LBPerson_$_Category,
};
Copy the code

A search for _PROP_LIST_LBPerson_ will find the attribute we defined. Of course, to summarize: defined methods, attributes, and so on are stored in the corresponding fields at compile time, and then stored in the generated Mach-O executable through the section identifier.

In the meantime, look further down

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
	&_OBJC_$_CATEGORY_LBPerson_$_Category};Copy the code

This means that when compiled, all categories in the project are stored in the __objc_catlist section of the __DATA section.

So that’s what compile-time classification does, the classification structure, the classification method, the declared properties, and the storage is done.

2. Run time

Want to see how categories are loaded and processed at run time. We need to know a concept first.

2.1 dyld

What is a dyld?

  • Dyld is Apple’s dynamic loader for loading images (note: image is not an image, but a mach-O binary file)

  • When the program starts, the system kernel loads dyLD first, and dyLD loads the various libraries that our APP depends on into memory, including LibobJC (OC and Runtime), before the APP’s main function executes.

  • _objc_init is an entry function of object-C runtime library. It is used to read the Segment section corresponding to object-C runtime file. And initializing the runtime related data structures.

So what we’re going to do is we’re going to go into _objc_init and see how the classification is loaded, how it’s read, how it’s released?

2.2 Viewing source Code

Step 1️ : Open objC4 source directly. Find the _objc_init entry function, or by adding a symbolic breakpoint.

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
Copy the code

I don’t want to talk about initialization. Let’s take a look at this, by the way.

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
Copy the code
  • map_images : dyldimageThis function is triggered when loaded into memory.
  • load_images : dyldInitialize theimageTriggers the method. (Well knownloadMethod is also called here.)
  • unmap_image : dyldimageThis function is triggered when it is removed.

So let’s look at what happens to our classification when map_images loads.

Step 2️ : Click on it, and I omit the intermediate transition method. Go straight to the _read_images method implementation.

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();

    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if(! cls) { catlist[i] = nil;if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \? \? \? (%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : ""); }}if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
}
Copy the code

Because the method is too long, I delete some other data to read, only retain the reading classification part, interested students can go to study the reading logic of other data.

Click on _getObjc2ClassList

GETSECT(_getObjc2ClassList,           classref_t,      "__objc_classlist");
Copy the code

This is what we just mentioned. At compile time, the categories are loaded into this section, and we can see that the reads are like this. This also validates our compilation process.

So that means iterating through all the categories and adding Settings one by one. Next, notice that there is a method in iterating

addUnattachedCategoryForClass(cat, cls->ISA(), hi);
Copy the code

As you might guess from the name, add to the original class (note the original class, not the metaclass). The original class).

Step 3️ : Go directly to check this method.

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertLocked();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list.sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}
Copy the code

Here is mainly NXMapInsert(cats, CLS, list); This method, in a nutshell, is to create a binding relationship between the current class and the original class. Prepare for the following.

Step 4️ : Returning to the ergodic method, after establishing binding relation, there is another method below, which is the last one in ergodic, so it must add the method in the classification to the original class. This is our guess, of course, and we analyze the code with that purpose in mind.

remethodizeClass(cls->ISA());
Copy the code

It opens up a space, and then underneath itprepareMethodListsIn, put the data such as classification to add to the metaclass into it

Step 5️ : Focus attachCategories method we have briefly outlined in the first half, then come to

rw->methods.attachLists(mlists, mcount);
Copy the code

Click in:

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        Void *memmove(void *__dst, const void *__src, size_t __len); * /
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0])); }}Copy the code

The above method is the core logic of the classification adding method. In simple terms, it is added to the method list of the original method by combining the two functions memmove and memcpy, as well as the method list of the original class and the method list of the new classification.

And note that:

The classified methods are added to the front of the method list array.

Summary of runtime read classification Settings:

  • ① : At runtime, readMach-OExecutable file to loadimageResource time (i.emap_imagesIn the method_read_images) to read what was stored at compile time__objc_catlistsectionSegment in the data structure, and store tocategory_tType in a temporary variable.
  • ② : Iterate over the array of temporary variables, reading in turn
  • (3) :catlistAnd the originalclsmapping
  • (4) : callremethodizeClassModify themethod_listStructure to add the contents of the classification to the original class.

For properties and protocols, the flow is the same as for methods, but stored in different sections. Here is no further details, please refer to the following:

Question:

One last question: ❓

First of all, we know that in the PROCESS of OC search method, when searching the method list of class method, a binary search method is adopted. So the extension method of our class method is added to the first place in the list of methods of the original class. So how does it guarantee that when an external method is called, it will be called in the class method?

A:

See the picture below:

When a method iterates through a binary lookup, it also looks forward to see if there is a method with the same name (method number). If there is a method with the same name (method number), it returns the previous method. This ensures priority order, which means that the methods at the top of the list have high priority execution permission.

Thus, the classification is guaranteed to achieve the purpose.


CategoryThe attribute

As you know, categories declare properties, but they don’t generate the corresponding setters and getters and corresponding instance variables in method_list, and they compile with warnings.

So the solution, as you all know, is to manually set the association property, which means manually fill in setter and getter methods. The specific writing is as follows:

#import "LBPerson+Category.h"
#import <objc/runtime.h>

static NSString * ageCategoryKey = @"ageCategoryKey";
@implementation LBPerson (Category)
- (NSString *)ageCategory {
    return objc_getAssociatedObject(self, &ageCategoryKey);
}

- (void)setAgeCategory:(NSString *)age {
    objc_setAssociatedObject(self, &ageCategoryKey, age, OBJC_ASSOCIATION_COPY);
}
Copy the code

Why does categorization not automatically generate corresponding methods for its attributes?

We don’t see a list of instance variables in a class structure, which is called ivar_list. So there wouldn’t be a compiler that would automatically do the @synthesize and ivar and setters and getters for us like the class does

So let’s take a closer look at how relational properties are implemented and how they are stored and destroyed.

Note: this is also objC4

Step 1. Click the objc_setAssociatedObject method. Transition method skipped.

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0.nil);
    // Do memory management!!
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        // Initialize the HashMap
        AssociationsHashMap &associations(manager.associations());
        // The address of the current object is reversed by bit (key)
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            //<fisrt : disguised_object , second : ObjectAssociationMap>
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if(i ! = associations.end()) {// secondary table exists
                ObjectAssociationMap *refs = i->second;
                 //< fisRT: flag (custom), second: ObjcAssociation>
                ObjectAssociationMap::iterator j = refs->find(key);
                if(j ! = refs->end()) { old_association = j->second;//ObjcAssociation
                    j->second = ObjcAssociation(policy, new_value);
                } else{ (*refs)[key] = ObjcAssociation(policy, new_value); }}else {
                // create the new association (first time).ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); }}else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if(i ! = associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key);if(j ! = refs->end()) { old_association = j->second; refs->erase(j); }}}}// release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
Copy the code

To summarize this method, we can probably guess what this method does, but it does something similar to the automatic setter method.

  • AcquireValue (value, policy) acquireValue(value, policy)

  • Create an AssociationsHashMap and assign a value to the map object: (Bitwise reverse the address of the current object as key and create an ObjectAssociationMap object as value)

  • Assign to ObjectAssociationMap: (pass the key specified by the user as the key, create ObjcAssociation object as the value)

  • ObjcAssociation contains user-specified values and memory management policy semantics. (ObjcAssociation(policy, new_value))

  • (Object ->setHasAssociatedObjects())

Step 2. Search for delloc, click to enter, and find:

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if(fastpath(isa.nonpointer && ! isa.weakly_referenced && ! isa.has_assoc && ! isa.has_cxx_dtor && ! isa.has_sidetable_rc)) { assert(! sidetable_present()); free(this);
    } 
    else {
        object_dispose((id)this); }}Copy the code

The object_Dispose method is called in the destructor according to isa when an associated object or other identifier is present. Click again to enter in sequence

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}
Copy the code

Release association properties in turn, and so on. In other words, the life cycle of our associated property follows the original object.

So far, the principle of the associated object has been analyzed and summarized:

CategorySummary of associated attributes:

  • The association properties store and read user Settings by defining a new data structure ObjcAssociation container. To achieve the effect of attributes accessing instance variables through methods.

  • The classification association attribute has the same life cycle as the original class. Destruction is implemented in Dealloc by indicating in ISA whether there is an associated object.


The next chapter will continue to explore the load method. Welcome to pay attention to exchange.

Jane’s address book