This article belongs to “Jane Book — Liu Xiaozhuang” original, please note:

< Jane books – Liu Xiaozhuang > https://www.jianshu.com/p/3019605a4fc9



This article is based on objC-723. The source code is available on Apple Github and Apple OpenSource, but you need to compile it yourself.

The key is ~, you can go to my Github to download the compiled source code, the source code has written a large number of annotations, convenient for readers to study. (If you feel good, please click Star😁) Runtime Analyze

Object initialization process

When an object is initialized, it is usually instantiated by either alloc+init or new. The process of instantiating by alloc+init will be examined below, and the following code is the key code.

The first two steps are simple, making function calls directly.

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/.true/*allocWithZone*/);
}
Copy the code

There are two ways to create objects at the place. One is to open up memory through calloc and then initialize the memory through initInstanceIsa. The second is to call class_createInstance directly and implement the initialization logic internally.

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (fastpath(cls->canAllocFast())) {
        bool dtor = cls->hasCxxDtor();
        id obj = (id)calloc(1, cls->bits.fastInstanceSize());
        if(slowpath(! obj))return callBadAllocHandler(cls);
        obj->initInstanceIsa(cls, dtor);
        return obj;
    }
    else {
        id obj = class_createInstance(cls, 0);
        if(slowpath(! obj))return callBadAllocHandler(cls);
        returnobj; }}Copy the code

But in the latest version of OBJC-723, calling the canAllocFast function directly returns false, so only the second else block above is executed.

bool canAllocFast() {
    return false;
}
Copy the code

The initialization code eventually calls the _class_createInstanceFromZone function, which is the key code for initialization. The following code goes into the if statement, allocates memory via calloc based on the size returned by instanceSize, and initializes the ISA_t pointer.

id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline))
id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                                 bool cxxConstruct = true, 
                                 size_t *outAllocatedSize = nil)
{
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size = cls->instanceSize(extraBytes);

    id obj;
    if(! zone && fast) { obj = (id)calloc(1, size);
        if(! obj)return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if(! obj)return nil;
        obj->initIsa(cls);
    }

    return obj;
}
Copy the code

In the instanceSize() function, the original size of the object is obtained via the alignedInstanceSize function, as defined in the instanceSize variable in the class_ro_t structure. This variable stores the amount of memory that all variables take up when the object is instantiated. This size is determined by the compiler and cannot be dynamically changed at runtime.

After instanceSize is obtained, address the obtained size. It is important to note that the CF framework requires all objects to be at least 16 bytes in size, or 16 bytes if not.

size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}
Copy the code

This is also a key step, since the nonpointer field is passed true when initIsa is called, so the if statement is executed directly to set the CLS of ISA to the Class passed in. Isa is the structure member variable of objc_object, which is of type ISA_t.

inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}

inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    if(! nonpointer) { isa.cls = cls; }else {
        isa_t newisa(0);
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3; isa = newisa; }}Copy the code

Creating an object using a new function is essentially the same thing. It’s created internally by a callAlloc function, which is also called if you call the alloc method. So calling new to initialize an object can be the same as calling alloc+init.

+ (id)new {
    return [callAlloc(self.false/*checkNil*/) init];
}
Copy the code

In the Runtime source code, doing init is essentially just returning self.

- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    return obj;
}
Copy the code

dealloc

When the object is destroyed, the runtime environment calls NSObject’s dealloc method to execute the destruction code, and we don’t need to call it manually. The objc_object::rootDealloc(C++ namespace) function inside the Runtime is then called.

In the rootDealloc function, some pre-release operations are performed, such as pointing all references to an object to nil and calling the free function to free memory.

Object_dispose is executed in the ELSE if ARC environment and the current object has instance variables, otherwise the above if statement is executed. The if statement above indicates that the current object has no instance variable, so the current object is simply free.

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

In object_Dispose, it is primarily implemented through the objc_destructInstance function. There are three main things that are done inside the function:

  1. Destructor is called when the current object is destructed.cxx_destructDelta function, and inside the delta function, there’s also delta functionreleaseOperation.
  2. Removes all associations for the current object.
  3. Go to the lastclearOperation.
// The core implementation of the dealloc method will do the judgment and destruct operations
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // determine whether there is a destructor for OC or C++
        bool cxx = obj->hasCxxDtor();
        // Whether the object has an associated reference
        bool assoc = obj->hasAssociatedObjects();

        // Destructs the current object
        if (cxx) object_cxxDestruct(obj);
        // Remove the association of all objects, e.g. set the weak pointer to nil
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}
Copy the code

The above function calls object_cxxDestruct for destruct, and the inside of the function is implemented through the object_cxxDestructFromClass function.

Internally, the function iterates from the class to which the current object belongs, all the way to the root class. The.cxx_destruct function is continuously executed during the traversal to destruct the passed object.

Because each class in the successor chain has its own destructor code, it is necessary to pass in the current objects and perform the destructor one by one to complete all the destructors of the objects.

// call the C++ destructor
static void object_cxxDestructFromClass(id obj, Class cls)
{
    void (*dtor)(id);

    // Start traversing from the current class until you reach the root class
    for(; cls; cls = cls->superclass) {if(! cls->hasCxxDtor())return;
        // SEL_cxx_destruct is the selector for.cxx_destruct
        dtor = (void(*) (id))
            lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
        if(dtor ! = (void(*) (id))_objc_msgForward_impcache) {
            // Get the function pointer to.cxx_destruct and call it(*dtor)(obj); }}}Copy the code

After the.cxx_destruct destruct is executed, the final release operation is called inside the destruct.

AddMethod implementation

The following two Runtime functions are used in projects to dynamically manipulate the list of methods, such as dynamically adding or replacing a method. In the following two functions, which are essentially implemented by addMethod, the return value is negated in class_addMethod, so if this function returns NO, the method already exists. Do not add it again.

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if(! cls)return NO;

    rwlock_writer_t lock(runtimeLock);
    return! addMethod(cls, name, imp, types ? :"".NO);
}

IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if(! cls)return nil;

    rwlock_writer_t lock(runtimeLock);
    returnaddMethod(cls, name, imp, types ? :"".YES);
}
Copy the code

Let’s take a look at the implementation of the addMethod function, still keeping only the core source code.

The addMethod function determines whether the method to be added exists. If it already exists, the corresponding IMP is returned directly. Otherwise, a method is added dynamically. There is a replace field in the class_addMethod function that distinguishes whether or not class_replaceMethod is called. If replace is NO, IMP is returned directly, if YES, the original implementation of the method is replaced.

If the added method does not exist, a pointer to the method_list_t structure is created and the three basic parameters name, types, and IMP are set. Then the newly created method_list_t structure is added to the list of methods through the attachLists function.

static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;
    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // already exists
        if(! replace) { result = m->imp; }else{ result = _method_setImplementation(cls, m, imp); }}else {
        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;

        prepareMethodLists(cls, &newlist, 1.NO.NO);
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}
Copy the code

The implementation is simpler in the attachLists function by shifting the original address and copying the newly created method_list_t structure into the method list.

void attachLists(List* const * addedLists, uint32_t addedCount) {
    // ...
    memmove(array()->lists + addedCount, array()->lists, oldCount * sizeof(array()->lists[0]));
    memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));
    // ...
}
Copy the code

Add Ivar

In Runtime, you can add instance objects to a class using the class_addIvar function. Note, however, that this function cannot add instance variables to an existing class. It can only be used to dynamically add instance variables to classes created through the Runtime API.

The function should add instance variables between classes created by calling objc_allocateClassPair and classes registered by calling objc_registerClassPair, or it will fail. You cannot add instance variables to a metaclass, you can only add instance variables to a class.

Here is the code to dynamically create a class and add instance variables to the newly created class.

Class testClass = objc_allocateClassPair([NSObject class]."TestObject".0);
BOOL isAdded = class_addIvar(testClass, "password".sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
objc_registerClassPair(testClass);

if (isAdded) {
    id object = [[testClass alloc] init];
    [object setValue:@"lxz" forKey:@"password"];
}
Copy the code

So why put the code that dynamically adds instance variables between these two functions? Let’s explore.

First, the objc_allocateClassPair function is used to create a class. When creating a class, the getClass function is used to determine whether the class name is used. Then, the verifySuperclass function is used to determine whether the superclass is appropriate.

The class and metaclass are created using the alloc_class_for_subclass function. Inside the alloc function, essentially the memory is allocated through the calloc function and nothing else is done. The objc_initializeClassPair_internal function is then executed. Inside the initialize function are the initialization operations used to initialize the newly created Class and metaClass.

Class objc_allocateClassPair(Class superclass, const char *name, 
                             size_t extraBytes)
{
    Class cls, meta;
    if(getClass(name) || ! verifySuperclass(superclass,true/*rootOK*/)) {
        return nil;
    }

    cls  = alloc_class_for_subclass(superclass, extraBytes);
    meta = alloc_class_for_subclass(superclass, extraBytes);

    objc_initializeClassPair_internal(superclass, name, cls, meta);
    return cls;
}
Copy the code

This is the internal implementation of the initialize function, which is all the initialization code and does nothing else. At this point, the class is initialized and you can add instance variables outside using the class_addIvar function.

static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
    class_ro_t *cls_ro_w, *meta_ro_w;
    
    cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
    meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
    cls_ro_w   = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    meta_ro_w  = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
    cls->data()->ro = cls_ro_w;
    meta->data()->ro = meta_ro_w;

    // Set basic info
    cls->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    meta->data()->flags = RW_CONSTRUCTING | RW_COPIED_RO | RW_REALIZED | RW_REALIZING;
    cls->data()->version = 0;
    meta->data()->version = 7;

    / /...
}
Copy the code

After the class is created, the new class is registered through the objc_registerClassPair function. Just like creating a new class, registering a new class is divided into a registered class and a registered metaclass. The metaclass is registered by the addNonMetaClass function below, and the class is registered by calling the NXMapInsert function directly.

void objc_registerClassPair(Class cls)
{
    cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
    cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);

    addNamedClass(cls, cls->data()->ro->name);
}

static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    Class old;
    if((old = getClass(name)) && old ! = replacing) { inform_duplicate(name, old, cls); addNonMetaClass(cls); }else{ NXMapInsert(gdb_objc_realized_classes, name, cls); }}Copy the code

Both the registered class and the registered metaclass are internally implemented by the NXMapInsert function. In Runtime, all classes are stored in a hash table in the buckets of the table. Each time a new class is created, it needs to be added to the hash table. Here is the logic for inserting into the hash table.

void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
    MapPair	*pairs = (MapPair *)table->buckets;
    // Computes the subscript of the key in the current hash table. The hash subscript is not necessarily the last
    unsigned	index = bucketOf(table, key);
    // Find the starting address of buckets and use index subscript to calculate the corresponding location to obtain the MapPair corresponding to index
    MapPair	*pair = pairs + index;
    // Returns if key is empty
    if (key == NX_MAPNOTAKEY) {
        _objc_inform("*** NXMapInsert: invalid key: -1\n");
        return NULL;
    }

    unsigned numBuckets = table->nbBucketsMinusOne + 1;
    // If the current addresses do not conflict, the pair is directly assigned
    if (pair->key == NX_MAPNOTAKEY) {
        pair->key = key; pair->value = value;
        table->count++;
        if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
            return NULL;
    }
    
    /* At this point, the hash table conflicts with */
    
    // Replace the old class with the new class if it has the same name
    if (isEqual(table, pair->key, key)) {
        const void	*old = pair->value;
        if(old ! = value) pair->value = value;return (void *)old;
    
    // The hash table is full, rehash the hash, and then execute the function again
    } else if (table->count == numBuckets) {
        /* no room: rehash and retry */
        _NXMapRehash(table);
        return NXMapInsert(table, key, value);

    // The hash table conflicts
    } else {
        unsigned	index2 = index;
        // Resolve hash table conflicts. Linear detection is used to resolve hash table conflicts
        while((index2 = nextIndex(table, index2)) ! = index) { pair = pairs + index2;if (pair->key == NX_MAPNOTAKEY) {
                pair->key = key; pair->value = value;
                table->count++;
                // The hash table is not enough
                if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
                    return NULL;
            }
            // If it finds a class with the same name, it replaces the old class with the new class and returns
            if (isEqual(table, pair->key, key)) {
                const void	*old = pair->value;
                if(old ! = value) pair->value = value;return (void*)old; }}return NULL; }}Copy the code

thinking

So why can you only add Ivars to classes that are dynamically created at runtime, but not to classes that already exist?

This is because the read-only structure class_ro_t is determined at compile time and is immutable at run time. One of the fields in the ro structure is instanceSize, which indicates how much space the current class needs to create the object. Subsequent creations allocate memory for the class based on this size.

If adding a parameter to an existing class changes the structure of ivars, problems will arise when accessing objects created before the change.

For example, create TestObject class in the project and add three member variables. Its ivars memory structure occupies 20 bytes. If a bool parameter is added dynamically at runtime, subsequent ivars objects take up 21 bytes.

When accessing a previously created object through the ivars structure, the memory space allocated is still 20 bytes because the previously created object does not have sex. In this case, accessing sex causes the address to be out of bounds.

The data access

When you define an object, you give it a type. A type is not an object in nature, but is used to indicate the space occupied by the current object. In C, for example, objects are accessed by address, and the type is how many bits are read from the first address of the current object.

int number = 18;
char text = 'i';
Copy the code

For example, the above code defines a number of type int that takes up four bytes and a text variable of type char that takes up one byte. When accessing objects in memory, it is to find the corresponding memory area according to the address of the pointer, and then take the range of memory according to the type of the pointer to complete the object reading operation.

In object-oriented languages, however, the naming conventions of functions or methods need to be retained at run time. In C++, for example, there is a concept called “function overloading.” function overloading refers to a set of functions that are allowed to have the same function name but different argument list types.

Function:void print(charC) Overload result: _ZN4test5printEcCopy the code

There are certain rules for C++ function overloading. For example, the result of the overloaded print function is the function that is actually executed at runtime. Function overloading occurs at compile time and regenerates the function name from the namespace, class name, function name, return value, parameter, and other parts.

The concept of function overloading also exists in OC, but instead of modifying the original method name directly, OC adds a method_t structure that codes the return values and parameters in a method_t manner.

The method_t structure stores method information, where the types field is the encoding of the return values and parameters. The encoded string is similar to “iv@:d”. The full encoding rules can be found in the official documentation.

The following is the definition of Method, which contains three key pieces of information.

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};
Copy the code

Protocol

We use protocols a lot in our projects. How does that work?

The Runtime source code shows that a protocol is an object of a protocol_T structure. The protocol_T structure is inherited from objC_Object and thus has the characteristics of an object.

In addition to the struct parameters defined in objC_Object, PROTOCOL_t also defines some unique parameters, such as the common name, method list, Property list, size, etc. So you can see that object methods, class methods, and object properties and class properties can be declared in a protocol.

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

Since it has the characteristics of an object, it also has isa Pointers. All ISAs in Protocol refer to the same class Protocol. Nothing too complicated is done in the Protocol class, just basic methods are implemented.

@implementation Protocol 

+ (void) load {

}

- (BOOL) conformsTo: (Protocol *)aProtocolObj {
    return protocol_conformsToProtocol(self, aProtocolObj);
}

- (struct objc_method_description *) descriptionForInstanceMethod:(SEL)aSel {
    return method_getDescription(protocol_getMethod((struct protocol_t *)self, 
                                                     aSel, YES.YES.YES));
}

- (struct objc_method_description *) descriptionForClassMethod:(SEL)aSel {
    return method_getDescription(protocol_getMethod((struct protocol_t *)self, 
                                                    aSel, YES.NO.YES));
}

- (const char *)name {
    return protocol_getName(self);
}

// Protocol overrides the isEqual method and looks internally for its parent to determine whether it is a subclass of Protocol.
- (BOOL)isEqual:other {
    Class cls;
    Class protoClass = objc_getClass("Protocol");
    for (cls = object_getClass(other); cls; cls = cls->superclass) {
        if (cls == protoClass) break;
    }
    if(! cls)return NO;
    // check equality
    return protocol_isEqual(self, other);
}

- (NSUInteger)hash {
    return 23;
}

@end
Copy the code

Initialization of the protocol is also done in the _read_images function, and the initialization process is mainly a traversal. The logic is to get the Protocol list, iterate through the array, and call the readProtocol function to initialize it.

// Iterate over all Protocol lists and load the Protocol list into the Protocol hash table
for (EACH_HEADER) {
    extern objc_class OBJC_CLASS_$_Protocol;
    // CLS = Protocol. All protocols and objects have similar structures. Isa corresponds to Protocol
    Class cls = (Class)&OBJC_CLASS_$_Protocol;
    assert(cls);
    // Get the protocol hash table
    NXMapTable *protocol_map = protocols();
    bool isPreoptimized = hi->isPreoptimized();
    bool isBundle = hi->isBundle();

    // Reads and initializes Protocol from the compiler
    protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
    for (i = 0; i < count; i++) { readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle); }}Copy the code

In the readProtocol function, initialization is performed based on the protocol passed in. Of the incoming arguments, protocol_class is the Protocol class to which the ISA of all Protocol classes points.

Protocol source code can be seen, its object model is relatively simple, and Class object model is not quite the same. The Protocol object model consists of objects loaded from the Protocol List and the Protocol class that ISA points to. There is no other instantiation. The Protocol class does not have a metaclass.

// Initializes all protocols passed in. If the initialized Protocol already exists in the hash table, nothing is done
static void
readProtocol(protocol_t *newproto, Class protocol_class,
             NXMapTable *protocol_map, 
             bool headerIsPreoptimized, bool headerIsBundle)
{
    auto insertFn = headerIsBundle ? NXMapKeyCopyingInsert : NXMapInsert;
    // Get the corresponding Protocol object based on the name
    protocol_t *oldproto = (protocol_t *)getProtocol(newproto->mangledName);

    If Protocol is not NULL, the same Protocol already exists, no processing is done, and the following if statement is entered.
    if (oldproto) {
        // nothing
    }
    // If Protocol is NULL, simply initialize it and set Protocol's ISA to Protocol class
    else if (headerIsPreoptimized) {
        protocol_t *cacheproto = (protocol_t *)
            getPreoptimizedProtocol(newproto->mangledName);
        protocol_t *installedproto;
        if(cacheproto && cacheproto ! = newproto) { installedproto = cacheproto; }else {
            installedproto = newproto;
        }
        // Hash table inserts pointer to function
        insertFn(protocol_map, installedproto->mangledName, 
                 installedproto);
    }
    // The following two else procedures are used to initialize protocol_t
    else if (newproto->size >= sizeof(protocol_t)) {
        newproto->initIsa(protocol_class);
        insertFn(protocol_map, newproto->mangledName, newproto);
    }
    else {
        size_t size = max(sizeof(protocol_t), (size_t)newproto->size);
        protocol_t *installedproto = (protocol_t *)calloc(size, 1); memcpy(installedproto, newproto, newproto->size); installedproto->size = (__typeof__(installedproto->size))size; installedproto->initIsa(protocol_class); insertFn(protocol_map, installedproto->mangledName, installedproto); }}Copy the code

Protocols can be added dynamically at run time, similar to the process of creating classes, with two parts: create and register. After a Protocol is created, the Protocol is in an unfinished state and is available only after registration.

// Create a new Protocol and call the register method below
Protocol *
objc_allocateProtocol(const char *name)
{
    if (getProtocol(name)) {
        return nil;
    }

    protocol_t *result = (protocol_t *)calloc(sizeof(protocol_t), 1);

    // The CLS below is the __IncompleteProtocol class, which represents an unfinished Protocol
    extern objc_class OBJC_CLASS_$___IncompleteProtocol;
    Class cls = (Class)&OBJC_CLASS_$___IncompleteProtocol;
    result->initProtocolIsa(cls);
    result->size = sizeof(protocol_t);
    result->mangledName = strdupIfMutable(name);
    
    return (Protocol *)result;
}
Copy the code

The registration Protocol.

// Register the newly created protocol object into the protocol hash table
void objc_registerProtocol(Protocol *proto_gen) 
{
    protocol_t *proto = newprotocol(proto_gen);

    extern objc_class OBJC_CLASS_$___IncompleteProtocol;
    Class oldcls = (Class)&OBJC_CLASS_$___IncompleteProtocol;
    extern objc_class OBJC_CLASS_$_Protocol;
    Class cls = (Class)&OBJC_CLASS_$_Protocol;

    // If already registered in the hash table, return directly
    if (proto->ISA() == cls) {
        return;
    }
    // If the current protocol's isa is not __IncompleteProtocol, the protocol is problematic
    if(proto->ISA() ! = oldcls) {return;
    }
    proto->changeIsa(cls);
    NXMapKeyCopyingInsert(protocols(), proto->mangledName, proto);
}
Copy the code

SEL

Previously, SEL was implemented by the objC_selector structure, but from the current source, SEL is a const char* constant string that simply represents a name.

typedef struct objc_selector *SEL;
Copy the code

Why is SEL just a constant string? Let’s explore this in the Runtime source code.

This is the implementation of SEL list in _read_images function. The main logic is to load SEL list into memory, and then register all SEL into the hash table of SEL through sel_registerNameNoLock function.

However, we can see from this code that most SEL and const char* conversions are directly cast, so they are the same memory.

// Register all SEL to hash table, is another hash table
static size_t UnfixedSelectors;
sel_lock();
for (EACH_HEADER) {
    if (hi->isPreoptimized()) continue;

    bool isBundle = hi->isBundle();
    // Retrieve an array of strings, such as "class" at the beginning
    SEL *sels = _getObjc2SelectorRefs(hi, &count);
    UnfixedSelectors += count;
    for (i = 0; i < count; i++) {
        // inside the sel_cname function is to force SEL to a constant string
        const char *name = sel_cname(sels[i]);
        // Register SEL operationssels[i] = sel_registerNameNoLock(name, isBundle); }}Copy the code

Sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock: sel_registerNameNoLock

static SEL sel_alloc(const char *name, bool copy)
{
    return (SEL)(copy ? strdupIfMutable(name) : name);    
}
Copy the code

Peer exchange protocol

When studying Apple source code, you can also study through GNUStep, GNUStep is a set of Apple peer exchange source code, OC code to re-implement again, the internal implementation is roughly similar to Apple. GNUStep


Due to typesetting problems, the reading experience is not good, such as layout, picture display, code and many other problems. So go to Github and download the Runtime PDF collection. Put all the Runtime articles together in this PDF, with a table of contents on the left for easy reading.

Please give me a thumbs up, thank you! 😁