Flow chart:

Yulingtianxia.com/blog/2016/0…

Method_t Memory structure

IOS14 used to be big

The one that was decided upon after iOS14 during build time was small, and the method that was added at run time was big

The core principles of Small are as follows: The offset of the address implemented by method is fixed in the program and does not deviate from the address range of the current library. Therefore, the 32-bit offset can be used to record the address. Using the address loaded by the program + offset to replace the real address can reduce the memory occupied by method, and can also optimize the previous rebase. The time required to repair the real address of Method

In iOS14, if setImp is performed on a method and you can’t use small to store it directly (on the one hand, the method added at runtime is of type big, on the other hand, you can’t use 32-bit offset to do this), there will be a global map to manage it. Key is the address for method_t, and value is the value for IMP

struct method_t {
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    struct small {
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP> imp;
    }
    small &small(a) const {
        return *(struct small *)((uintptr_t)this& ~ (uintptr_t)1);
    }
    big &big(a) const {
        ASSERT(!isSmall());
        return *(struct big *)this; }}Copy the code
template <typename T>
struct RelativePointer: nocopy_t {
    int32_t offset; // This is a 32-bit signed offset that provides a range of ±2GB.

    T get(a) const {
        if (offset == 0)
            return nullptr;
        uintptr_t base = (uintptr_t)&offset;
        uintptr_t signExtendedOffset = (uintptr_t) (intptr_t)offset;
        uintptr_t pointer = base + signExtendedOffset;
        return(T)pointer; }};Copy the code

After iOS14, there’s an optimization to the method and you can see it in detail here

Methods the cache

Even methods found in the list of superclass methods will eventually be cached in their own cache

+ (BOOL)resolveInstanceMethod:(SEL)sel

  1. And you can do it in this methodruntimeAdd a method
  2. In the method search, found in the method list can not find, if it is found that the method is implemented, it will execute the method and mark the dynamic parsing, re-enterlookUpImpOrForwardAnd will not be dynamically resolved again in this process
  3. The returned value will only affect the system information printing, and does not affect the forwarding process (it is recommended to follow the system advice and return YES after method addition).

– (id)forwardingTargetForSelector:(SEL)selector

In the process of message forwarding, this method can be used to forward its unrealized method to other objects. This method can be used in conjunction with protocol to realize multiple inheritance in a disguised manner

– (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

To work with the Runtime, the compiler encodes both the method return type and the parameter type as a string, the types of method_t.

When the method is called, the objc_MSgsend function is converted and passed the message receiver, the method name corresponding to the message, and the actual parameter list

Types can then be used to receive parameters with appropriate types and return the return value

NSMethodSignature is an OC wrapper around different types. It allows you to easily obtain the number of parameters, types, and return value types of a method_t method. It has nothing to do with SEL and IMP for method_t

@interface NSMethodSignature : NSObject

+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;

@property (readonly) NSUInteger numberOfArguments;
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;

@property (readonly) NSUInteger frameLength;

- (BOOL)isOneway;

@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;
@property (readonly) NSUInteger methodReturnLength;

@end
Copy the code

– (void)forwardInvocation:(NSInvocation *)anInvocation

NSInvocation is like an OC encapsulation for message sending, very flexible

NSInvocation can be used to get the actual parameter value that was passed in, change some of the parameters that were passed in, change the message receiver, set the SEL to be called, and set the return value

NSInvocation can be used for cross-component communication or to call third-party library private functions, similar to -PerformSelector: but with unlimited arguments (downside: Hard coding to use reflection, extra risk)

@interface NSInvocation : NSObject

+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

@property (readonly.retain) NSMethodSignature *methodSignature;

- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;

@property (nullable.assign) id target;
@property SEL selector;

- (void)getReturnValue:(void *)retLoc;
- (void)setReturnValue:(void *)retLoc;

- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

- (void)invoke;
- (void)invokeWithTarget:(id)target;

@end
Copy the code

The source code

Search method address

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(a);if (slowpath(! cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }

    runtimeLock.lock(a);checkIsKnownClass(cls);
   
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked(a); curClass = cls;// In the inheritance chain, look up the method cache and method list from low to high
    for (unsigned attempts = unreasonableClassCount();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            imp = cache_getImp(curClass, sel);
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass(a);#endif
        } else {
            // curClass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            // If no method is found up to the root class, mark IMP as' forward_IMP '
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break; }}// Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        // Find the parent method cache
        imp = cache_getImp(curClass, sel);
        // If 'imp' is found to be 'foward_IMP', it means to forward the message, directly break
        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)) {
            // Found the method in a superclass. Cache it in this class.
            gotodone; }}// No implementation found. Try method resolver once.

    // The method is dynamically resolved
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
    // indicates that the method has been parsed
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass(a); }#endif
// Cache the method found
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock(a);if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}
Copy the code

Method dynamic analysis

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked(a);ASSERT(cls->isRealized());

    runtimeLock.unlock(a);if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, 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);
}

Copy the code