Runtime
Interview Questions:
- What’s the difference between a compile-time language and a dynamic runtime language?
- What is the difference between a message and a function call?
- How does the system forward messages to us when our methods are not implemented?
Research Focus of this paper
- The data structure
- Class objects and metaclass objects
- Messaging mechanism
- How does a method cache look up a method cache
- forward
- Dynamic addition method
- 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
- class_ro_t
- Protocols list_array_tt TWO-DIMENSIONAL array
- Properties list_array_tt two-dimensional array
- 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:
- The name of the
- The return value
- parameter
- 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
- 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)
- 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.
- 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
- forward
- Dynamic addition method
- 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
- Matters needing attention
Dynamic addition method
Have you ever used peformSlector:? There are many things involved, possibly dynamic addition methods.
- 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
- 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