Isa pointer

Data structures in ISA

We know that when a message is sent to an object, the object uses its ISA pointer to find the class to which it belongs, and then looks for the method implementation in that class. So the ISA pointer should store information about the class address, but if you look at the objC source code (I’m using objC4-781 here) you’ll see that the ISA pointer in the object data structure looks like this:

Objc-private. h file:

struct objc_object { private: isa_t isa; // isa pointer isa union type public: // more... // more ... // more ...Copy the code

Definition of ISA_t:

union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; // Uintptr_t bits; #if defined(ISA_BITFIELD) struct {ISA_BITFIELD; // defined in isa.h }; #endif };Copy the code

Check isa’s specific ISA_BITFIELD interpretation information in isa.h (currently copied is arm64 architecture type)

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 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)
Copy the code

We know that on the ARM64 architecture, isa Pointers are 64 bits, or 8 bytes. Struct {ISA_BITFIELD; struct {ISA_BITFIELD; // defined in isa.h }; Seems to store a lot of content, where does it fit? This is because ISA uses a Union data structure,

Struct {ISA_BITFIELD; struct {ISA_BITFIELD; struct {ISA_BITFIELD; // defined in isa.h }; It doesn’t actually store any content, and its main purpose is to tell you what the internal memory layout is. The specific ISA information is stored in the Uintptr_t bits guy. When the specific data for storage or removed using similar & (and), | (or),! (non) operation, and then fetch information in a range of 64-bit memory.

The contents stored in isa

  • If nonpointer is 0, the ISA pointer points directly to a class or metaclass. 1 indicates that the ISA pointer is optimized to use bitfields to hold more information

  • Has_assoc indicates whether the associated object has been set. If not, the memory will be freed faster.

  • Has_cxx_dtor releases c++ destructors

  • Shiftcls stores the memory address of this Class or Meta_Class

  • Magic debugging determines if an object is not initialized

  • Weakly_referenced Whether a weak reference is set

  • Whether the DealLocating object is being released

  • Extra_rc stores reference counts (currently allocated 19 bits, so it can still store a lot)

  • The has_SIDETABLE_rc reference count is already full in the ISA pointer. If it is 1, the reference count is stored in a SideTable property

Therefore, there isa lot of information stored in isa.

Method lookup

In Objective-C, messages are sent in three phases:

  • Method to find the

  • Dynamic message parsing

  • forward

A classic method lookup graph:

Related data structures in the class

In the file objc-Runtime-new.h

You can see a structure at the bottom of the method like this:

struct method_t { SEL name; // const char *types; // Code (return value type, parameter type) MethodListIMP IMP; // function implementation address};Copy the code

The encoding Type of the method can be viewed in detail here: Type Encodings

The Class Class stores the following information:

struct objc_class : objc_object { Class ISA; // isa pointer to Class superclass; // Parent pointer cache_t cache; // Store cache methods such as class_datA_bits_t bits; // Specific information //... more }Copy the code

Class_data_bits_t struct:

Class_rw_t // Readable and writable information class_ro_t // Read-only informationCopy the code

Class_rw_t:

Method_array_t // Method list protocol_T // Protocol list protocol_T // protocol list protocol_T //... moreCopy the code

Class_ro_t Stores basic class information:

const uint8_t * ivarLayout; const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; // Uint8_t * weakIvarLayout; // Uint8_t * weakIvarLayout; property_list_t *baseProperties; / /... moreCopy the code

Method Lookup process

In Objective-C, the flow of sending a message to an object looks like this:

Method caches the data structure of cache_t

As we saw above, object method lookups have a cache_t, which caches related methods called by the object and uses hash tables to make lookups more efficient

Cache_t Specifies the approximate data structure:

<struct bucket *> buckets // bucket_t, bucket_t, bucket_t... <mask_t> _mask // Hash length -1 uint16_t _occupied // The number of cached methodsCopy the code

bucket_t:

_sel // the method name is key_imp // the method implementation address is valueCopy the code

In objC source code, you can find the cache method insert:

If the cache container is full, the previous cache method is emptied and the cache capacity is doubled.

Dynamic method parsing

Object -> superClass -> superClass -> superClass… – > NSObject. But if it doesn’t find it until NSObject, then it goes into method dynamic parsing

During the dynamic method resolution phase, developers can dynamically add implementations to methods.

Dynamic method resolution includes:

  • + (BOOL)resolveInstanceMethod:(SEL) dynamic resolution of SEL object methods

  • + (BOOL)resolveClassMethod:(SEL) dynamic resolution of SEL class methods

An example of implementing a method that dynamically resolves an object:

@implementation Cat + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { Method method = class_getInstanceMethod(self, @selector(lotOfFish)); class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method)); Return true; } return [super resolveInstanceMethod:sel]; } - (void)lotOfFish{ NSLog(@"%s", __func__); } @endCopy the code

Test code:

int main(int argc, const char * argv[]) { @autoreleasepool { Cat *cat = [[Cat alloc] init]; [cat performSelector:@selector(eat)]; // cat does not have eat method, or does not implement} return 0; }Copy the code

Output:

2021-01-17 11:18:07.157671+0800 Runtime[66738:1460934] -[Cat lotOfFish]
Copy the code

+ (BOOL)resolveInstanceMethod:(SEL) SEL

The dynamic resolution flow of class methods is the same as that of objects, so add custom method implementations.

(Note that after dynamic method parsing is added, the object will go through the message lookup again.)

The general flow chart is as follows:

forward

What if dynamic method resolution is not implemented? At this point, the message forwarding phase will come. In the message forwarding phase, developers can:

  • Forwarding methods to other objects for processing;

  • Capture methods, add method signatures, and then method forwarding (or do nothing at all).

The method is forwarded to other objects to process the message

Add a Dog class:

@interface Dog : NSObject - (void)eat; - (void)eat {NSLog(@" eat Dog food"); } @endCopy the code

Cat class to achieve message forwarding:

@ implementation Cat - (id) forwardingTargetForSelector aSelector: (SEL) {if (aSelector = = @ the selector (eat)) {/ / message is forwarded to the dog Return [[Dog alloc] init]; } / / other logical constant return [super forwardingTargetForSelector: aSelector]; } @endCopy the code

Test code:

int main(int argc, const char * argv[]) { @autoreleasepool { Cat *cat = [[Cat alloc] init]; // Cat has no eat method [Cat performSelector:@selector(eat)]; } return 0; }Copy the code

Output:

2021-01-17 11:49:20.044035+0800 Runtime[67857:1479538] Eat dog food
Copy the code

As you can see, when cat executes eat, since cat does not have that method and its dynamic method parsing phase does not add its own method implementation, So at this time will come forward phase – (id) forwardingTargetForSelector aSelector: (SEL). In this method you can forward captured methods to other objects for execution.

Add method signature & processing

If there are no realize the first step forward phase – (id) forwardingTargetForSelector (SEL) aSelector method, then the method will be called after:

  1. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

  2. - (void)forwardInvocation:(NSInvocation *)anInvocation

Cat test class add implementation code:

@ implementation Cat / / the method signature - (NSMethodSignature *) methodSignatureForSelector (SEL) aSelector {if (aSelector = = @selector(eat)) { return [NSMethodSignature signatureWithObjCTypes:"v@:"]; / / parameter types can see apple's website} return [super methodSignatureForSelector: aSelector]; } - (void)forwardInvocation:(NSInvocation *)anInvocation {// this can be used to intercept all the missing methods of this class, NSLog(@" No invocation: %s", anInvocation. Selector) will not crash if the message is not found. } @endCopy the code

Testing:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Cat *cat = [[Cat alloc] init];
        [cat performSelector:@selector(eat)];
    }
    return 0;
}
Copy the code

Output:

2021-01-17 11:57:40.963118+0800 Runtime[68203:1486085Copy the code

The general process of message forwarding is as follows:

Some introduction to objC apis

related

Create a class dynamically (parameter: Parent class, class name, Extra memory space) Class objc_allocateClassPair(Class superclass, const char *name, Void objc_registerClassPair(Class CLS) Destroy a Class void objc_disposeClassPair(Class) Class object_getClass(id obj); CLS; Class CLS) Determines whether an OC object is aClass BOOL object_isClass(ID obj) Determines whether aClass is a metaclass. BOOL class_isMetaClass(Class CLS) Retrieves the parent Class class_getSuperclass(Class cls)Copy the code

A simple test:

#import <Foundation/Foundation.h> #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { Class Cat = objc_allocateClassPair([NSObject class], "Cat", 0); // register a class and its metaclass class_addIvar(Cat, "_age", 4, 1, @encode(int)); // Add member variables (must precede class memory registration) objc_registerClassPair(Cat); // register memory id cat = [[cat alloc] init]; [cat setValue:@5 forKey:@"_age"]; NSLog(@"cat age = %@", [cat valueForKey:@"_age"]); Cat age = 5} return 0; }Copy the code

Member variables

Ivar class_getInstanceVariable(Class CLS, Const char *name) Ivar *class_copyIvarList(Class CLS, Void object_setIvar(id obj, Ivar Ivar, id value) id object_getIvar(id obj, BOOL class_addIvar(Class CLS, const char * name, size_t size, BOOL class_addIvar(Class CLS, const char * name, size_t size, uint8_t alignment, Const char * types) Obtain information about member variables const char *ivar_getName(Ivar v) const char *ivar_getTypeEncoding(Ivar v)Copy the code

attribute

Get a property objc_property_t class_getProperty(Class CLS, Const char *name) copy property list (free) objc_property_t *class_copyPropertyList(Class CLS, Unsigned int *outCount) BOOL class_addProperty(Class CLS, const char *name, Const objc_property_attribute_t *attributes, unsigned int attributeCount) void class_replaceProperty(Class CLS, const char *name, const objc_property_attribute_t *attributes, Unsigned int attributeCount) gets some information about an attribute const char *property_getName(objC_property_t property) const char *property_getAttributes(objc_property_t property)Copy the code

methods

Method class_getInstanceMethod(CLS, SEL name) Method class_getInstanceMethod(CLS, SEL name) IMP class_getMethodImplementation(Class CLS, SEL name) IMP class_getMethodImplementation(Method m, IMP imp) void method_exchangeImplementations(Method m1, Method *class_copyMethodList(Class CLS, Unsigned int *outCount) BOOL class_addMethod(Class CLS, SEL name, IMP IMP, Const char *types) IMP class_replaceMethod(Class CLS, SEL name, IMP IMP, SEL method_getName(Method m) IMP method_getImplementation(Method m) SEL method_getName(Method m) IMP method_getImplementation(Method m) const char *method_getTypeEncoding(Method m) unsigned int method_getNumberOfArguments(Method m) char *method_copyReturnType(Method m) char *method_copyArgumentType(Method m, Unsigned int index) selector associated const char *sel_getName(SEL SEL) SEL sel_registerName(const char * STR) implement IMP with block as method imp_implementationWithBlock(id block) id imp_getBlock(IMP anImp) BOOL imp_removeBlock(IMP anImp)Copy the code

supplement

IsKindOfClass and isMemberOfClass

We can actually go to the source code to see the internal implementation:

  • For class objects:

+ (BOOL)isMemberOfClass:(Class) CLS isa pointing Class that finds its own isa pointer, i.e. its own metaclass

+ (BOOL)isKindOfClass:(Class) CLS finds its own isa pointer to the pointing Class, that is, its own metaclass, and if it can’t find it, iterates over its parent Class. So it’s going to go all the way to NSObject

  • For instance objects:

– (BOOL)isMemberOfClass:(Class) CLS finds its own Class and compares the current Class

– (BOOL)isKindOfClass:(Class) CLS finds its Class property and then compares the current Class, if not, iterates through the parent Class until NSObject

Test it out:

Int main(int argc, const char * argv[]) {@autoreleasepool {// class NSLog(@"NSObject isKindOfClass NSObject: %d", [NSObject isKindOfClass:[NSObject class]]); // 1 NSLog(@"NSObject isMemberOfClass NSObject: %d", [NSObject isMemberOfClass:[NSObject class]]); // 0 NSLog(@"Cat class isKindOfClass Cat: %d", [Cat isKindOfClass:[Cat class]]); // 0 NSLog(@"Cat class isMemberOfClass Cat: %d", [Cat isMemberOfClass:[Cat class]]); // 0 NSLog(@"------------------"); Cat: NSObject Cat * Cat = [Cat alloc] init]; NSLog(@"cat instance isKindOfClass NSObject: %d", [cat isKindOfClass:[NSObject class]]); // 1 NSLog(@"cat instance isMemberOfClass NSObject: %d", [cat isMemberOfClass:[NSObject class]]); // 0 NSLog(@"cat instance isKindOfClass Cat: %d", [cat isKindOfClass:[Cat class]]); // 1 NSLog(@"cat instance isMemberOfClass Cat: %d", [cat isMemberOfClass:[Cat class]]); // 1 } return 0; }Copy the code

Output:

2021-01-17 20:10:12.286409+0800 Runtime[76526:1658908] NSObject isKindOfClass NSObject: 1 2021-01-17 20:10:12.286671+0800 Runtime[76526:1658908] NSObject isMemberOfClass NSObject: 0 2021-01-17 20:10:12.286703+0800 Runtime[76526:1658908] Cat class isKindOfClass Cat: 0 2021-01-17 20:10:12.286723+0800 Runtime[76526:1658908] Cat class isMemberOfClass Cat: 0 2021-01-17 20:10:12. 286741 + 0800 Runtime (76526-1658908) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the 2021-01-17 20:10:12. 286760 + 0800 Runtime[76526:1658908] cat instance isKindOfClass NSObject: 1 2021-01-17 20:10:12.286780+0800 Runtime[76526:1658908] cat instance isMemberOfClass NSObject: 0 2021-01-17 20:10:12.286798+0800 Runtime[76526:1658908] Cat instance isKindOfClass cat: 1 2021-01-17 20:10:12.286815+0800 Runtime[76526:1658908] cat instance isMemberOfClass cat :1Copy the code

The super keyword

Add the following tests:

// --------------- Animal ---------------

@interface Animal : NSObject

- (void)eatFood;

@end

@implementation Animal

- (void)eatFood {
    NSLog(@"Animal eat");
}

@end

// --------------- Cat ---------------

@interface Cat : Animal

@end

@implementation Cat

- (void)eatFood {
    [super eatFood];
    
    NSLog(@"Cat eat");
}

@end

Copy the code

Test run:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Cat *cat = [[Cat alloc] init];
        [cat eatFood];
    }
    return 0;
}
Copy the code

Output:

2021-01-17 14:10:42.039886+0800 Super[7101-17 14:10:42.040163+0800 Super[71153:1538568] Animal eat 2021-01-17 14:10:42.040163+0800 Super[71153:1538568] Cat  eatCopy the code

This is just a simple inheritance and method call that works as expected.

Now look at the following:

@interface Cat : Animal

- (void)sleep;

@end

- (void)sleep {
    NSLog(@"[self class] = %@", [self class]);
    NSLog(@"[self superclass] = %@", [self superclass]);
    NSLog(@"[super class] = %@", [super class]);
    NSLog(@"[super superclass] = %@", [super superclass]);
}
Copy the code

Testing:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Cat *cat = [[Cat alloc] init];
        [cat sleep];
    }
    return 0;
}
Copy the code

Output:

2021-01-17 14:14:14.480170+0800 Super[71285:1540874] [self class] = Cat 2021-01-17 14:14:14.480781+0800 Super[71285:1540874] [self superclass] = Animal 2021-01-17 14:14:14.480816+0800 Super[71285:1540874] = Cat 2021-01-17 14:14:14.480838+0800 Super[71285:1540874] [Super superclass] = AnimalCopy the code

What’s going on? [super class] = Cat and [super superclass] = Animal

We know that the message sent at Runtime is going to objs_msgSend this guy. The message flow for an object is that if an object can’t find a method in its own class, it looks in its parent class, and then all the way to NSObject. [super class] = cat instead of [super class] = Animal

To simplify the code, see what [Super eatFood] does:

- (void)eatFood {
    [super eatFood];
}
Copy the code

Execute the command clang-rewrite-objc cat.m -o cat.cpp, convert to c++ code, and look at the underlying implementation (of course, the underlying OC code is definitely not like this, but its basic logical implementation is similar)

In c++ code, find an implementation of the eatFood method and you’ll find something like this:

static void _I_Cat_eatFood(Cat * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Cat"))},
    sel_registerName("eatFood"));
}
Copy the code

The objc_msgSendSuper method takes two arguments, __rw_objc_super and eatFood. The __rw_objc_super structure takes two arguments: The first one is the current object, which in our case is cat, and the second one is the parent of the current class, which is cat, which is Animal.

__rw_objc_super data size structure:

struct __rw_objc_super { struct objc_object *object; Struct objc_object *superClass; // Message receiver's parent class};Copy the code

So the recipient of a call to [super Class] from a Cat object method is still a Cat object, except that its message lookup starts from the parent class, skipping the Cat class. So we find:

NSLog(@"[super class] = %@", [super class]); [super class] = Cat NSLog(@"[super superclass] = %@", [super superclass]); // Output: [super superclass] = AnimalCopy the code

Objc: msgsendSuper objC: msgsendSuper

(I download objC4-781 source code)

Objc_msgSendSuper:

/** * Sends a message with a simple return value to the superclass of an instance of a class. * * @param super A pointer  to an \c objc_super data structure. Pass values identifying the * context the message was sent to, including the instance of the class that is to receive the * message and the superclass at which to start searching for the method implementation. * @param op A pointer of type SEL. Pass the selector of the method that will handle the message. * @param ... * A variable argument list containing the arguments to the method. * * @return The return value of the method identified  by \e op. * * @see objc_msgSend */ OBJC_EXPORT id _Nullable objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0); #endifCopy the code

Objc_super structure:

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
    __unsafe_unretained _Nonnull Class super_class;
    /* super_class is the first class to search */
};
Copy the code

As you can see, the implementation is a little different from our c++ code in that the specific method receiver is still the current object, and the method lookup starts from the parent class.