The Runtime profile

Objective-c is a dynamic language, which means it always finds a way to defer some of the decision making from the compile connection to the runtime. That is, a compiler is not enough; you also need a Runtime system to execute the compiled code. This is why the Objective-C Runtime system exists, as a cornerstone of the entire Objc Runtime framework.

Runtime version and platform

According to Apple’s documentation, there are actually two versions of Runtime: “Modern” and “Legacy.” Objective-c 2.0 is a modern version of the Runtime system that runs on 64-bit applications after iOS and OS X 10.5. (apple has made all its products 64-bit since the iPhone5s in 2013.) Previous 32-bit applications used the Runtime system from earlier versions of Objective-C 1. The biggest difference between the two versions is that when you change the layout of a class’s instance variables, in legacy you need to recompile its subclasses, whereas in modern you don’t.

Click here to see the open source code maintained by Apple. The source code analysis below comes from Object C4-750.1

Interact with the Runtime

Objective-c interacts with the Runtime system at three different levels: through Objective-C source code; Methods defined by the NSObject class in the Foundation framework; Through a direct call to the Runtime function.

Objective – C source code

In most cases, the Runtime system runs automatically in the background. Simply write and compile objective-C source code to use it.

The runtime’s main functions are those that send messages using data structures and functions created by the compiler to implement dynamic language features. Objc classes, methods, protocols, and so on are defined in the Runtime by data structures (such as the id and SEL in the objc_msgSend function and its argument list)

NSObject methods

Most of the classes in Cocoa inherit from NSObject, with the special exception of the NSProxy class. NSObject defines the common interface and behavior of all the classes below the class in the class hierarchy. NSProxy is a class specifically designed to implement proxy objects. It implements message forwarding methods and can be inherited to implement a surrogate class of another class or to create a dummy class that does not exist. Both base classes follow the NSObject protocol. In the NSObject protocol, public methods are declared for all OC objects.

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

@interface NSProxy <NSObject> {
    Class isa;
}

Copy the code

Runtime defines some basic operations in the NSObject protocol:

// Return a string that describes the contents of the class @property (readonly, copy) NSString *description; // Return the object's Class - (Class) Class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead"); - (BOOL)isKindOfClass:(Class)aClass; - (BOOL)isMemberOfClass:(Class)aClass; - (BOOL)conformsToProtocol:(Protocol *)aProtocol; - (BOOL)respondsToSelector:(SEL)aSelector;Copy the code

There is also a method defined in the NSObject class that returns the address IMP of the specified method implementation

- (IMP)methodForSelector:(SEL)aSelector;
Copy the code

A function of the Runtime

The Runtime system consists of a series of functions and data structures. The header files are stored in the /usr/include/objc directory. Many functions allow you to implement the same functionality in Objc repeatedly in pure C code. There are some methods that form the basis of the NSObject class, but you won’t use them directly when writing Objc code, unless you’re doing some bridging of Objc to other languages or low-level debugging. The Objective-C Runtime Reference provides detailed documentation of the Runtime functions.

NSObject base class

There are three ways to interact with The Runtime. The first two are all related to NSObject, so let’s start with the NSObject base class. The source code for NSObject is as follows:

typedef struct objc_class *Class;

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}
Copy the code

From the source of NSObject, we can see an objc_class. In Xcode10, click to view the source code as follows:


/* Types */
//  #if ! OBJC_TYPES_DEFINED indicates that the code inside is invalid
#if ! OBJC_TYPES_DEFINED

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

/// An opaque type that represents a category.
typedef struct objc_category *Category;

/// An opaque typethat represents an Objective-C declared property. typedef struct objc_property *objc_property_t; struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; // OBJC2_UNAVAILABLE indicates that Objective-C 2.0 is no longer used#if ! __OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;	
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

#endif
Copy the code

From here, you can see that each class is an objc_class structure with an ISA pointer to its own class. Ivars is the pointer to the objc_iVAR_list member variable list. MethodLists are Pointers to the objc_method_list pointer, and *methodLists are Pointers to the list of methods.

However, after reviewing apple’s comments, the source code above is the Legacy version!! Objc-runtimenew.h objc-runtimenew.h objc-runtimenew.h objc-runtimenew.h Objc-runtimenew.h

typedef struct objc_class *Class;

truct 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() { 
        returnbits.data(); }... Omit other methods}Copy the code

Objc_class inherits from objc_Object. What is objc_Object? It is defined in objc-private.h

typedef struct objc_object *id; struct objc_object { private: isa_t isa; public: // ISA() assumes this is NOT a tagged pointer object Class ISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); . Other method declarations are omitted here}Copy the code

The objc_object structure contains an ISA pointer, so we continue to look at the source of isa_t

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
}
Copy the code

We convert the above source definition into a class diagram, which is

Objective-c objects are implemented as C constructs. In ObjC2.0, all objects contain an ISA_T union. Typedefs are the id type of objc_object, which is the common id type. This structure contains only one ISA_T union. Since objc_class inherits from Objc_Object, which also contains isa Pointers of type ISA_T union, a class in Objective-C is also an object. In objC_class, in addition to ISA, there are three other member variables, one is the pointer to the superclass, one is the method cache, and the last one is the linked list of instance methods of the class.

isa

When an instance method of an object is called, the corresponding class is found through ISA and the method is found in class_datA_bits_t of that class. Class_data_bits_t is the data region that points to the class object. Find the corresponding implementation of the corresponding method in the data area. But what is inside the ISA of the class object when we call the class method? In order to be consistent with the mechanism of object lookup methods, the concept of meta-class is introduced.

What is a meta-class?

The type of a class object is called a metaclass, which is used to express the metadata that the class object itself has. Class methods are defined here, because these methods can be understood as instance methods of class objects. Each class has only one class object, and each class object has only one metaclass associated with it. When you send a message like [NSObject alloc], you are actually sending the message to a Class Object, which must be an instance of a metaclass, which is also an instance of the root meta Class. All metaclases end up pointing to the root metaclass as its superclass. All metaclases have a list of methods that can respond to the message, so when [NSObject alloc] is sent to the class object, objc_msgSend() looks for a method in its metaclass that can respond to the message. If it finds one, A method call is then made to the class object.

  • When an instance method of an object is called, the implementation of the method is obtained in the class through the object’s ISA.

  • When a class method of a class object is called, the implementation of the method is obtained in the metaclass through the class isa.

Meta-class is important because it stores all the class methods of a class. Each class will have a separate meta-class, because the class methods of each class are unlikely to be identical.

The following diagram illustrates the relationship between objects, classes, and metaclasses:

The solid line is the super_class pointer and the dotted line is the ISA pointer.

  • Root class is really just NSObject, and NSObject has no superclass, so the superclass of Root class points to nil

  • Each Class has an ISA pointer to a unique Meta Class

  • The superclass of Root class(meta) points to Root class(class), which is NSObject, forming a loop. Isa pointer to each Meta class points to Root class (Meta)

Objc_class allows you to see at runtime that a class is also associated with its superclass Pointers, class names, member variables, methods, caches, and associated protocols.

cache_t

struct cache_t { struct bucket_t *_buckets; mask_t _mask; mask_t _occupied; . Omit other methods};#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif

public:
    inline cache_key_t key() const { return _key; }
    inline IMP imp() 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

Cache_t contains a bucket_t structure and two unsigned int variables.

  • Mask: used to cache the total number of buckets.

  • Occupied: Indicates the number of occupied cache buckets.

  • The bucket_t structure stores an unsigned long and an IMP. IMP is a function pointer that points to an implementation of a method.

  • The _buckets is a hash table that stores a linked list of methods.

The main purpose of the Cache is to optimize the performance of method calls. When the object receiver calls the method message, it first finds its corresponding class according to the object Receiver’s ISA pointer, and then searches for the method in the class’s methodLists. If none is found, it uses the super_class pointer to look for methodLists in the parent class. Once found, the method is called. If it is not found, the message may be forwarded or ignored. But this lookup is inefficient, because a class is often called only about 20% of the time, but 80% of the time. So Cache is used to Cache frequently called methods. When a method is called, it is searched in Cache first. If it is not found, it is searched in methodLists

class_data_bits_t

The most complex of objc_classes is bits. The class_data_bits_t structure contains too much information, mainly class_rw_t, Retain/release/autorelease/retainCount and alloc information, a lot of access method is also revolves around it. Objc-runtimenew.h objc-Runtimenew.h objc-Runtimenew.h objc-runtimenew.h

struct class_data_bits_t {

	// Values are the FAST_ flags above.
	uintptr_t bits;
	class_rw_t* data() {
	   return(class_rw_t *)(bits & FAST_DATA_MASK); }... 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. Omit the associated methods of the flags operation}; 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() const {
        returnbaseMethodList; }};Copy the code

Comments written to class_datA_bits_t in the objC_class structure are equivalent to the class_rw_T pointer with rr/alloc flags.

class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
Copy the code

It gives us a convenient way to return the class_rw_t * pointer:

class_rw_t *data() {
    return bits.data();
}
Copy the code

The contents of class_rw_t can be dynamically modified at runtime, which provides the Runtime’s ability to extend classes, so to say that most of the runtime’s extension to classes is stored here. Class_ro_t, on the other hand, stores most of the information that the class has already determined at compile time. Both contain information about the class’s methods, attributes (member variables), protocols, and so on, but the lists that store them are implemented in different ways, namely, RW-readwrite and ro-Readonly. The following figure

Relationship between class_rw_t and class_ro_t

Before a class is initialized, objc_class->data() returns a pointer to a class_ro_t structure. When the static Class realizeClass(Class CLS) static method is called on the first Class initialization, it opens up the class_rw_t space and assigns the class_ro_t pointer to class_rw_t->ro, MethodizeClass is called to load all the properties, protocols, and methods in the class.

The entire runtime process can be described as follows:

In summary, let’s summarize the differences between ObjC 1.0 and 2.0

The message

Messages are sent in Objc with brackets ([]) around the receiver and message, and messages are not bound to method implementations until runtime.

Objc_msgSend function

The compiler converts the message expression [Receiver Message] into a call to objc_msgSend(Receiver, selector), which converts the receiver and the method name displayed in the message — the method selector, As its two main parameters.

If there are other parameters in the message, it is:

id objc_msgSend(id self, SEL op, ...) ;Copy the code

id

Objc_msgSend the first argument is of type ID, which is a pointer to an instance of the class, as described earlier

typedef struct objc_object *id;
Copy the code

SEL

The second argument to the variable argument function objc_msgSend is SEL, which is the representation of selector in Objc. Selector is the method selector, which can be interpreted as the ID of the discriminating method. The data structure of this ID is SEL:

typedef struct objc_selector *SEL;
Copy the code

It’s just a C string that maps to a method. You can use the Objc compiler command @selector() or the Runtime sel_registerName function to get a method selector of type SEL.

Note that the @selector() selector is only related to the function name. Methods of the same name in different classes have the same method selector. Even if the method name is the same but the variable type is different, they will have the same method selector.

IMP IMP is defined in objc.h as:

typedef void (*IMP)(void /* id, SEL, ... */ ); 
Copy the code

It is simply a function pointer that is generated by the compiler. When you send an ObjC message, the code that it eventually executes is specified by this function pointer, IMP, which points to the implementation of this method. If we have an entry to execute a method of an instance, we can bypass the messaging phase and execute the method directly

IMP points to the same type of method as objc_msgSend, including id and SEL. Each method name corresponds to a method selector of SEL type, and the method implementation corresponding to SEL in each instance object must be unique. The unique method implementation address can be determined by a set of ID and SEL parameters. And vice versa.

Method

Method is a type that represents a Method in a class.

typedef struct method_t *Method;
Copy the code

Objc_method stores the method name, method type, and method implementation:

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() (const method_t& lhs,
                         const method_t& rhs)
        { returnlhs.name < rhs.name; }}; };Copy the code

The method name type is SEL. As mentioned earlier, methods with the same name have the same method selector even if they are defined in different classes. The method type types is a char pointer that actually stores the parameter types and return value types of the method. Imp points to the implementation of a method, which is essentially a function pointer

Steps for sending messages

1. Check whether the selector is to be ignored. Mac OS X development, for example, has garbage collection and ignores functions like retain and release.

2. Check if this target is nil. When it’s nil, if there’s a nil handler here, it jumps to that function; If there is no function to handle nil, the scene is automatically cleaned up and returned. This is why sending messages to nil in OC does not crash.

3, if the above two pass, then start to find the IMP of this class, first from the cache to find, if you can jump to the corresponding function to execute.

4, If you can’t find it in the cache, go to the method table.

5. If the subtable cannot be found, query the subtable of the superclass, and keep looking in the superclass until the NSObject class is found.

6. If you can’t find it, start dynamic method parsing, which will be discussed later.

Ps: A distribution is essentially a list of methods in a Class that associates method selectors with method implementation addresses

The following figure shows the message sending process

Hidden parameters in the call

When objc_msgSend finds the corresponding implementation of the method, it calls the method implementation directly and passes all the parameters in the message to the method implementation. It also passes two hidden parameters:

  • The object that receives the message (that is, what self points to)
  • Method selector (what _cmd points to)

They are hidden because they are not declared in the source method definition. They are inserted into the implementation when the code is compiled. Although these parameters are not explicitly declared, we can still refer to them in source code. In the following example, self receives the message for the Strange object, and _cmd references the selector for the strange method:

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}
Copy the code

Of the two arguments, self is the more useful. In effect, it is the way to access instance variables of the message receiver object in the method implementation.

When the super keyword in the method receives a message, the compiler creates an objc_super structure:

struct objc_super { id receiver; Class class; };
Copy the code

This structure specifies the definition by which messages should be passed to a particular superclass. But the receiver is still self itself. This is important to note because when we want to get the superclass through [super Class], the compiler just passes the id pointer to self and SEL of class to objc_msgSendSuper. Because the class method is only found in NSObject, the class method calls object_getClass() and then objc_msgSend(objc_super->receiver, @selector(class)). The first argument passed in is an ID pointer to self, the same as calling [self class], so we always get the type of self.

Obtain method address

The only way to get around dynamic binding is to simply get the address of a method and call it, which is rarely used unless you need to repeatedly call a method repeatedly to avoid the overhead of messaging.

Using methodForSelector:, defined in the NSObject class, you can get an IMP that points to the implementation method and call it directly using IMP

The following example shows how to call setFilled: to implement this method:

Void (* setter) (id, SEL, BOOL); int i; Setter = (void (*) (id, SEL, BOOL)) [target methodForSelector: @selector (setFilled :)];
for(I = 0; i <1000; I ++) setter (targetList [I], @selector (setFilled :), YES);Copy the code

When a method is called as a function, we need to explicitly give two hidden arguments to the passing process: the receive object (self) and the method selector (_cmd).

Use methodForSelector: circumventing dynamic binding saves much of the time required for message delivery. But the savings are only effective if a particular message is called multiple times, as shown in the for loop above.

PS: methodForSelector: Methods are provided by the Cocoa Runtime system, not a feature of Objc itself.

Dynamic method parsing

Sometimes we want to provide an implementation of a method dynamically. For example, we can use the @dynamic keyword to decorate a property in the class’s implementation file:

@dynamic propertyName;
Copy the code

This means that we will provide access to this property dynamically, which means that the compiler will no longer generate setPropertyName: and propertyName methods for us by default, but will need us to provide them dynamically. We can override the resolveInstanceMethod: and resolveClassMethod: methods to add instance method implementations and class method implementations, respectively.

Because the Runtime system cannot find a method to execute in the Cache and method table (including superclasses), it calls resolveInstanceMethod: or resolveClassMethod: to give the programmer a chance to dynamically add method implementations. We need to add a specific method implementation to a specific class using the class_addMethod function:

void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... } @implementation MyClass + (BOOL)resolveInstanceMethod:(SEL)aSEL {if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end
Copy the code

The above example for resolveThisMethodDynamically method to add the implementation content, namely dynamicMethodIMP method with the code. Where “v@:” represents method parameter coding, v represents Void, @ represents OC object, and: represents SEL type

PS: Dynamic method resolution is performed before the message forwarding mechanism is immersed. If respondsToSelector: or instancesRespondToSelector: method is executed, the dynamic method the parser will be the first to provide the method to give a selector corresponding IMP. If you want the method selector to be passed to the forward mechanism, then resolveInstanceMethod: returns NO

forward

It is an error to send a message to an object that does not process the message. However, the Runtime system gives the receiving object an opportunity to process the message again before throwing an error.

The following figure is the flow chart of message forwarding:

Alternate receiver

Before performing message forwarding mechanism, the runtime system will give us an opportunity again, by overloading – (id) forwardingTargetForSelector: (SEL) aSelector method to replace the message receiver for other objects:

+ (BOOL) instancesRespondToSelector aSelector: (SEL) {/ / return YES, there is no method of dynamic analysis, to enter the next stepreturnYES; } / / this method replace the message receiver for other objects - (id) forwardingTargetForSelector aSelector: (SEL) {if (aSelector == @selector(work)) {
        return [[Person alloc] init];
    }
    return  [super forwardingTargetForSelector:aSelector];
}
Copy the code

If you want to replace the class methods recipient, need to overwrite the + (id) forwardingTargetForSelector (SEL) aSelector method, and returns the class object:

+ (BOOL) instancesRespondToSelector aSelector: (SEL) {/ / return YES, there is no method of dynamic analysis, to enter the next stepreturnYES; } / / if you want to replace the class methods recipient + (id) forwardingTargetForSelector aSelector: (SEL) {if (aSelector == @selector(work)) {
        return NSClassFromString(@"Person");
    }
    return  [super forwardingTargetForSelector:aSelector];
}
Copy the code

forwarding

The message forwarding mechanism is triggered when neither the dynamic method resolution nor the alternate receiver processes. The forwardInvocation: method is invoked at this point and we can override this method to define our forward logic:

/ / * * * * * * * * * * * * * * * * * * complete message forwarding * * * * * * * * * * * * * * * * * * * * * * * * * + (BOOL) instancesRespondToSelector aSelector: (SEL) {/ / return YES, No dynamic method resolution, go to the next stepreturnYES; } - (id) forwardingTargetForSelector aSelector: (SEL) {/ / spare the recipient is not set, to enter the next stepreturn  nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"travel"]) {// sign, enter forwardInvocationreturn [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return[super methodSignatureForSelector:aSelector]; } // message invocation - (void)forwardInvocation:(NSInvocation *)anInvocation {SEL SEL = anInvocation. Person *p = [[Person alloc] init];if ([p respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:p];
    }else{ [self doesNotRecognizeSelector:sel]; }}Copy the code

When an object cannot respond to a message because there is no method implementation, the runtime system notifies the object via forwardInvocation: message. Each object inherits the forwardInvocation: method from the NSObject class. Method of NSObject, however, simply call the doesNotRecognizeSelector:. By implementing our own forwardInvocation: method, we can forward messages to other objects in this method implementation.

ForwardInvocation: the method is like a distribution center for unrecognized messages that are forwarded to different receiving objects. Or it can act like a transport station and send all messages to the same receiving object. It can translate one message into another, or simply “eat” some messages, so there are no responses and no errors. The forwardInvocation method can also provide the same response for different messages, depending on how the method is implemented. What this method provides is the ability to link different objects to a message chain.

Note: the forwardInvocation: method is only called if the message receiving object cannot properly respond to the message. So, if we want an object to forward the negotiate message to other objects, that object cannot have an negotiate method. Otherwise, forwardInvocation: will not be called.

Forwarding and multiple inheritance

Forwarding, like inheritance, can be used to add some multi-inheritance effects to Objc programs, as shown in the figure below, where an object is forwarded by message as if it could borrow or “inherit” a method implementation from another object.

So from an inheritance hierarchy, the object that forwards the message has two branches of “inheritance” methods – its own branch and the branch of the object that responds to the message. In the image above, Warrior and Diplomat are not related by inheritance, but Warrior forwards the Negotiate message to Diplomat as if Diplomat were a superclass of Warrior.

Message forwarding provides most of the functionality we want from multiple inheritance, but there are significant differences: multiple inheritance is combining different functionality in a single object, which tends to favor larger, more versatile objects; Message forwarding, on the other hand, assigns different responsibilities to different objects, breaks down the problem very finely, forwards only for the methods you want to borrow, and the forwarding mechanism is transparent.

Surrogate Objects

Forwarding not only mimics multiple inheritance, but also enables lightweight objects to represent or “override” heavyweight objects, replacing others and sending messages to them

For example, if you have an object A that manipulates A large amount of data (creating A complex image or reading the contents of A file on disk), because running A can be time-consuming, you want to do it when you really need it or when system resources are free. But at the same time, we need to have A placeholder for A in order for the rest of the objects in the application to work. In this case, we can start with a lightweight Surrogate rather than a complete object. This object can do some things on its own, such as answering questions about the data, but mostly it just reserves a place for the larger object and forwards messages to it when the time comes. When the Surrogate’s forwardInvocation: method receives a message to another object, it first ensures that the object exists and creates it if it does not. Because all of the larger object’s messages pass through the Surrogate, the larger object is the same as the larger object for the rest of the program.

Forwarding and inheritance

Although forwarding is a lot like inheritance, the NSObject class does not confuse the two. Methods like Respondto Selector and isKindOfClass only take into account inheritance, not forward chains. For example, a Warrior object in the figure above is asked if it can respond to the Negotiate message:

if([aWarrior respondsToSelector: @selector (negotiate)])...Copy the code

The result is NO, which responds to the message by forwarding it to the Diplomat class, although it is able to accept the Negotiate message without an error.

If you want Warrior to resemble Diplomat’s negotiate method, you need to re-implement the respondsToSelector: and isKindOfClass: methods to add to your forwarding algorithm:

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
        /* Here, test whether the aSelector message can     *
         * be forwarded to another object and whether that  *
         * object can respond to it. Return YES if it can.  */
    }
    return NO;
}
Copy the code

In addition to respondsToSelector: and isKindOfClass: instancesRespondToSelector: should also be written in a forwarding algorithm. If a protocol is used, conformto Protocol: also participates. Similarly, if an object forward it accepts any remote message, it must give a methodSignatureForSelector: to return accurate description methods, this method will be forwarded message final response. Such as an object to its replacement object forwarding messages, it needs to implement methodSignatureForSelector: as follows:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if(! signature) { signature = [surrogate methodSignatureForSelector:selector]; }return signature;
}
Copy the code

Method Swizzling

Swizzling is a powerful part of the Runtime API. It can change any Method through the Runtime API. It can hook into any OC Method at runtime by using the class name/Method name, replace any implementation of any class, and add any new classes

Method Swizzling principle:

Method Swizzing is primarily used to swap two methods at run time, essentially swapping IMP and SEL. We can write Method Swizzling code anywhere, but swaps only work after this Method Swilzzling code has been executed

Method Swizzling sample

#import <objc/runtime.h>@implementation UIViewController (Swizzling) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // When swizzling a class method, use the following: // Class aClass = object_getClass((id)self); Class aClass = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(cy_viewWillApper:); Method originalMethod = class_getInstanceMethod(aClass, originalSelector); Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector); // Check if there is any implementation of the method to replace in the original class. If the method doesn't exist, we add the implementation of the replaced method BOOL didAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));if(didAddMethod) {class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); }elseMethod_exchangeImplementations (originalMethod, swizzledMethod) {// Implementations implementations (originalMethod, swizzledMethod); }}); }#pragma mark - Method Swizzling

- (void)cy_viewWillApper:(BOOL)animated {
    [self cy_viewWillApper:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end
Copy the code

Note: If the class does not have a method to replace, add and replace the implementation of the two methods with the class_addMethod and class_replaceMethod functions. If there is already a Method in the class that you want to replace, then we call method_exchangeImplementations which swaps the IMP of the two methods, which is a convenient way That Apple gives us to implement Method Swizzling. Method_exchangeImplementations the method does something equivalent to the atomic operation:

IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);
Copy the code

Method Swizzling Precautions:

Swizzling should always be performed in +load

Objective-c automatically calls the class’s two methods + Load and + Initialize at runtime. The +load method is called when the class is initially loaded, and the + Initialize method is called lazy-loaded. If the program never sends a message to a class or its subclasses, the + Initialize method will never be called. So, in contrast to +initialize, +load is guaranteed to be loaded during class initialization.

Comparison table of +load and +initialize

type +load +initialize
Call time Initialization is added to runtime Is called in lazy loading mode
Call to order Superclass -> subclass -> category Parent class -> child class
Call the number 1 time Many times
Whether an explicit call to a superclass implementation is required no no
Whether to use the implementation of the parent class no is
Implementation in classification Both classes and classifications are executed Override the methods in the class, performing only the implementation of the classification

Swizzling should always be performed in dispatch_once

Swizzling changes global state, so taking a few precautions at run time using dispatch_once ensures that code is executed only once, no matter how many threads, and that it is an atomic operation, thread safety is important.

For example, if you do not write dispatch_once and Swizzling the objectAtIndex: method in both NSArray and NSMutableArray, Swizzling in NSArray may fail.

The reason: if this Swizzling is performed multiple times, after multiple IMP and SEL swaps, the result may be the state before the swap. For example, B method of parent class A and D method of subclass C swap, swap once, parent class A holds the IMP of D method, subclass C holds the IMP of B method, but swap once again, and restore. The parent class A still holds the IMP of the method B, and the child class C still holds the IMP of the method D. If “dispatch_once” is not sent, Swizzling fails.

3, Swizzling in +load execution, do not call [super load]

Calling the [super Load] method in the + (void)load method causes the parent class’s Swizzling to be repeated twice, invalidating the parent class’s Swizzling.

Class aClass = object_getClass((id)self

Swizzling class method using object_getClass((id)self) to get aClass

Object_getClass ((id)self) and [self class] both return a result type of class, but the former is a metaclass and the latter is itself, because self is a class and not an instance.

Note the difference between [NSObject Class] and [object Class] :

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
Copy the code

Cy_viewWillAppear: call [self cy_viewWillAppear: animated]

The definition of the cy_viewWillAppear: method looks like recursive calls cause an infinite loop, but they don’t. Because the [self cy_viewWillAppear:animated] message dynamically finds the implementation of the cy_viewWillAppear: method, whose implementation we’ve swapped with the viewWillAppear: method implementation, this code not only doesn’t loop, If you change [self cy_viewWillAppear:animated] to [self viewWillAppear:animated] it will trigger an endless loop.

Code and references

Github source address code involves: dynamic Method parsing, alternate receiver, complete message forwarding, Method Swizzling

References:

Runtime official documentation

Objective-C +load vs +initialize

Objective-C Runtime

Principle of Objective-C message sending and forwarding mechanism

Delve into the structure of methods in ObjC

Objective-c Runtime day 1 — ISA and Class

Second day of hospital stay in Objective-C Runtime — message sending and forwarding

Objective C Runtime discharge day 3 – How to use Runtime correctly