This article will document objective-C message passing and forwarding mechanism, Method Swizzling related information, if any errors are welcome to point out ~

Objective-c is essentially a domain specific language based on THE C language. C is a static language that decides which function to call at compile time. Objective-c, on the other hand, is a dynamic language that cannot decide at compile time which function will be called when it is finally executed (function calls in Objective-C are called messaging). This dynamic binding mechanism in Objective-C is implemented through an intermediate layer, the Runtime.

Message passing (method invocation)

In Objective-C, messages are not bound to method implementations until runtime. The compiler converts the message expression into a call to a message function.

The message expression in OC is as follows (method call)

id returnValue = [someObject messageName:parameter];
Copy the code

The compiler sees this message and converts it into a standard C function call

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
Copy the code

We can see that the transformation uses the objc_msgSend function, which takes the message receiver and method name as the main parameters, as follows:

Objc_msgSend (receiver, selector) // With no arguments objc_msgSend(receiver, selector, arg1, arg2...) / / parametersCopy the code

Objc_msgSend implements the dynamic binding mechanism through the following steps.

  • First, getselectorMethod implementation pointing to. Since the same method may have different implementations in different classes, according toreceiverClass to which it belongs.
  • Secondly, transmissionreceiverObject, method specified arguments to invoke method implementation.
  • Finally, the return value of the method implementation is returned.

The key to messaging is the Runtime — the objC_class structure logged by instance objects, class objects, and metaclass objects, which has two key fields:

  • isa: pointer to the parent class
  • methodLists: class methods are published (dispatch table)

When a new object is created, it is allocated memory and its member variables are initialized. The ISA pointer is also initialized to give the object access to the class and its inheritance chain.

A diagram of the message delivery process is shown below.

  • When a message is delivered to an object, it is first cached from the runtime systemobjc_cacheTo find. If found, execute. Otherwise, proceed with the following steps.
  • objc_msgSendPass objectisaThe pointer gets the structure of the class and is published in the method sectionmethodListsTo find the methodselector. If not found, will be along the classisaFind the parent class and publish in the subclass of the parent classmethodListsContinue to look for.
  • And so on, all the way down the inheritance chain of the classNSObjectClass. Once you’ve foundselectorTo execute the implementation of the method and add the method to the cacheobjc_cache. If you still don’t find itselector, will enterforwardprocess

forward

When an object can receive a message, it follows the normal messaging process. What happens when an object cannot receive a message?

  • By default, if a[object message]Call the method in the form of ifobjectUnable to respond tomessageMessage, the compiler will report an error.
  • If isperformSeletor:Call a method in the form of, wait until runtime to determineobjectCan I receive it?messageThe message. If not, the program crashes.

For the latter, when it is uncertain whether an object can receive a message, we can call respondsToSelector: to determine.

if ([self respondsToSelector:@selector(method)]) {
    [self performSelector:@selector(method)];
}
Copy the code

In fact, when an object is unable to receive a message, a mechanism called message forwarding is initiated. Through the message forwarding mechanism, we can tell objects how to handle unknown messages.

The message forwarding mechanism can be roughly divided into three steps:

  • Dynamic Method Resolution
  • Alternate receiver
  • Full message forwarding

The following figure shows a diagram of the message forwarding process.

Dynamic method parsing

This is the first stage of the entire message forwarding process. If a message cannot be responded to, the method of the class will be called:

// Instance object + (BOOL)resolveInstanceMethod (SEL)selector // Class object + (BOOL)resolveClassMethod (SEL)selectorCopy the code

Where the selector parameter is an unprocessed method.

The return value @return indicates whether a new method can be added to the process. This is usually done using the @dynamic property:

/ * * * * * * * * * * * * * * use resolveInstanceMethod implement @ the dynamic properties of * * * * * * * * * * * * * * / id autoDictionaryGetter (id self, SEL _cmd); void autoDictionarySetter(id self, SEL _cmd, id value); + (BOOL)resolveInstanceMethod:(SEL)selector { NSString *selectorString = NSStringFromSelector(selector);if (/* selector is from a @dynamic property */)
    {
        if ([selectorString hasPrefix:@"set"]) {class_addMethod(self, selector, (IMP)autoDictionarySetter,"v@:@");
        }
        elseClass_addMethod (self, selector, (IMP)autoDictionaryGetter,"@ @.");
        }
        return YES;
    }
    return [super resolveInstanceMethod:selector];
}
Copy the code

Standby receiver

This is the second stop in the entire message forwarding mechanism, which, as the name suggests, is looking for an alternate rescue recipient. At this stage, the system calls this method:

- (id)forwardingTargetForSelector:(SEL)aSelector;
Copy the code

The passed parameter aSelector is also an unhandled method.

The return value is the currently found standby receiver, or nil if none, to proceed to the next stage.

Complete message forwarding mechanism

If the first two phases fail to process the message, the full message forwarding mechanism is initiated.

The NSInvocation object is first created, with all the details of the unprocessed message in it. When the NSInvocation object is fired, the message-Dispatch system assigns the message to the target. This method is called:

- (void)forwardInvocation:(NSInvocation *)anInvocation;
Copy the code

The anInvocation passed in contains all the content of the message.

If still unable to process the message at this time, you could have the inheritance order step by step to the parent class calls the same method, until finally the NSObject class, at that time if you don’t have method to handle the message, it invokes doesNotRecognizeSelector: throw an exception.

At this point, the whole process of message forwarding is complete.

Method Swizzling

The Method Swizzling technique in Objective-C allows you to dynamically replace Method implementations. Hook functionality is a more flexible way to “override” methods than subclassing. This means that during development, we may encounter the API provided by the system does not meet the actual requirements, and we want to modify it to achieve the desired effect.

Method Swizzling principle

The implementation of Method Swizzling takes full advantage of the dynamic binding mechanism.

When you call a method in Objective-C, you’re actually sending a message to an object, and the only way to find that message is by the method name selector. Each class has a method list, objC_method_list, that holds all of its methods, objc_Methods.

typedef struct objc_method *Method struct objc_method{ SEL method_name OBJC2_UNAVAILABLE; // Method name char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; // method implementation}Copy the code

Each method objC_method holds a mapping between the method name (SEL) and the method implementation (IMP). Method Swizzling resets SEL and IMP mappings. As shown below:

For detailed application scenarios, see iOS Development: Runtime (2) Method Swizzling

reference

IOS Development – Runtime – Messaging and forwarding mechanism

Objective-c Runtime messaging and forwarding

Effective Objective-C Notes: Understand the messaging mechanism

Objective-c associative object with Method Swizzling