Runtime

Interview Questions:

  1. What’s the difference between a compile-time language and a dynamic runtime language?
  2. What is the difference between a message and a function call?
  3. How does the system forward messages to us when our methods are not implemented?

Research Focus of this paper

  1. The data structure
  2. Class objects and metaclass objects
  3. Messaging mechanism
  4. How does a method cache look up a method cache
  5. forward
  6. Dynamic addition method
  7. Dynamic method parsing

1. Data structure

  • Objc_object
  • objc_class
  • Isa pointer
  • Method_t

Id = objc_object

typedef struct objc_object *id;

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA(a);

    // getIsa() allows this to be a tagged pointer object
    Class getIsa(a);

    // initIsa() should be used to init the isa of new objects only.
    // If this object already has an isa, use changeIsa() for correctness.
    // initInstanceIsa(): objects with no custom RR/AWZ
    // initClassIsa(): class objects
    // initProtocolIsa(): protocol objects
    // initIsa(): other objects
    void initIsa(Class cls /*indexed=false*/);
    void initClassIsa(Class cls /*indexed=maybe*/);
    void initProtocolIsa(Class cls /*indexed=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);

    // changeIsa() should be used to change the isa of existing objects.
    // If this is a new object, use initIsa() for performance.
    Class changeIsa(Class newCls);

    bool hasIndexedIsa(a);
    bool isTaggedPointer(a);
    bool isClass(a);

    // object may have associated objects?
    bool hasAssociatedObjects(a);
    void setHasAssociatedObjects(a);

    // object may be weakly referenced?
    bool isWeaklyReferenced(a);
    void setWeaklyReferenced_nolock(a);

    // object may have -.cxx_destruct implementation?
    bool hasCxxDtor(a);

    // Optimized calls to retain/release methods
    id retain(a);
    void release(a);
    id autorelease(a);

    // Implementations of retain/release methods
    id rootRetain(a);
    bool rootRelease(a);
    id rootAutorelease(a);
    bool rootTryRetain(a);
    bool rootReleaseShouldDealloc(a);
    uintptr_t rootRetainCount(a);

    // Implementation of dealloc methods
    bool rootIsDeallocating(a);
    void clearDeallocating(a);
    void rootDealloc(a);

private:
    void initIsa(Class newCls, bool indexed, bool hasCxxDtor);

    // Slow paths for inline control
    id rootAutorelease2(a);
    bool overrelease_error(a);

#if SUPPORT_NONPOINTER_ISA
    // Unified retain count manipulation for nonpointer isa
    id rootRetain(bool tryRetain, bool handleOverflow);
    bool rootRelease(bool performDealloc, bool handleUnderflow);
    id rootRetain_overflow(bool tryRetain);
    bool rootRelease_underflow(bool performDealloc);

    void clearDeallocating_slow(a);

    // Side table retain count overflow for nonpointer isa
    void sidetable_lock(a);
    void sidetable_unlock(a);

    void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
    bool sidetable_addExtraRC_nolock(size_t delta_rc);
    size_t sidetable_subExtraRC_nolock(size_t delta_rc);
    size_t sidetable_getExtraRC_nolock(a);
#endif

    // Side-table-only retain count
    bool sidetable_isDeallocating(a);
    void sidetable_clearDeallocating(a);

    bool sidetable_isWeaklyReferenced(a);
    void sidetable_setWeaklyReferenced_nolock(a);

    id sidetable_retain(a);
    id sidetable_retain_slow(SideTable& table);

    uintptr_t sidetable_release(bool performDealloc = true);
    uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);

    bool sidetable_tryRetain(a);

    uintptr_t sidetable_retainCount(a);
#if DEBUG
    bool sidetable_present(a);
#endif
};

Copy the code

Objc_object also provides additional information

isa_t

Related to ISA operation

Weak reference correlation

Associated object correlation

Memory management related

** objc_class**

typedef struct objc_class *Class;
// Inherits from objc_object
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data(a) { 
        return bits.data(a); }void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        assert(isFuture() | |isRealized());
        data() - >setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        assert(isFuture() | |isRealized());
        data() - >clearFlags(clear);
    }

    // set and clear must not overlap
    void changeInfo(uint32_t set, uint32_t clear) {
        assert(isFuture() | |isRealized());
        assert((set & clear) == 0);
        data() - >changeFlags(set, clear);
    }

    bool hasCustomRR(a) {
        return ! bits.hasDefaultRR(a); }void setHasDefaultRR(a) {
        assert(isInitializing());
        bits.setHasDefaultRR(a); }void setHasCustomRR(bool inherited = false);
    void printCustomRR(bool inherited);

    bool hasCustomAWZ(a) {
        return ! bits.hasDefaultAWZ(a); }void setHasDefaultAWZ(a) {
        assert(isInitializing());
        bits.setHasDefaultAWZ(a); }void setHasCustomAWZ(bool inherited = false);
    void printCustomAWZ(bool inherited);

    bool requiresRawIsa(a) {
        return bits.requiresRawIsa(a); }void setRequiresRawIsa(bool inherited = false);
    void printRequiresRawIsa(bool inherited);

    bool canAllocIndexed(a) {
        assert(!isFuture());
        return !requiresRawIsa(a); }bool canAllocFast(a) {
        assert(!isFuture());
        return bits.canAllocFast(a); }bool hasCxxCtor(a) {
        // addSubclass() propagates this flag from the superclass.
        assert(isRealized());
        return bits.hasCxxCtor(a); }void setHasCxxCtor(a) { 
        bits.setHasCxxCtor(a); }bool hasCxxDtor(a) {
        // addSubclass() propagates this flag from the superclass.
        assert(isRealized());
        return bits.hasCxxDtor(a); }void setHasCxxDtor(a) { 
        bits.setHasCxxDtor(a); }bool isSwift(a) {
        return bits.isSwift(a); }#if SUPPORT_NONPOINTER_ISA
    // Tracked in non-pointer isas; not tracked otherwise
#else
    bool instancesHaveAssociatedObjects(a) {
        // this may be an unrealized future class in the CF-bridged case
        assert(isFuture() | |isRealized());
        return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
    }

    void setInstancesHaveAssociatedObjects(a) {
        // this may be an unrealized future class in the CF-bridged case
        assert(isFuture() | |isRealized());
        setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
    }
#endif

    bool shouldGrowCache(a) {
        return true;
    }

    void setShouldGrowCache(bool) {
        // fixme good or bad for memory use?
    }

    bool shouldFinalizeOnMainThread(a) {
        // finishInitializing() propagates this flag from the superclass.
        assert(isRealized());
        return data()->flags & RW_FINALIZE_ON_MAIN_THREAD;
    }

    void setShouldFinalizeOnMainThread(a) {
        assert(isRealized());
        setInfo(RW_FINALIZE_ON_MAIN_THREAD);
    }

    bool isInitializing(a) {
        return getMeta() - >data()->flags & RW_INITIALIZING;
    }

    void setInitializing(a) {
        assert(!isMetaClass());
        ISA() - >setInfo(RW_INITIALIZING);
    }

    bool isInitialized(a) {
        return getMeta() - >data()->flags & RW_INITIALIZED;
    }

    void setInitialized(a);

    bool isLoadable(a) {
        assert(isRealized());
        return true;  // any class registered for +load is definitely loadable
    }

    IMP getLoadMethod(a);

    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isRealized(a) {
        return data()->flags & RW_REALIZED;
    }

    // Returns true if this is an unrealized future class.
    // Locking: To prevent concurrent realization, hold runtimeLock.
    bool isFuture(a) { 
        return data()->flags & RW_FUTURE;
    }

    bool isMetaClass(a) {
        assert(this);
        assert(isRealized());
        return data()->ro->flags & RO_META;
    }

    // NOT identical to this->ISA when this is a metaclass
    Class getMeta(a) {
        if (isMetaClass()) return (Class)this;
        else return this->ISA(a); }bool isRootClass(a) {
        return superclass == nil;
    }
    bool isRootMetaclass(a) {
        return ISA() == (Class)this;
    }

    const char *mangledName(a) { 
        // fixme can't assert locks here
        assert(this);

        if (isRealized() | |isFuture()) {
            return data()->ro->name;
        } else {
            return ((const class_ro_t *)data())->name; }}const char *demangledName(bool realize = false);
    const char *nameForLogging(a);

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize(a) {
        assert(isRealized());
        return data()->ro->instanceSize;
    }

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize(a) {
        return word_align(unalignedInstanceSize());
    }

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

    void setInstanceSize(uint32_t newSize) {
        assert(isRealized());
        if(newSize ! =data()->ro->instanceSize) {
            assert(data()->flags & RW_COPIED_RO);
            *const_cast<uint32_t* > (&data()->ro->instanceSize) = newSize;
        }
        bits.setFastInstanceSize(newSize); }};Copy the code
  • Isa pointer
/ / share
union isa_t 
{
    isa_t() {}isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;

#if SUPPORT_NONPOINTER_ISA

    // extra_rc must be the MSB-most field (so it matches carry/overflow flags)
    // indexed must be the LSB (fixme or get rid of it)
    // shiftcls must occupy the same bits that a real class pointer would
    // bits + RC_ONE is equivalent to extra_rc + 1
    // RC_HALF is the high bit of extra_rc (i.e. half of its range)

    // future expansion:
    // uintptr_t fast_rr : 1; // no r/r overrides
    // uintptr_t lock : 2; // lock for atomic property, @synch
    // uintptr_t extraBytes : 1; // allocated with extra bytes

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (1ULL<<7)
    };

# else
    // Available bits in isa field are architecture-specific.
#   error unknown architecture
# endif

// SUPPORT_NONPOINTER_ISA
#endif

};
Copy the code

For non-pointer isAs, it really only takes 30 or 40 places to save the address.

Question: What does the ISA pointer mean when?

There are pointer isa and non-pointer ISA.

Isa to

With respect to objects, they refer to class objects.

For class objects, they refer to metaclass objects.

Calling the instance method actually looks up the class object through the ISA pointer.

cache_t

A function used to quickly find the execution of a method

Is a hash table structure that can be incrementally expanded, structure storage will also increase dynamically.

It is the best application of locality principle

struct cache_t {
    struct bucket_t* _buckets;
    mask_t _mask;
    mask_t _occupied;

public:
    struct bucket_t *buckets(a);
    mask_t mask(a);
    mask_t occupied(a);
    void incrementOccupied(a);
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty(a);

    mask_t capacity(a);
    bool isConstantEmptyCache(a);
    bool canBeFreed(a);

    static size_t bytesForCapacity(uint32_t cap);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);

    void expand(a);
    void reallocate(mask_t oldCapacity, mask_t newCapacity);
    struct bucket_t * find(cache_key_t key, id receiver);

    static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};

Copy the code

bucket_t

struct bucket_t {
private:
    cache_key_t _key; // selector corresponding to OC
    IMP _imp;  // Untyped function pointer

public:
    inline cache_key_t key(a) const { return _key; }
    inline IMP imp(a) const { return (IMP)_imp; }
    inline void setKey(cache_key_t newKey) { _key = newKey; }
    inline void setImp(IMP newImp) { _imp = newImp; }

    void set(cache_key_t newKey, IMP newImp);
};
Copy the code

class_data_bits_t

This is mainly the encapsulation of class_rw_t

Class_rw_t represents the encapsulation of class_ro_t.

Class_ro_t represents class-related read-only information.

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit)
    {
        return bits & bit;
    }

#if FAST_ALLOC
    static uintptr_t updateFastAlloc(uintptr_t oldBits, uintptr_t change)
    {
        if (change & FAST_ALLOC_MASK) {
            if(((oldBits & FAST_ALLOC_MASK) == FAST_ALLOC_VALUE) && ((oldBits >> FAST_SHIFTED_SIZE_SHIFT) ! =0)) 
            {
                oldBits |= FAST_ALLOC;
            } else{ oldBits &= ~FAST_ALLOC; }}return oldBits;
    }
#else
    static uintptr_t updateFastAlloc(uintptr_t oldBits, uintptr_t change) {
        return oldBits;
    }
#endif

    void setBits(uintptr_t set) 
    {
        uintptr_t oldBits;
        uintptr_t newBits;
        do {
            oldBits = LoadExclusive(&bits);
            newBits = updateFastAlloc(oldBits | set, set);
        } while (!StoreReleaseExclusive(&bits, oldBits, newBits));
    }

    void clearBits(uintptr_t clear) 
    {
        uintptr_t oldBits;
        uintptr_t newBits;
        do {
            oldBits = LoadExclusive(&bits);
            newBits = updateFastAlloc(oldBits & ~clear, clear);
        } while (!StoreReleaseExclusive(&bits, oldBits, newBits));
    }

public:

    class_rw_t* data(a) {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        assert(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        bits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
    }

    bool hasDefaultRR(a) {
        return getBit(FAST_HAS_DEFAULT_RR);
    }
    void setHasDefaultRR(a) {
        setBits(FAST_HAS_DEFAULT_RR);
    }
    void setHasCustomRR(a) {
        clearBits(FAST_HAS_DEFAULT_RR);
    }

#if FAST_HAS_DEFAULT_AWZ
    bool hasDefaultAWZ(a) {
        return getBit(FAST_HAS_DEFAULT_AWZ);
    }
    void setHasDefaultAWZ(a) {
        setBits(FAST_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ(a) {
        clearBits(FAST_HAS_DEFAULT_AWZ);
    }
#else
    bool hasDefaultAWZ(a) {
        return data()->flags & RW_HAS_DEFAULT_AWZ;
    }
    void setHasDefaultAWZ(a) {
        data() - >setFlags(RW_HAS_DEFAULT_AWZ);
    }
    void setHasCustomAWZ(a) {
        data() - >clearFlags(RW_HAS_DEFAULT_AWZ);
    }
#endif

#if FAST_HAS_CXX_CTOR
    bool hasCxxCtor(a) {
        return getBit(FAST_HAS_CXX_CTOR);
    }
    void setHasCxxCtor(a) {
        setBits(FAST_HAS_CXX_CTOR);
    }
#else
    bool hasCxxCtor(a) {
        return data()->flags & RW_HAS_CXX_CTOR;
    }
    void setHasCxxCtor(a) {
        data() - >setFlags(RW_HAS_CXX_CTOR);
    }
#endif

#if FAST_HAS_CXX_DTOR
    bool hasCxxDtor(a) {
        return getBit(FAST_HAS_CXX_DTOR);
    }
    void setHasCxxDtor(a) {
        setBits(FAST_HAS_CXX_DTOR);
    }
#else
    bool hasCxxDtor(a) {
        return data()->flags & RW_HAS_CXX_DTOR;
    }
    void setHasCxxDtor(a) {
        data() - >setFlags(RW_HAS_CXX_DTOR);
    }
#endif

#if FAST_REQUIRES_RAW_ISA
    bool requiresRawIsa(a) {
        return getBit(FAST_REQUIRES_RAW_ISA);
    }
    void setRequiresRawIsa(a) {
        setBits(FAST_REQUIRES_RAW_ISA);
    }
#else
# if SUPPORT_NONPOINTER_ISA
#   error oops
# endif
    bool requiresRawIsa(a) {
        return true;
    }
    void setRequiresRawIsa(a) {
        // nothing
    }
#endif

#if FAST_ALLOC
    size_t fastInstanceSize(a) 
    {
        assert(bits & FAST_ALLOC);
        return (bits >> FAST_SHIFTED_SIZE_SHIFT) * 16;
    }
    void setFastInstanceSize(size_t newSize) 
    {
        // Set during realization or construction only. No locking needed.
        assert(data()->flags & RW_REALIZING);

        // Round up to 16-byte boundary, then divide to get 16-byte units
        newSize = ((newSize + 15) & ~15) / 16;
        
        uintptr_t newBits = newSize << FAST_SHIFTED_SIZE_SHIFT;
        if ((newBits >> FAST_SHIFTED_SIZE_SHIFT) == newSize) {
            int shift = WORD_BITS - FAST_SHIFTED_SIZE_SHIFT;
            uintptr_t oldBits = (bits << shift) >> shift;
            if((oldBits & FAST_ALLOC_MASK) == FAST_ALLOC_VALUE) { newBits |= FAST_ALLOC; } bits = oldBits | newBits; }}bool canAllocFast(a) {
        return bits & FAST_ALLOC;
    }
#else
    size_t fastInstanceSize(a) {
        abort(a); }void setFastInstanceSize(size_t) {
        // nothing
    }
    bool canAllocFast(a) {
        return false;
    }
#endif

    bool isSwift(a) {
        return getBit(FAST_IS_SWIFT);
    }

    void setIsSwift(a) {
        setBits(FAST_IS_SWIFT); }};Copy the code

Class_rw_t

It basically contains

  1. class_ro_t
  2. Protocols list_array_tt TWO-DIMENSIONAL array
  3. Properties list_array_tt two-dimensional array
  4. Methods list_array_TT two-dimensional array

struct class_rw_t {
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

    void setFlags(uint32_t set) 
    {
        OSAtomicOr32Barrier(set, &flags);
    }

    void clearFlags(uint32_t clear) 
    {
        OSAtomicXor32Barrier(clear, &flags);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        assert((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t*)&flags)); }};Copy the code

Class_ro_t

Contains several properties

name

BaseMethodList One-dimensional array

BaseProtocols One-dimensional array

BaseProperties one-dimensional array

Ivars one-dimensional array

Store the original definition of some method list, property list, protocol list.

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods(a) const {
        returnbaseMethodList; }};Copy the code
  • Method_t

Four elements of the function:

  1. The name of the
  2. The return value
  3. parameter
  4. The body of the function

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator(a) (const method_t& lhs,
                         const method_t& rhs)
        { returnlhs.name < rhs.name; }}; };Copy the code

Types encoding rule

IMP SEL

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

/// A pointer to the function of a method implementation. 
#if! OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... * / ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

Copy the code

As a whole to

  1. Class objects and metaclass objects

The instance object uses the ISA pointer to find its class object, which stores a list of methods.

The class object finds its metaclass object through the ISA pointer. A metaclass object stores a list of class methods.

Both class objects and metaclass objects are objC_class data structures, and since objC_class inherits from ObjC_Object, they both have isa Pointers, which in turn implement both of the above described points. (Must be complete, plus)

  1. Does it crash if we call a class method with no corresponding implementation, but an instance method with the same name? Will there be an actual call?

The superClass of the root metaclass points to the root class object. When we look for the class method in the metaclass object, the class method cannot be found, and we will follow the pointer to the instance method. If there is a method with the same name, the instance method call is executed.

Key point: Instance methods of the same name are written to NSObject, existing instance methods are either added by class.

  1. Messaging mechanism

Call the instance method and follow the ISA pointer to the instance object to find the class object, which in turn follows the Superclass

Call the class method, follow the isa pointer of the class object to find the metaclass object, and the metaclass object in turn follows the superclass until it finds Nil.

The interview questions

@interface Person(a)
@end

@implementation Person

- (instancetype)init{
    self = [super init];
    if (self) {
        
        // What is the print result
        NSLog(@"% @".NSStringFromClass([self class]));
        NSLog(@"% @".NSStringFromClass([super class]));
    }
    return self;
}
@end
Copy the code

The super compiler keyword is converted to a structure like objc_super

Cache lookup

How does method cache search method cache? What is the process

If the cache of the current class is not there, does it look up the cache of the parent class, does it find the parent class, does it put it in the cache list, and if it’s cached, does it cache the cache list of the parent class or does it cache the current class?

F (key) : hash lookup key&mask

Find in the current class

For the sorted list, binary search algorithm is used to find the corresponding execution function of the method.

For the unsorted list, the general traversal search algorithm is used to find the corresponding execution function of the method.

In messaging, when is the list of methods sorted and when is it not sorted?

/*********************************************************************** * getMethodNoSuper_nolock * fixme * Locking: runtimeLock must be read- or write-locked by the caller * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp(a);int methodListHasExpectedSize = mlist->entsize() = =sizeof(method_t);
    
  	 // Determine the order of binary search
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        // linear traversal search
        for (auto& meth : *mlist) {
            if (meth.name == sel) return&meth; }}#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not"); }}}#endif

    return nil;
}
Copy the code

Look up the parent class level by level

  1. forward
  2. Dynamic addition method
  3. Dynamic method parsing

/*********************************************************************** * lookUpImpOrForward. * The standard IMP lookup. * initialize==NO tries to avoid +initialize (but sometimes fails) * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere) * Most callers should use initialize==YES and cache==YES. * inst is an instance of cls or a subclass thereof, or nil if none is known. * If cls is an un-initialized metaclass then a non-nil inst is faster. * May return _objc_msgForward_impcache. IMPs destined for external use * must be converted to _objc_msgForward or _objc_msgForward_stret. * If you don't want forwarding at all, use lookUpImpOrNil() instead. **********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked(a);// Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    if(! cls->isRealized()) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(cls);
    }

    if(initialize && ! cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    // The lock is held 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.
 retry:
    runtimeLock.read(a);// Ignore GC selectors
    if (ignoreSelector(sel)) {
        imp = _objc_ignored_method;
        cache_fill(cls, sel, imp, inst);
        goto done;
    }

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.

    meth = getMethodNoSuper_nolock(cls, sel);
    if (meth) {
       // Cache the current class
        log_and_fill_cache(cls, meth->imp, sel, inst, cls);
        imp = meth->imp;
        goto done;
    }

    // Try superclass caches and method lists.

    curClass = cls;
    while ((curClass = curClass->superclass)) {
        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (imp) {
            if(imp ! = (IMP)_objc_msgForward_impcache) {// Found the method in a superclass. Cache it in this class.
              	// Cache the current class
                log_and_fill_cache(cls, imp, sel, inst, curClass);
                goto done;
            }
            else {
                // Found a forward:: entry in a superclass.
                // Stop searching, but don't cache yet; call method 
                // resolver for this class first.
                break; }}// Superclass method list.
        meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
           // Cache the current class
            log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
            imp = meth->imp;
            gotodone; }}// No implementation found. Try method resolver once.

    if(resolver && ! triedResolver) { runtimeLock.unlockRead(a); _class_resolveMethod(cls, sel, inst);// Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead(a);// paranoia: look for ignored selectors with non-ignored implementations
    assert(! (ignoreSelector(sel) && imp ! = (IMP)&_objc_ignored_method));// paranoia: never let uncached leak out
    assert(imp ! = _objc_msgSend_uncached_impcache);return imp;
}

Copy the code

forward

Method-Swizzling

  1. Matters needing attention

Dynamic addition method

Have you ever used peformSlector:? There are many things involved, possibly dynamic addition methods.

  1. When is the dynamic add method needed?

A common scenario would be a variable property defined at sign Dynamic, where you might dynamically add setter getter methods

It is also common to add a method to a receiving object when forwarding a message if it does not have this method

  1. The function that returns the first step of message forwarding, whether it returns yes or no, as long as you add a method dynamically, after the method list is updated, the system will automatically reset the message passing mechanism and restart the method lookup.

The first time the system calls resolveInstanceMethod, it registers the method and returns yes. If this step returns no, the system will forward forward the forwardTarget callback to seek forward. If this step is not processed then the system will make a final processing for the For wardInvocation