[OC bottom layer_principle of message sending and forwarding mechanism]

Preface

OC has always been called a dynamic language. Objective-C is a dynamic language. You can understand it when you learn KVO and KVC. Of course, it is not only because of this characteristic, OC also has dynamic binding, dynamic types , etc. Characteristics, the message passing and message forwarding mechanism studied next will allow you to deeply understand these special dynamic characteristics.

Briefly understand the meaning of dynamic binding and dynamic typing

dynamic type

Dynamic typing: Objective-CThe objects in are dynamically typed, which means that you can dynamically send messages to the object at runtime, and the object can execute corresponding methods based on the received messages. This is different from statically typed languages, which require method calls to be explicitly specified at compile time.

dynamic binding

Dynamic binding: Objective-C uses dynamic binding to implement method dispatch. At runtime, the Objective-C runtime system determines which method to call based on the recipient of the message. This means you can decide which method to call at runtime, rather than at compile time.

Why dynamic language?

Objective-C is a dynamic language, so determining which method to call is deferred to runtime, not compile time. In contrast, C language uses static binding, which means that the function that should be called when the program is running can be determined at compile time. Therefore, in C language, if a function is not implemented, it will not pass at compile time. Objective-C is a relatively dynamic language, and methods can be dynamically added to classes during runtime. Therefore, it cannot be determined at compile time whether the method has a corresponding implementation, and the compiler cannot report an error during compilation.

  • For example, if a button does not implement its response event, it will warn but not report an error. When I click it during operation, an error will be reported. This is a manifestation of dynamics. Of course, this is also a manifestation of insecurity. There is no such thing in Swift.
    Please add image description

Swift

Please add image description

  • If the corresponding method is not written in Swift, an error will be reported directly. Swift is called an extremely safe language.

What is messaging

When calling a method on an object, it is very common in Objective-C. This is called sending a message to an object. The message can not 名称only 选择子accept parameters, but also have a return value.

In Objective-C, if a message is passed to an object, the dynamic binding mechanism is used to determine the method that needs to be called. At the bottom level, all methods are ordinary C language functions . However, after the object receives the message, which method should be called is completely determined during the runtime, and can even be changed while the program is running. These features make Objective-C a real programming language. dynamic language.

Send a message to an object

id returnValue = [someObject messageName:parameter];
  • In Objective-C, [someObject messageName:parameter]is the syntax for sending messages. This statement means someObjectsending messageNamea message named to the object and passing parameters parameter.
  • Because Objective-Cit is a dynamic language, the compiler cannot determine the someObjectspecific type of the message receiver and the specific implementation of the message at compile time. Therefore, the runtime system needs to be used Objective-Cto parse the message and execute the corresponding method at runtime. At runtime, the compiler will convert the method call in the above format into a standard C language function call implementation: , objc_ msgSend()this function is the core function .
void objc_msgSend(id self, SEL cmd, ....
  • objc_msgSendThe function of () is to send messages to objects and execute corresponding methods. It accepts several parameters, including the receiver object of the message, a selector for the method to be called, and any method parameters. Based on the selector, the runtime system finds the method implementation of the receiver object and dynamically calls the method at runtime.

At runtime, the above Objc method call will be translated into a C language function call, as follows:

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter)

SELECT SEL

In Objective-C, selector (Selector) is the data type used to represent method names. It is a unique identifier generated by the compiler at runtime and is used to find and call the corresponding method in the object.

During compilation, OC will generate a unique ID to distinguish this method based on the name of the method (including parameter sequence) . This ID is of SEL type. What we need to note is that as long as the method names (including parameter sequences) are the same, their IDs are the same . So whether it is a parent class or a subclass, if the name is the same, the ID will be the same.

Please add image description

A SEL table is maintained in Runtime. This table stores SELs not according to classes. As long as the same SELs are regarded as one, they will be stored in the table. When the project is loaded, all methods will be loaded into this table, and dynamically generated methods will also be loaded into the table.

IMP

What is IMP?

IMP: a function pointer that stores the method address

  • It is the address of the OC method implementation code block, through which you can directly access any method, eliminating the need to send messages.

IMP is the abbreviation of " implementation ", which is the address of the objetive-C method (method) implementation code block , which can be called directly like a C function. Usually we send a message to the object through [object method:parameter] or objc_msgSend() , and then the Objective-C runtime (Objective-C runtime) looks for the IMP that matches this message, and then calls it; but sometimes we Hope to get the IMP and call it directly.

Declare an IMP:

typedef id (&IMP)(id,SEL,...);

IMP is a function pointer . The pointed function contains an object id (self pointer) that receives the message, the option SEL (method name) of the calling method, and an indefinite number of method parameters, and returns an id.

The relationship between IMP and SEL

  • SEL is a method pointer, similar to a number.
  • IMP is a function pointer that saves the address of the method.
    In Objective-C, SELand IMPare types used to represent method selectors and method implementations.
  • SEL (selector):
    • SEL is a data type that represents method names, which are generated by the compiler at compile time.
    • SEL is a unique identifier used to find and call the corresponding method at runtime.
    • Use @selectorthe keyword to convert method names to SEL types.
    • SEL is mainly used to dynamically call methods and check and compare method names at runtime.
  • IMP (method implementation):
    • IMP is a function pointer type used to represent the memory address of the implementation code of a method.
    • IMP points to the actual code of the method, which contains the specific implementation logic of the method.
    • IMP is generated by the compiler at compile time and corresponds to SEL.
    • At runtime, the corresponding IMP can be found through SEL to call the method implementation.

connect:

  • SEL and IMP are closely related and are used together to implement the dynamic features of Objective-C.
  • SEL is used to identify the method name, while IMP represents the specific implementation of the method.
  • In the Objective-C runtime system, SEL can be used to find and associate the corresponding IMP, thereby dynamically calling methods at runtime.
  • The combined use of SEL and IMP enables Objective-C to implement dynamic dispatch of messages and dynamic invocation of methods at runtime.

Summary: SEL is a unique identifier of the method name, used to find the implementation of the method , while IMP is a function pointer pointing to the method implementation code . Together, they constitute the method calling mechanism in the Objective-C runtime system and realize the dynamic characteristics of Objective-C.

message sending

OC is a dynamic language. The specific type is not known when compiling. The data type is checked when running. The implementation of the function is found based on the function name. The runtime is what realizes language dynamics. There are two cores:

  1. Dynamic configuration: dynamically modify class information, add attribute methods and even member variable values ​​and other data structures.
  2. Message passing: including sending and forwarding, method calls are converted into functions during compilation objc_msgsendto send messages, and the implementation process is implemented through SELthe search ( method name search method implementation)IMP

1. Quick search

Execution process of objc_msgSend()

  1. Message sending: Responsible for finding methods from the cache list and method list of the class and parent class
  2. Dynamic parsing: If no method is found in the message sending phase , it will enter the dynamic parsing phase: responsible for dynamically adding method implementations
  3. Message forwarding: If the dynamic parsing method is not implemented , the message forwarding stage will be performed to forward the message to a receiver that can process the message for processing.

The message sending and forwarding process can be summarized as follows: Messaging (Messaging) is the process in which Runtime quickly searches for IMP through the selector. With the function pointer, the corresponding method can be executed; Message Forwarding (Message Forwarding) is the process of executing a process after failing to find IMP. If the slow channel of the series forwarding process is not forwarded, logs will be logged and exceptions will be thrown.

objc_msgSend() deep base

As the core protagonist of sending messages, his logic is as follows

id objc_msgSend(id self, SEL _cmd, ...) {
    
    
  Class class = object_getClass(self);
  IMP imp = class_getMethodImplementation(class, _cmd);
  return imp ? imp(self, _cmd, ...) : 0;
}

Core logic and parameter analysis

  • Core Logic
    This is objc_msgSend()an example implementation of a simplified version of the function. This function is used to send messages and call methods in the Objective-C runtime.
  1. Get selfthe object's class (Class): Use object_getClass(self)the function to get selfthe object's class object. This class object is used to obtain the method implementation at runtime.
  2. Get the implementation of the method (IMP): Use class_getMethodImplementation()the function to get the implementation of the corresponding method based on the class object and selector _cmd. This function returns a function pointer (IMP) pointing to the method implementation.
  3. Calling method implementation: If a valid method implementation (non-null) is obtained, impthe method implementation is called through the function pointer. Use imp()a function and pass self, _cmdand other possible parameters to make a method call.
  4. Return result: The return value of the return method.

This is just objc_msgSend()an example of a simplified version of the function. The actual objc_msgSend()function will handle more details, including message forwarding, method caching, etc.

  • Parameter parsing has two default parameters
    objc_msgSend when calling . The first parameter is the receiver of the message, and the second parameter is the method name. If the method itself has parameters, its own parameters will be spliced ​​after these two parameters.

Source code analysis

The source code of objc_msgSend() is written in assembly language. It is implemented differently for different architectures. The efficiency of assembly language is faster than c/c++. It directly accesses and operates registers. Compared with memory operations, it is more low-level and more efficient. .

Apple always starts the names of assembly methods with an underscore to prevent symbol conflicts.

Take a look at the core code of the function


		//进入objc_msgSend流程
	ENTRY _objc_msgSend
    //流程开始,无需frame
	UNWIND _objc_msgSend, NoFrame

    //判断p0(消息接收者)是否存在,不存在则重新开始执行objc_msgSend
	cmp	p0, #0			// nil check and tagged pointer check
//如果支持小对象类型,返回小对象或空
#if SUPPORT_TAGGED_POINTERS
    //b是进行跳转,b.le是小于判断,也就是p0小于0的时候跳转到LNilOrTagged
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
    //等于,如果不支持小对象,就跳转至LReturnZero退出
	b.eq	LReturnZero
#endif
    //通过p13取isa
	ldr	p13, [x0]		// p13 = isa
    //通过isa取class并保存到p16寄存器中
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class


This code is the process that objc_msgSendstarts and ends with the method of finding the class object cache. There is also a prototype of if else

First determine receiverwhether it exists and whether it is taggedPointera pointer of type. If it is not taggedPointera type, we take out isathe pointer of the object (in the x13 register), find the class object (in the x16 register) through the isa pointer, and then search whether there is one CacheLookupin the class object. cacheIf the method is cached, it will be called. If there is no objc_msg_uncachedbranch, it will be called.

Quick search summary of message sending: objc_msgSend(receiver, sel, …)

  • Check receiverwhether the message receiver exists, otherwise nilno processing will be done
  • receiverFind the corresponding classclass object through the isa pointer
  • Find classthe class object and perform memory translation to findcache
  • cacheget frombuckets
  • Comparebuckets the parameters selto see if there is a method with the same name in the cache.
  • If there is a corresponding sel in the buckets --> cacheHit --> call imp
  • If no matching method selector is found in the cache, a slow search process is performed, that is, the _objc_msgSend_uncached function is called, and the _lookUpImpOrForward function is further called for global method search.

In general, message sending will first be implemented through the cache fast search method . If it is not found in the cache, it will enter the slow search process, searching step by step from the class method list, parent class chain, etc., until a matching method is found or Eventually an exception is thrown.

Understanding buckets

Here we need to explain buckets: they are part of bucketsthe cache ( ).cache

  • In the message sending process of Objective-C, each class object has a cache structure used to store the most commonly used method implementations. The cache structure contains multiple bucket, each bucketcan store a method selector ( SEL) and its corresponding method implementation ( IMP).
  • When sending a message, the system will first check whether there is a method implementation in the cache that matches the target method selector. This check is implemented by traversing the cache and comparing the method selectors one by one . bucketIf a matching method selector is found, the corresponding method implementation can be directly called, thus avoiding the slow search process.
  • bucketsIt can be regarded as a slot in the cache that stores method selectors and corresponding method implementations . The specific implementation methods may vary depending on different compilers and platforms, but their goal is to provide a fast search mechanism to avoid a complete search process every time a message is sent.

Method cache

Let’s briefly understand the concept of cache. The above summary all mentioned the concept of cache.

If a method is called, there is a greater chance that this method will be called again. In this case, directly maintain a cache list and load the called method into the cache list. When the method is called again, go to the cache list first. Search for it, and if you can't find it, go to the method list to query. This avoids having to go to the method list to query every time a method is called, which greatly improves the speed.

2. Slow search

When to use lookUpImpOrForward

_lookUpImpOrForwardIt is a function in the Objective-C runtime that is used to perform slow lookup or forwarding operations during message sending.

The function is entered when the fast lookup process for message sending cannot find a matching method implementation_lookUpImpOrForward . The main function of this function is to find the appropriate method implementation in the runtime based on the given receiver object, method selector, and other related information, and perform subsequent processing.

_lookUpImpOrForwardThe specific behavior of the function depends on the state and environment of the object. It may do one of the following:

  1. Slow Path Lookup : If the class to which the object belongs does not provide an implementation of the specified method selector, it will be searched step by step based on the inheritance chain and protocol information until a matching method implementation is found, or an exception will eventually be thrown.
  2. Message Forwarding : If a matching method implementation cannot be found after slow search, the message forwarding mechanism will be triggered. During the message forwarding process, the runtime system will give the object the opportunity to handle unknown method calls. It can dynamically add method implementations, replace them with other objects to handle method calls, or throw exceptions.

_lookUpImpOrForwardFunctions are a very important part of the Objective-C runtime. They ensure that various situations can be handled during message sending and that the object can respond to method calls correctly. The specific implementation details may vary between compilers and platforms, but the core goal is to find appropriate method implementations or trigger message forwarding mechanisms in the runtime.

lookUpImpOrForward 深基

The method is simple to implement.

NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    
    
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    if (slowpath(!cls->isInitialized())) {
    
    
        // The first message sent to a class is often +new or +alloc, or +self
        // which goes through objc_opt_* or various optimized entry points.
        //
        // However, the class isn't realized/initialized yet at this point,
        // and the optimized entry points fall down through objc_msgSend,
        // which ends up here.
        //
        // We really want to avoid caching these, as it can cause IMP caches
        // to be made with a single entry forever.
        //
        // Note that this check is racy as several threads might try to
        // message a given class for the first time at the same time,
        // in which case we might cache anyway.
        behavior |= LOOKUP_NOCACHE;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.lock();

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    // 检查当前类是个已知类
    checkIsKnownClass(cls);
    // 确定当前类的继承关系
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); 
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookup the class's cache again right after
    // we take the lock but for the vast majority of the cases
    // evidence shows this is a miss most of the time, hence a time loss.
    //
    // The only codepath calling into this without having performed some
    // kind of cache lookup is class_getInstanceMethod().

    for (unsigned attempts = unreasonableClassCount();;) {
    
    
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
    
    
            // 如果是常量优化缓存
            // 再一次从cache查找imp
            // 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
            imp = cache_getImp(curClass, sel); //cache中找IMP
            if (imp) goto done_unlock; //找到就直接返回了
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
    
     //如果不是常量优化缓存
            // 当前类的方法列表。
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
    
    
                imp = meth->imp(false);
                goto done;
            }
            // 每次判断都会把curClass的父类赋值给curClass
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    
    
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        // 如果超类链中存在循环,则停止。
        if (slowpath(--attempts == 0)) {
    
    
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
    
    
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
    
    
            // 在超类中找到方法。在这个类中缓存它。
            goto done;
        }
    }

    // 没有实现,尝试一次方法解析器。
	// 这里就是消息转发机制第一层的入口
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
    
    
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
    
    
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
    
    
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
    
    
        return nil;
    }
    return imp;
}

This code is a partial implementation of a function in the Objective-C runtime _lookUpImpOrForward. This function performs slow lookup or forwarding operations during message sending.

First, the code checks instwhether the receiver is null, and if so, returns null directly.

Next, the code performs a series of processing and search operations based on the receiver's class object clsto find the appropriate method implementation imp. This includes:

  1. Check if the class object has been initialized, if not then LOOKUP_NOCACHEadd the flag behaviorto avoid cache lookup.
  2. realizeAndInitializeIfNeeded_lockedInstantiate and initialize the class object through functions to ensure that the class object is ready.
  3. Implemented using a loop-by-level search method, including searching in the cache of the class, searching in the method list of the class, and searching in the parent class chain. If a matching method implementation is found, jump to donethe label.
  4. If no matching method implementation is found during the search process, message forwarding is required. forward_impAssign the default implementation of message forwarding to imp.
  5. If the flag is set LOOKUP_RESOLVER, it means that the method parser needs to be called for further processing and jumps to resolveMethod_lockedthe function for parsing.
  6. After the lookup or forwarding is completed, if LOOKUP_NOCACHEthe flag is not set, the found method implementation is impcached in the cache of the class object.

Finally, the code unlocks the runtime lock, returning the found method implementation impor null as appropriate.

The core of _lookUpImpOrForward

// unreasonableClassCount()表示循环的上限;
    for (unsigned attempts = unreasonableClassCount();;) {
    
    
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
    
    
            // 如果是常量优化缓存
            // 再一次从cache查找imp
            // 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
    
    
            // curClass方法列表。
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
    
    
                imp = meth->imp(false);
                goto done;
            }
            // 每次判断都会把curClass的父类赋值给curClass
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
    
    
                // 没有找到实现,方法解析器没有帮助。
                // 使用转发。
                imp = forward_imp;
                break;
            }
        }

        // 如果超类链中存在循环,则停止。
        if (slowpath(--attempts == 0)) {
    
    
            _objc_fatal("Memory corruption in class list.");
        }

        // 超类缓存。
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
    
    
            // 在超类中找到forward::条目。
            // 停止搜索,但不要缓存;调用方法
            // 首先为这个类解析器。
            break;
        }
        if (fastpath(imp)) {
    
    
            // 在超类中找到方法。在这个类中缓存它。
            goto done;
        }
    }

The above code snippet is _lookUpImpOrForwardpart of the function and is used for method lookup in the inheritance chain of class objects .

The code first uses unreasonableClassCount()the function to determine the upper limit of the loop. Then it enters an infinite loop.

In the loop, first check whether the cache of the current class object is a constant optimization cache (isConstantOptimizedCache ). If it is a constant optimization cache, the code tries to get the method implementation from the cache ( cache_getImp(curClass, sel)). If the method implementation is successfully obtained, jump to done_unlockthe label and end the search.

If the cache for the current class object is not a constant-optimized cache, code execution continues. getMethodNoSuper_nolockFind a method in the method list of the current class object by calling the function ( meth = getMethodNoSuper_nolock(curClass, sel)). If a matching method is found, obtain the corresponding method implementation ( imp = meth->imp(false)), jump to donethe label, and end the search.

If no matching method implementation is found in the method list of the current class object, code execution continues. Assign the parent class of the current class object to curClassand determine whether it is nil. If the parent class is nil, it means that it has reached the top of the inheritance chain and no matching method implementation has been found. At this time, the default forwarding implementation forward_impis assigned impand the loop is jumped out.

On each iteration of the loop, attemptsthe value of is decremented by one, representing the number of searches that have not been completed. If attemptsthe value of decreases to zero, it indicates that there is a cycle in the inheritance chain of the class object, which is unreasonable. At this point an error is triggered, terminating program execution.

If the forwarded entry ( imp == forward_imp) is found in the cache of the current class object, it means that the forwarded method implementation is found in the cache of the parent class. At this time, the loop will stop, but the forwarded method will not be cached. Instead, the method parser will be called first to process it.

Finally, after the loop ends, the found method implementation is cached into the class object's cache as needed, then the runtime lock is unlocked, and the found method implementation or a null value is returned as needed.

This code snippet shows the process of method lookup in the inheritance chain of a class object . It first tries to get the method implementation from the cache and then looks up the hierarchy until it finds a matching method implementation or reaches the top of the inheritance chain. If no matching method implementation is found, forwarding is used.

Slow lookup IMP summaryIMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)

  1. Find imp from this class method list(binary search/traversal search)
  2. cacheSearch from the parent class of this class imp( assembly)
  3. Find the imp from the parent class of this class method list (binary search/traversal search) ...Inheritance chain traversal...(parent class->...->root parent class) to find the imp of cache and method list
  4. If imp is found in any of the above links, jump out of the loop, cache the method to the cache of this class, and return imp
  5. Until it is found nil, specify it impas message forwarding, jump out of the loop, and execute dynamic resolution resolveMethod_locked (content of message forwarding)
    Insert image description here

message forwarding

After the message is sent and we still haven't found a way, we will implement the message forwarding mechanism, but before that, we will also perform a step called dynamic resolution.

Dynamic resolution resolveMethod_locked

cacheWhen neither this class nor the sum under the inheritance chain of this class method listcan be found imp, and it impis assigned _objc_msgForward_impcachebut it is not called, it will enter the dynamic method resolution process, and it will only be executed once.

resolveMethod_lockedIs one of the implementations of the method parser in the Objective-C runtime. It is used to solve the situation where the method implementation cannot be found during the message sending process. Here is resolveMethod_lockedthe code for the function:

/***********************************************************************
* resolveMethod_locked
* Call +resolveClassMethod or +resolveInstanceMethod.
*
* Called with the runtimeLock held to avoid pressure in the caller
* Tail calls into lookUpImpOrForward, also to avoid pressure in the callerb
**********************************************************************/
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    
    
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    //判断是不是元类
    if (! cls->isMetaClass()) {
    
    
        // 不是元类,则是实例方法的动态方法解析
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
    
    
        // 是元类,则是类方法的动态方法解析
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls); // inst:类对象   cls: 元类
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
    
    
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

The above code flow is as follows:

  1. Determine whether the parsed object is a metaclass
  2. If it is not a metaclass, call _class_resolveInstanceMethodthe object method to dynamically resolve
  3. a. If it is a metaclass, call _class_resolveClassMethodthe dynamic parsing of the class method
    . b. After completing the dynamic parsing of the class method, query the imp in cls again. If it is not found, perform a dynamic parsing of the object method.

resolveInstanceMethodand resolveClassMethod. Also called dynamic resolution of a method.

lookUpImpOrForwardTryCache

resolveMethod_lockedReturning after the above execution method lookUpImpOrForwardTryCachePlease add image description
essentially calls _lookUpImpTryCachethe method

_lookUpImpTryCacheYou can find it here ; cache_getImpthat is to say, after making a dynamic resolution, you will also find a method from cache_getImpinside . If the method cannot be added dynamically, it will be executed once . When the method is entered, the value passed here will change. After entering the method for the second time , when this judgment is executedcacheselPlease add image description
(imp == NULL)?lookUpImpOrForwardlookUpImpOrForwardbehavior
Please add image description
lookUpImpOrForwardif (slowpath(behavior & LOOKUP_RESOLVER))

// 这里就是消息转发机制第一层的入口
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
    
    
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

According to the changed behaviorvalue and LOOKUP_RESOLVERthe relationship between the values, the if statement can only be entered for the first time, so this judgment is equivalent to a singleton. It explains why the dynamic analysis mentioned at the beginning resolveMethod_lockedis only executed once.

Enter message forwarding

If the system does not find an implementation during the dynamic resolution phase, it will enter the message forwarding phase. They are fast forwarding of messages and slow forwarding of messages.

When cachenot found imp, none of the method lists in the inheritance chain of the class are found imp, and resolveInstanceMethod/ resolveClassMethodreturns NO to enter message forwarding.

1. Fast forwarding (message recipient replacement)

The function of forwarding is that if the current object cannot respond to the message, it is forwarded to an object that can respond.

When an object cannot respond to a specific method , Objective-Cthe runtime will automatically call the object's forwardingTargetForSelector:method, giving the developer an opportunity to return an object that can respond to the method. The signature of this method is as follows:

- (id)forwardingTargetForSelector:(SEL)aSelector;

Developers can return an object that implements this method as needed in this method, so that the object can receive and process the message. The returned object will be used to receive messages and execute corresponding methods. If nil is returned, enter the next step of message forwarding mechanism.

Fast forwarding also replaces the message receiver, so if this class does not have the ability to process the message, then it will be forwarded to other classes and let other classes handle it.

//第二根稻草,使用快速消息转发,找其他对象来实现方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    
    if (aSelector == @selector(methodTwo)) {
    
    
        //也就是本类中的其他对象,此处选取的对象是forwardObject
        return self.forwardObject;
    }
    return nil;
}

2. Slow forwarding (complete message forwarding)

If forwardingTargetForSelectorthe method returns yes nil, then we have the last straw to grab full message forwarding. Compared with fast forwarding, not only the message recipient can be replaced, but also the method:

//第三根稻草,使用完全消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    
    if (aSelector == @selector(methodThree)) {
    
    
        NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return sig;
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    
    //选择一个函数去替换
    anInvocation.selector = @selector(methodNormal);
    //选择一个消息接收者(对象)去替换
    anInvocation.target = self.forwardObject;
    [anInvocation invoke];
}

There are two classes here, NSMethodSignatureand NSInvocation. where NSMethodSignatureis the method signature, which can be instantiated by the method's characters. NSInvocationIt is a method calling entity, which consists of targetand selectorand parameters.

The logic of this third life-saving straw is: first determine methodSignatureForSelectorwhether it has been implemented and the return value is not nil. If it has been implemented and the return value is not nil, then proceed to the next step to determine forwardInvocationwhether it has been implemented. If forwardInvocationit has been implemented, then use the method signature. Generate NSInvocationthe object and call forwardInvocationthe method, and finally return forwardInvocationthe execution result. If forwardInvocationthe method is not implemented, directly call doesNotRecognizeSelectorthe method to print the log and throw an exception. If methodSignatureForSelectorit is not implemented or the return value is nil, then directly call doesNotRecognizeSelectorthe method to print the log and throw an exception.

It should be noted that forwardInvocationthe :method methodSignatureForSelectorwill be called only when the :method returns a non-nil method signature. If methodSignatureForSelector: returns nil, then forwardInvocation: will not be triggered, but will enter the last step of message forwarding: dynamic method resolution or exception throwing.

Three rescues of message forwarding
Please add image description

Summarize

messaging

  1. The essence of OC method calling is message sending, and message sending is the search process of SEL-IMP.

dynamic resolution

  • There is no way to find it through the message sending mechanism. The system will also perform dynamic resolution before entering the message forwarding. (Divided into class method dynamic resolution and instance method dynamic resolution)

message forwarding

  • Dynamic resolution can't find a way to really enter the message forwarding stage.
  • Dynamic resolution, fast forwarding (recipient replacement), and slow forwarding (complete message forwarding) are collectively called three life-saving straws to prevent system crashes caused by method lookup.

fast:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    
    

Slow speed:

// 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
// 正向调用
- (void)forwardInvocation:(NSInvocation *)anInvocation;

The message forwarding mechanism is basically divided into three steps, also known as the three rescues of messages:

  1. Dynamic method parsing : Call resolveInstanceMethodor resolveClassMethodmethod, try to add an implementation to an unimplemented method (dynamic parsing)
  2. Backup receiver : Call and forwardingTargetForSelectortry to let other objects in this class execute this function (fast message forwarding)
  3. Complete message forwarding : If fast forwarding is not performed, you can use methodSignatureForSeletorand frowardInvocationto perform complete message forwarding, not only to replace the message recipient, but also to replace the method. (Full message forward)

Attached are two summaries of the boss’s flowcharts:
Principles of message sending and forwarding mechanisms
Insert image description here

Insert image description here

  • The runtime finds the corresponding IMP implementation through the selector: cache search - current class search - parent class search step by step.
  • The subclass calls the method of the parent class and caches the currently passed in class.

The study during the winter vacation was relatively shallow, because I didn't come into contact with the underlying classes and objects of OC. I slowly learned the relationships between metaclasses, parent classes, and some underlying principles. It became easier to see the message sending and forwarding mechanism.

Guess you like

Origin blog.csdn.net/weixin_61639290/article/details/131299200