What does runtime do?

  • In the process of program running, dynamically create classes, dynamically add, modify the attributes and methods of this class;
  • Iterate over all member variables, attributes, and methods in a class
  • Message passing and forwarding

case

  • Add attributes and methods to system classes
  • Methods exchange
  • Gets the properties, private properties of the object
  • Dictionary transformation model
  • KVC, KVO
  • Archiving (encoding, decoding)
  • NSClassFromString Class <-> string
  • block
  • Class self-detection

Rumtime is the dynamic core of Objective-C, objective-C objects are generally based on the Runtime class structure, to achieve many compile-time determination methods deferred to Runtime, so as to achieve dynamic modification, determination, exchange… Properties and Methods

Isa pointer

Isa:

  • Is a pointer of type Class. Each instance object has a pointer to ISA, which points to the object’s class
  • Class(Class objectThere is also a pointer to isa to meteClass. Metaclasses hold a list of class methods. When a class method is called, the metaclass looks for its implementation from itself. If not, the metaclass looks for the method from its parent.

Note also that a metaclass is also a class; it is also an object. A metaclass also has an ISA pointer, whose ISA pointer ultimately points to a root metaclass. The isa pointer to the root metaclass points to itself, forming a closed inner loop.

Before arm64, ISA was a common pointer, the memory address of the Class and meta-class objects was started with ARM64, isa was optimized to be a common union structure, and bit fields were used to store more information starting from 64bit. Isa requires a bit operation to calculate the real address (see isa & ISA_MSAK for information about the ISA pointing class).

There are two types of ISA Pointers

  • A pure pointer to a memory address
  • NON_POINTER_ISAIn addition to the memory address, there is some other information

isa_t

From the initIsa method we can see isa = ISA_t ((uintptr_t) CLS); Isa’s data structure is actually ISA_T, and then let’s go into ISA_T.

Isa_t source

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
# if __arm64__
    struct {
      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
    };
#endif
};

# elif __x86_64__
    struct {                                                        \
      uintptr_t nonpointer        : 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
}
Copy the code

Using the ARM64 architecture,ISA_BITFIELD, let’s take a look at what each field stores for a deeper understanding of the nature of the object.

Members of the position meaning
nonpointer 1bit Indicates whether pointer optimization is enabled for isa Pointers. 0: pure ISA pointer; 1: not just class object addresses. Isa contains class information, reference counting of objects, and so on
has_assoc 1bit Flag bit: Indicates whether an object has an associated object. 0: none. 1: Exists. Objects with no associated objects are released faster
has_cxx_dtor 1bit Flag bit: indicates whether the object has a C++ or ARC destructor. Objects without destructors are freed faster
shiftcls 33bit Store the value of a class pointer. With pointer optimization turned on, 33 bits are used to store class Pointers in the ARM64 architecture.
magic 6bit Space used by the debugger to determine whether the current object is a real object or not initialized, fixed at 0x1A
weakly_referenced 1bit Flag bit: Used to indicate whether this object is weakly referenced or referenced by other ARC objects. Objects that are not weakly referenced are released faster
deallocating 1bit Flag bit: Used to indicate whether the object is being released
has_sidetable_rc 1bit Flag bit: used to indicate whether the current reference count is too large (greater than 10) to be stored in isa and needs to be borrowed from sidetable, indicating whether there isa plug-in hash table
extra_rc 19bit The value of extra_rc is 6. For example, if an object has a reference count of 7, the value of extra_rc is 6

KVC

Key-value encoding is a mechanism for indirectly accessing the properties of an object, instead of calling accessor methods or accessing them directly through instance variables, using strings to identify the properties.

When setValue:forKey: sets the property value, the underlying execution flow is as follows

  1. Set

    -> _set

    -> setIs >

    • If you have any of these setter methods, set the value of the property directly.

    • If none, go to 2

  2. If there is no the first step of three simple setter method, is to find whether accessInstanceVariablesDirectly returns YES,

    • If YES is returned, the indirectly accessed instance variables are assigned in the following order: _

      -> _is< key>

      • If any of the instance variables are found, the value is assigned

      • If none, go to 3

    • If NO is returned, 3 is entered

  3. If neither setter method nor instance variable is found, the setValue: forUndefinedKey: method of the object is executed, throwing an exception of type NSUndefinedKeyException by default

KVO

The implementation of KVO actually takes advantage of the OC Runtime mechanism. When an instance object (such as self.person above) adds an observer, The underlying class is dynamically added based on the class to which the instance object belongs (the dynamically added class name is preceded by the NSKVONotifying_ prefix), which is inherited from the original class. The underlying implementation of the above example is as follows:

  1. The pointer to the instance object ISA was changed from the original class to the intermediate class after registering the KVO observer

  2. The middle class overrides setter methods, class, dealloc, _isKVOA methods for observing properties

    • The intermediate class overrides the observation propertysetterMethods are calledMethod of elegantly-named setName:As I saidMethod of elegantly-named setName:It’s overwritten, so it’s actually calling _NSSetObjectValueAndNotifyThis method. This method implementation apple is not open source, can not know its specific implementation, but you can guess its implementation process is roughly as follows: first call[self willChangeValueForKey:@"name"];This method. And then call the originalsetterMethod implementation (e.g_name = name;); Call again[self didChangeValueForKey:@"name"];This method. Finally, indidChangeValueForKey:The method that calls the observerobserveValueForKeyPath: ofObject: change: context:Method to notify the observer that the property value has changed.
  3. In the dealloc method, when the KVO observer is removed, the isa pointer to the instance object is changed from the intermediate class to the original class

  4. Intermediate classes are stored in memory from the moment they are created and will not be destroyed

IsKindOfClass and isMemberOfClass in iOS

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
Copy the code

Print result:

2020-09-15 22:38:50 +0800 KCObjc[23825:541164] re1:1 RE2:0 RE3:0 RE4:0 2020-09-15 22:38:50 +0800 KCObjc[23825:541164] re5 :1 re6 :1 re7 :1 re8 :1Copy the code

- (BOOL)isKindOfClass object method

The first is to get the object class to compare with the incoming class, if not equal, the subsequent comparison is to continue to get the parent class of the last class to compare with the incoming class

  • re5: NSObject Class of the objectNSObject classNSObject classEqual, return1
  • re7: LGPerson object classLGPerson classLGPerson classEqual, return1
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
Copy the code

+ (BOOL)isKindOfClass method

The first comparison is made between the metaclass that fetched the class and the incoming class, and the second comparison is made between the parent class that fetched the last result and the incoming class

  • re1: NSObject classNSObject metaclassNot equal, and then compareNSObject classNSObject Is the parent of the metaclass, equal, returns1
  • re3: LGPerson classLGPerson metaclassNot equal, and then compareLGPerson classThe parent of the LGPerson metaclassLGPerson metaclassNot equal, and then compareLGPerson classLGPerson metaclassThe parent of the –NSObject classNot equal, and then compareLGPerson classNSObject classThe parent of the –nilNot equal, return0
+ (BOOL)isKindOfClass:(Class) CLS {// Class vs metaclass // root metaclass vs NSObject // NSObject // LGPerson vs metaclass (root metaclass) (NSObject) for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; }Copy the code

- (BOOL)isMemberOfClass object method

Gets the class of the object, compared to the class passed in

  • re6: NSObject Class of the objectNSObject classNSObject classEqual, return1
  • re8: LGPerson object classLGPerson classLGPerson classEqual, return1
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
Copy the code

+ (BOOL)isMemberOfClass method

Gets the metaclass of the class, compared to the incoming class

  • re2: NSObject classNSObject metaclassNot equal, return0
  • re4: LGPerson classLGPerson metaclassNot equal, return0
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
Copy the code

Runtime method cache? The form of storage, data structure, and search process?

The hash table structure for cache_t incremental extensions. The bucket_t stored internally in the hash table. Bucket_t stores SEL and IMP key-value pairs.

  • For an ordered list of methods, use binary lookup
  • If it’s an unordered list of methods, just walk through the search

The essence of a Class is a structure with the following internal structure:

typedef struct objc_class *Class; typedef struct objc_object *id; struct objc_class : objc_object { // Class ISA; Class superclass; // Parent pointer cache_t cache; // Method cache stores data structure class_data_bits_t bits; Class_rw_t *data() const {return bits.data(); }... }Copy the code

Cache_t structure

struct cache_t { struct bucket_t * _buckets; // The cache array, the hash bucket, is an array of the bucket_t structure that holds SEL and IMP. mask_t _mask; // The critical size of the cache array is actually used to serve the uint16_t _flags; // Uint16_t _occupied; // The number of cached methods in the cache array... Omit}Copy the code

Message lookup process (objc_msgSend process)

  1. Quick find: Cache -> the first address is offset by 16 bytes to obtain cache, the highest 16 bits to store mask, and the lowest 48 bits to store buckets -> The method subscript index is obtained by SEL &mask. Buckets (IMP, SEL) and buckets (IMP, SEL) are the same. Check sel in different cycles (from back to front).
  2. Slow lookups: lookUpImpOrForward -> binary lookups -> this class cannot find its parent, recursively lookups until it finds NSObject, NSObject->superCls = nil, stops recursive loop -> dynamic method resolution
  3. Dynamic method resolution: resolveInstanceMethod, resolveClassMethod asks whether the current class can handle the unknown selector by dynamically adding methods
  4. Fast forward: forwardingTargetForSelector check whether there is other objects can deal with this message, is called a redundant receiver, can handle to a redundant receiver processing, processing, into a complete message forwarding process
  5. Complete message forwarding: methodSignatureForSelector, forwardInvocation runtime system will put all the information associated with the message in the NSInvocation object, give the recipient an opportunity to deal with the unknown selector

objcIn the direction of onenilWhat happens when the object sends a message?

It doesn’t crash if it goes to a nil. The object sends a message, and the first thing it does when looking for the object’s ISA pointer is that the 0 address is returned, so there is no error. It doesn’t collapse.

objcWhat happens when you send a message to an object?

When objC sends a message to an object, the Runtime will find the class to which the object belongs according to the isa pointer of the object, and then run the method through the list of methods in the class and the list of methods of its parent class. If the root class is not found, the runtime will intercept the call and use the message forwarding mechanism. Go ahead and execute its implementation IMP.

useruntime AssociateMethod associated with the object that needs to be in the main objectdeallocIs it time to release?

Not required either under MRC or ARC. Associated objects are released much later in their life cycle than the objects themselves in object_Dispose () method called by nsobject-dealloc.

  1. call-release: The reference count becomes zero

The object is being destroyed and its life cycle is about to end

There can be no new _weak reference, otherwise it will point to nil.

Call [the self dealloc]

  1. The parent class call-dealloc

-dealloc is called from the most directly inherited parent of the inherited relationship

In MRC code, instance variables will be released manually (iVars)

The parent class of each level in the inheritance relationship is called -dealloc again

  1. NSObject-dealloc

Do only one thing: call object_Dispose () method in Objective 1 C Runtime

  1. callobject_dispose()

Call destructors for C++ instance variables (iVars)

Call -release for instance variables (iVars) in ARC profiles

Disassociate all objects associated with the Runtime Associate method

Empty the reference count table and clear the weak reference table, setting the weak pointer to nil

Calling free ()

Category

CategoryThe implementation principle of?

Is added to the corresponding structure of class_rw_t.

Category is actually a Category_t structure. At runtime, new methods are inserted in reverse order to the top of the list of existing methods, so different categories that add the same method actually execute the last one.

The Category is separated from the original class when it is compiled, and is merged with the original class only after the program is up and running through the Runtime.

Mememove, memcpy: this method is both displacement and copied, moved to the method of simple understanding is the original, according to a new controls, to classify the position of the front left, then the classification of the methods, according to the reverse order insert, could conclude that it is, the longer participate in the classification of compilation, the inside of the method is effective.

CategoryAfter compilation, when was it merged with the original class?

  1. After the program is started and compiled, the Runtime initializes it by calling _objc_init.

  2. And then we have map_images. (This function is triggered when Dyld loads the image into memory.)

  3. Next call map_images_NOLock.

  4. Then there’s read_images, which reads the information about all the classes.

  5. Finally, call reMethodizeClass:, which means reMethodizeClass.

  6. Within the reMethodizeClass: method is called attachCategories:, which passes in classes and categories and merges the list of methods, protocols, and so on with the original Class. And then we add it to the class_rw_T structure.

CategoryWhat are the uses?

  • Add methods, attributes (need to associate objects) to the system class.
  • For a large number of methods on a class, you can implement categorization by different names.

How to giveCategoryAdd attributes? In what form are associated objects stored?

The associated objects are stored in a global singleton in the form of a hash table.

@interface NSObject (Extension)

@property (nonatomic,copy ) NSString name;

@end
@implementation NSObject (Extension)

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
    return objc_getAssociatedobject(self,@selector(name));
}
@end

Copy the code

Class_ro_t is read-only and stores information about fields determined at compile time. Class_rw_t is created at runtime, and it makes a copy of class_ro_t, and then it adds in the attributes, methods, protocols, etc. of the class, and the reason for that is because Objective-C is a dynamic language, and you can change their methods at runtime, Properties, and classes can add new methods to a class without changing its design.

[self class] and [super Class]

What does the following code output?

@implementation Son : Father
- (id)init
{
    self = [super init);
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@",  NSStringFromClass([super class]));
    }
    return self;
}
@end
Copy the code

NSStringFromClass([self class]) = Son

NSStringFromClass([super class]) = Son

A: In Objective 1 C, you can understand self and super.

  • selfIs a hidden parameter of the class, pointing to an instance of the class on which the method is currently called;
  • superIs essentially a compiler identifier, andselfIs the same message receiver that points to.

The difference is that super tells the compiler that when a method is called, it calls the method of the parent class, not the method in this class. When a method is called with self, it looks from the list of methods in the current class. If not, it looks from the parent class. When you use super, you start with the list of methods in the parent class. Then call this method of the parent class.

When calling [super Class], the Runtime will call objc_msgSendSuper instead of objc_msgSend;

In the objc_msgSendSuper method, the first argument is an objc_super structure that has two variables, the receiver for the message and the super_class, the parent of the current class.

Objc_msgSendSuper should work like this: Look for a selector from the superClass parent’s list of methods pointed to by the objC_super structure, and call the parent’s selector with objC -> Receiver. Note that the last caller is objc->receiver, not super_class! Then objc_msgSendSuper ends up changing to:

Objc msgSend (objc_super->receiver, @selector (class)) /// Specifies an instance of a class. This is an instance of class _unsafe_unretained ID receiver; - (Class) Class {return object_getClass(self); }Copy the code

Found the IMP of the class method in the parent NSObject, and passed the ginseng objc_super->receiver = self. Self is son, calling class, so the parent class executes IMP, and the output is son, and the output is son, both are the same.

Can I add instance variables to the compiled class? Can I add instance variables to classes created at run time? Why is that?

  • Clean Memory: block of memory that does not change after loading,class_ro_tBelong toClean Memory, because it is read-only (method list), (attribute list), (protocol list), and instance variables, class name, size).
  • Dirty Memory:class_rw_tBelong toDirty Memory, a block of memory that changes at run time. Once the class structure is loaded, it becomesDirty Memory(Method list, protocol list, property list)

runtimeHow to implementweakAutomatic setting of variablesnil? knowSideTable?

The Runtime lays out the registered classes and puts weak objects into a hash table. If the reference count of this object is 0, the dealloc will be generated. If the reference count of this object is 0, the dealloc will be generated. If the reference count of this object is A, the dealloc will be generated. Set to nil.

What happens to the weak pointer when the object to which the weak reference points is released? When an object is released, the basic flow is as follows:

  1. Call objc_release

  2. Because the reference count for the object is 0, dealloc is executed

  3. In dealloc, the _objc_rootDealloc function is called

  4. In _objc_rootDealloc, the object_dispose function is called

  5. Call objc_destructInstance

  6. Finally, call objc_clear_deallocating

The objc_clear_deallocating function that is called when the object is released:

  1. Gets the address of the deprecated object as a key from the weak table

  2. Assign nil to all addresses with weak modifier variables included in the record

  3. Delete the record from the weak table

  4. Removes the record of discarded objects whose addresses are key values from the reference count table

The weak table is a hash table. Key is the address of the weak object and value is the address of the weak pointer.