Welcome to my blog post
Runtime is the deferment of data type determination from compile time to Runtime. It’s a set of underlying pure C apis, and all the objective-C code that we write, we end up converting to C code for the Runtime.
However, the implementation of the runtime API is developed in C++ (the implementation files in the source are.mm files).
To get a fuller understanding of the Runtime mechanism, let’s use the latest objC4 source code.
The messaging
We know objective-C is object oriented and C is process oriented, so we need to turn object-oriented classes into process oriented structures.
In Objective-C, all “messages” in messaging are converted by the compiler to:
id objc_msgSend ( id self, SEL op, ... ) ;Copy the code
For example, the method of executing an object: [obj foo]; , the underlying runtime is converted by the compiler to objc_msgSend(obj, @selector(foo)); .
So what does the execution flow look like inside a method? Let me start with some concepts.
concept
objc_object
An Objective-C object is represented by an ID type, which is essentially a pointer to an objc_Object structure.
typedef struct objc_object *id;
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
};
struct objc_object {
private:
isa_t isa;
// public & private method...
}
Copy the code
We see that objc_Object has only one object in its structure, which is the ISA pointer to its class.
When sending a message to an object, the Runtime finds the class to which the instance object belongs based on the ISA pointer.
objc_class
Objective-c classes are represented by the Class type, which is actually a pointer to the objc_class structure.
typedef struct objc_class *Class;
Copy the code
The objc_class structure defines a number of variables:
struct objc_class : objc_object {
// Pointer to class (in objc_object)
// Class ISA;
// A pointer to the parent class
Class superclass;
// Caches Pointers and vtables to speed up method calls
cache_t cache; // formerly cache pointer and vtable
// A place to store information about a class's methods, attributes, protocols that it follows, and so on
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// the class_data_bits_t constructor method used to return the class_rw_t pointer ()
class_rw_t *data() {
return bits.data();
}
// other methods...
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
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;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
// other methods
}
Copy the code
Objc_class inherits from Objc_Object, so it also has an ISA pointer. In addition, its structure holds Pointers to the parent class, caches, a list of instance variables, a list of methods, protocols to follow, and so on.
The metaclass
Metaclass is aclass of class objects that has the same structure as objc_class.
Since all classes are themselves an object, we can send messages to this object, such as calling class methods. In order to call a class method, the isa pointer to that class must point to an objC_class structure that contains the class method. Class objects store only instance methods, but no class methods, which leads to the concept of metaclasses, which hold all the information needed to create class objects and class methods.
To make it easier to understand, here’s an example:
- (void)eat; // an instance method + (void)sleep; // a class method // then the instance method needs to be called by the class object: [person eat]; // The class method needs to be called by the metaclass: [Person sleep];Copy the code
If the person object can also call sleep, then we can’t tell if it calls + (void)sleep; Or – (void) sleep; .
Class objects are instances of metaclasses, of class objectsisa
The pointer points to the metaclass.
This may be a little convoluted, but use this classic picture to understand:
When sending a message to an object, Runtime looks for the corresponding method in the list of methods of that object’s class, but when sending a message to a class, Runtime looks for the corresponding method in the list of meta Class methods of that class. The ISA of all meta classes, including Root class, Superclass, and Subclass, points to the meta class of the Root class, thus forming a closed loop.
Method(method_t)
Method is a pointer to the method_t structure and is defined in objc-private.h and objc-runtime-new.h:
typedef struct method_t *Method;
Copy the code
struct method_t {
// Method selector
SEL name;
// Type encoding
const char *types;
// A pointer to the method implementation
MethodListIMP imp;
}
Copy the code
Method = SEL + IMP + types
For types, see Type Encodings.
SEL(objc_selector)
SEL, also known as method selector, is a pointer to the objc_selector structure and the second parameter type of the objc_msgSend function.
typedef struct objc_selector *SEL;
Copy the code
Method selector is used to represent the name of the runtime method. When the code is compiled, it generates a unique integer identifier (an address of type Int), SEL, based on the name of the method (excluding arguments).
There can’t be two identical SEL’s in a class’s list of methods, which is why Overloading is not supported in Objective-C.
Different classes can have the same SEL, because instance objects of different classes performing the same selector will look for their corresponding IMPs in their respective method lists.
There are three ways to obtain SEL:
sel_registerName
function- Provided by the Objective-C compiler
@selector()
methods NSSeletorFromString()
methods
IMP
An IMP is essentially a function pointer to the address of the method implementation.
typedef void (*IMP)(void /* id, SEL, ... * / );
Copy the code
Parameter Description:
- Id: pointer to self (in the case of an instance method, the memory address of the class instance; A pointer to a metaclass if it is a class method.)
- SEL: method selector
- . : The argument list of the method
The relationship between SEL and IMP is similar to that between key and value in a hash table. Using this hash mapping method can speed up the method search.
cache_t
Cache_t represents the class cache and is one of the structural variables of object_class.
struct cache_t {
// An array of methods
struct bucket_t* _buckets;
// The maximum number that can be stored
mask_t _mask;
// The number of methods currently stored
mask_t _occupied;
// ...
}
Copy the code
To speed up message distribution, methods and their corresponding addresses are cached in cache_t.
In practice, most commonly used methods are cached, and the Runtime system is actually very fast, close to the speed of a program that executes a memory address directly.
category_t
Category_t represents a pointer to the categorized structure.
struct category_t {
// Indicates the class name, not the class name
const char *name;
// The class object to be extended is not defined at compile time, but corresponds to the corresponding class object by name at run time
classref_t cls;
// List of instance methods
struct method_list_t *instanceMethods;
// List of class methods
struct method_list_t *classMethods;
// Protocol list
struct protocol_list_t *protocols;
// Instance properties
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
// Class (metaclass) property list
struct property_list_t* _classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
Copy the code
Here’s a classic question:
Can I add instance variables/member variables/attributes to a class?
First, instance and member variables cannot be added directly to a classification.
As a matter of practice, adding instance/member variables to a classification generates an error at compile time, but adding attributes is allowed.
This is because there is no “instance variable/member variable” structure in the classification structure, but there is an “attribute” structure.
Can you add attributes directly to a category?
In fact, this is not the case, although there is no error message in the.h classification, but the following warning is reported in the.m, and the runtime error.
The warning indicates that there are two solutions:
The first: use the @dynamic modifier. But really, the @dynamic modifier just tells the compiler that the setter and getter methods for the property will be implemented by the user. But doing so only eliminates warnings, not fixes the problem, and the runtime still crashes.
The second option: manually adding setters and getters to classes is an efficient one.
We know at sign property equals ivar plus setter plus getter.
You can dynamically add attributes to a category using objc_setAssociatedObject and objc_getAssociatedObject, as described in “Adding attributes to a category by associating Objects” below.
process
The complete process of message passing is as follows:
That is, the process of finding IMP:
- First look for the current class’s cache method list.
- If it does, it returns the corresponding IMP implementation and caches the selector in the current class.
- If it’s not in the list of methods of the class, it looks in the list of methods of the parent class until it finds the NSObject class.
- When it does not, it enters dynamic method resolution and message forwarding.
forward
If the IMP cannot be found after the message is delivered, the message forwarding process is entered.
- With dynamic method resolution at run time, we can add a method to a class when it is needed.
- An object can forward some selectors that it cannot decipher to the standby receiver for processing.
- After the above two steps, if there is still no way to process the selectors, start the full message forwarding mechanism.
Dynamic method parsing
Two methods for dynamic method resolution:
// Add class methods
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5.2.0.9.0.1.0.2.0);
// Add instance methods
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5.2.0.9.0.1.0.2.0);
Copy the code
Let’s look at these two methods called in the source code:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
// check whether it is a metaclass
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// call the class resolveInstanceMethod method to dynamically add instance methods
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// Call the resolveClassMethod method of the metaclass to dynamically add class methods
_class_resolveClassMethod(cls, sel, inst);
if(! lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) { _class_resolveInstanceMethod(cls, sel, inst); }}}Copy the code
Let’s look at an example of dynamic method resolution.
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(foo)) {
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void fooMethod(id obj, SEL _cmd) {
NSLog(@"Doing foo");
}
Copy the code
As you can see, foo is not implemented, but fooMethod is dynamically added via class_addMethod and fooMethod IMP is executed.
If resolveInstanceMethod: method to return NO, runtime will move to the next step: forwardingTargetForSelector:.
Alternate receiver
If the target object implements forwardingTargetForSelector: method, the runtime can call this method, give you put forward this message to other recipients.
An example of implementing an alternate receiver is as follows:
#import "ViewController.h"
#import <objc/runtime.h>
@interface Person: NSObject
@end
@implementation Person
- (void)foo {
NSLog(@"Doing foo");// The foo function of Person
}
@end
@interface ViewController(a)
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// Return NO to proceed to the next step of forwarding.
return NO;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(foo)) {
// Return the Person object to receive the message
return [Person new];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
Copy the code
The implementation of the above is the use of the current forwardingTargetForSelector ViewController method of class foo forwarded to spare the recipient to perform the Person class.
Complete message forwarding
If you could not handle unknown messages in the previous step, the only thing you can do is enable a full message forwarding mechanism.
There are two main methods involved:
- send
methodSignatureForSelector
Performs method signature, which encapsulates the parameter types and return values of the function. If it returns nil, the Runtime will emitdoesNotRecognizeSelector
Message and program crash simultaneously. - If a function signature is returned, the Runtime creates one
NSInvocation
Object and sendforwardInvocation
Message to the target object.
An example of implementing a complete forward is as follows:
#import "ViewController.h"
#import <objc/runtime.h>
@interface Person: NSObject
@end
@implementation Person
- (void)foo {
NSLog(@"Doing foo");
}
@end
@interface ViewController(a)
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(foo)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// Return NO to proceed to the next step of forwarding.
return NO;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
// Return nil to proceed to the next forward.
return nil;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];// sign, go to forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
Person *p = [Person new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
} else{[selfdoesNotRecognizeSelector:sel]; }}@end
Copy the code
With the signature, the runtime generates an object, the anInvocation, to the method forwardInvocation, where we have the Person object execute the foo function.
The above is the three-way forwarding process of Runtime. The following are the practical applications of Runtime.
application
The associated object adds attributes to the classification
Associated Objects are a feature of the Objective-C runtime that allows developers to add custom properties to existing classes in extensions.
The associated object Runtime provides three apis:
// Get the associated object ID objc_getAssociatedObject(id object, const void *key); Void objc_setAssociatedObject(id object, const void *key, ID value, objc_AssociationPolicy policy); Void objc_removeAssociatedObjects(id object); // Remove the associated object.Copy the code
Parameter Description:
object
: Indicates the associated objectkey
: Unique identifier of the associated objectvalue
: Associated objectpolicy
: Memory management policy
The runtime.h source code describes the memory management strategy as follows:
/* Associative References */
/** * Policies related to associative references. * These are options to objc_setAssociatedObject() */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0./**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1./**< Specifies a strong reference to the associated object. * The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3./**< Specifies that the associated object is copied. * The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401./**< Specifies a strong reference to the associated object. * The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */
};
Copy the code
Let’s look at the attribute modifier corresponding to the memory policy.
Memory strategy | Properties of modified | describe |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) or @property (unsafe_unretained) | Specifies a weak reference to an associated object. |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (nonatomic, strong) | Specifies a strong reference to an associated object that cannot be used atomically. |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (nonatomic, copy) | Specifies a copy reference to an associated object that cannot be used atomically. |
OBJC_ASSOCIATION_RETAIN | @property (atomic, strong) | Specifies a strong reference to an associated object that can be used atomically. |
OBJC_ASSOCIATION_COPY | @property (atomic, copy) | Specifies a copy reference to an associated object that can be used atomically. |
Add a non-atomic property prop modified with copy to a class using associative objects.
We already know that you can’t add attributes to a class directly, you need to manually add access methods:
// NSObject+AssociatedObject.h
#import <Foundation/Foundation.h>
@interface NSObject (AssociatedObject)
@property (nonatomic.copy) NSString *prop;
@end
// NSObject+AssociatedObject.m
#import "NSObject+AssociatedObject.h"
#import <objc/runtime.h>
// There are three common ways to write key:
//
// 1. static void *propKey = &propKey;
// 2. static NSString *propKey = @"propKey";
// 3. static char propKey;
static NSString *propKey = @"propKey";
@implementation NSObject (AssociatedObject)
- (void)setProp:(NSString *)prop {
objc_setAssociatedObject(self, &propKey, prop, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)prop {
return objc_getAssociatedObject(self, &propKey);
}
@end
Copy the code
Dark magic add and replace methods
The dark magic is method swizzling, which is the IMP implementation of swapping methods.
It’s usually + (void)load; Perform method exchange in. Because it loads early, it basically ensures that methods are swapped.
Method to add
“Method addition” is already mentioned in dynamic method resolution.
//class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
Copy the code
Parameter Description:
cls
: The class to which the method is addedname
SEL to add the method nameimp
: method implementation. This function must take at least two arguments, self,_cmdtypes
: Type encoding
Methods to replace
Method substitution is to change the selection submapping table of the class.
If you want to interchange two already written method implementations, you can use the following function
void method_exchangeImplementations(Method m1, Method m2);
Copy the code
Method implementations can be obtained by using the following function:
void class_getInstanceMethod(Class aClass, SEL aSelector);
Copy the code
Let’s implement an example to replace the viewDidLoad method in ViewController.
@implementation ViewController
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(msviewDidLoad);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
// Determine whether original's method is implemented. If not, add the implementation and type of swizzledMethod to originalSelector
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
// Replace the originalMethod implementation and type with swizzledSelector
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
// Swap originalMethod and swizzledMethodmethod_exchangeImplementations(originalMethod, swizzledMethod); }}); } - (void)msviewDidLoad {
NSLog(@"msviewDidLoad");
[self msviewDidLoad];
}
- (void)viewDidLoad {
NSLog(@"viewDidLoad");
[super viewDidLoad];
}
@end
Copy the code
KVO implementation
The full name of KVO is key-value observing, which is also the key-value observer mode. It provides a mechanism for notifying the current object when the properties of other objects are modified.
The implementation of KVO also relies on isa-Swizzling in Runtime.
When observing object A, the KVO mechanism dynamically creates A new object named: NSKVONotifying_A is A new class that inherits from object A, and KVO is NSKVONotifying_A that overrides the setter method to observe the property, and the setter method is responsible for calling the original setter method before and after, Notifies all observed objects of changes in property values.
Here’s an example:
#import "ViewController.h"
#import <objc/runtime.h>
#import "A.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
A *a = [A new];
NSLog(@"Before KVO: [a class] = %@, a -> isa = %@", [a class], object_getClass(a));
[a addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"After KVO: [a class] = %@, a -> isa = %@", [a class], object_getClass(a));
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey.id> *)change context:(void *)context {
}
@end
Copy the code
The program results are as follows:
Before KVO: [a class] = A, a -> isa = A
After KVO: [a class] = A, a -> isa = NSKVONotifying_A
Copy the code
Isa refers to NSKVONotifying_A, a subclass of NSKVONotifying_A, a subclass of NSKVONotifying_A.
So when we look at the application level, we are completely unaware that there is a new class, this is the system “hiding” the underlying implementation of KVO, making us think it is the original class. However, if we create a new class named NSKVONotifying_A, we will find that the system will crash when we run the code to register KVO, because the system will dynamically create a middle class named NSKVONotifying_A when we register listening, and point to this middle class.
So what does subclass NSKVONotifying_A implement in setter methods?
KVO’s key-value observation notification relies on NSObject’s two methods:
-
-willChangeValueForKey: : This method is called before the observed property changes, notifying the system that the keyPath property value is about to change;
-
-didChangeValueForKey: : This method is called after the observed property has changed, notifying the system that the keyPath property value has changed. Methods observeValueForKey: ofObject: change: context: will also be called. And override setter methods for observing properties. This inheritance injection is implemented at run time, not compile time.
Thus, KVO calls the access method for the observer property override of a subclass and works in code equivalent to:
- (void)setName:(NSString *)name {
// KVO always calls before calling the accessor method
[self willChangeValueForKey:@"name"];
// Call the accessor method of the parent class
[super setValue:newName forKey:@"name"];
// KVO always calls after calling the accessor method
[self didChangeValueForKey:@"name"];
}
Copy the code
Implement transformations between dictionaries and models (MJExtension)
Principle:
By adding the method -initWithDict: to the class of NSObject.
Concrete implementation is as follows: Use the runtime function class_copyPropertyList to get the property list, and then iterate over all the properties of the Model itself (property_getName to get the property name, Get the type of the property from the property_getAttributes function). If the property has a corresponding value in JSON, it is assigned.
Source:
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
// get the class attribute and its corresponding type
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
/* * Example * name = value3 attribute = T@"NSString",C,N,V_value3 * name = value4 Attribute = T^ I,N,V_value4 */
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
// Get the name of the property using the property_getName function
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
// Get the property type using the property_getAttributes function
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
// Immediately free the memory pointed to by properties
free(properties);
// 2. Assign attributes based on type
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[selfsetValue:[dict valueForKey:key] forKey:key]; }}return self;
}
Copy the code
NSCoding automatic archiving and filing
Principle:
Override the methods -initWithcoder: and -encodeWithcoder: in the base class of Model.
The specific implementation is as follows: use the function class_copyIvarList provided by Runtime to obtain the list of instance variables, traverse all attributes of Model itself, and encode and decode the attributes.
Source:
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[selfsetValue:[aDecoder decodeObjectForKey:key] forKey:key]; }}return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[selfvalueForKey:key] forKey:key]; }}Copy the code
JSPatch
JSPatch is an iOS dynamic update framework that allows hot updates using JavaScript to invoke all objective-C native interfaces, simply by introducing an engine into your project.
It implements the problem of obtaining parameters through full message forwarding.
Principle:
When you call a method that doesn’t have an NSObject object, it doesn’t throw an exception right away. Instead, it goes through multiple layers of forwarding, Layers of invocation object – resolveInstanceMethod:, – forwardingTargetForSelector:, – methodSignatureForSelector:, – forwardInvocation: The NSInvocation object in the -Forward Invocation saves all information about the method invocation, including the method name, parameters, and return value types. This will not happen if the JS invocation is called to the -Forward Invocation:.