Make writing a habit together! This is the third day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

OC Basic principles of exploration document summary

OC Runtime guide document reading

Main Contents:

  1. Message is sent
  2. Dynamic method parsing
  3. forward
  4. Type encoding
  5. Attribute declarations

I did not completely translate word for word, but read comprehensively, because I felt it was better to read the original English than to translate word for word

Objective C Runtime Programming Guide

1, the introduction

The OC language deferred as many decisions as possible from compile and link time to run time. OC will always solve problems in a dynamic manner whenever possible, so OC needs not only a compiler, but also a runtime system to execute the compiled code.

This document describes 1) the NSObject class and how the OC program interacts with the runtime system, 2) specifically focuses on loading new classes at runtime and forwarding messages to other objects, 3) it also provides information on how to find about objects while the program is running

In earlier versions, if you wanted to change the layout of instance variables in a class, you recompiled all subclasses of that class. In the current version, if you change the layout of instance variables in a class, you do not need to recompile any subclasses of that class.

2. Interaction with the runtime system

There are three ways to interact with the runtime system

2.1 Through the OC source code

Most of the time, the runtime system runs automatically in the background, and we just need to write and compile the OC source code. When OC classes and methods are compiled, compile time automatically creates C/C++ data structures and functions to interact with the system. These data structures enable the language to be dynamic. These data structures contain information defined by classes and protocols, classes, objects, and so on.

2.2 Methods of the NSObject class in the Foundation framework

  • Some methods of the NSObject class provide information about querying the runtime system, meaning that the NSObject class simply gets information from the runtime system that can be used to perform some self-examination on objects.
  • Most classes in your program are subclasses of NSObject, so most inherit the methods of NSObject, and therefore the behavior of NSObject (NSProxy is an exception).
  • In some cases, the NSObject class specifies a template that says what should be done, but it doesn’t implement it, that is, how it should be done.
  • The Runtime is called indirectly by calling the NSObject method

Common methods

2.3 By directly calling the functions of the runtime system

  • A runtime system is a dynamic library with an exposed interface, consisting of a collection of data structures and functions
  • The declaration headers for these data structures and functions are in /usr/include/objc
  • These functions use pure C functions to implement OC functions
  • These functions make it possible to access the runtime system interface and provide development tools

Call the runtime C function directly

3. Message sending

This chapter shows you how to convert message expressions into objc_msgSend function calls and how to refer to methods by name. It then explains how to take advantage of objc_msgSend and, if necessary, how to bypass dynamic binding.

3.1 objc_msgSend function

In Objective-C, messages are not bound to method implementations until runtime. The compiler converts message expressions to [Receiver Message]

Call the message passing function objc_msgSend. This function takes the receiver and the name of the method mentioned in the message (that is, the method selector) as its two main arguments. objc_msgSend(receiver, selector)

Any parameters passed in the message will also be passed to objc_msgSend:

objc_msgSend(receiver, selector, arg1, arg2, …)

Dynamic binding

The message passing function is dynamically bound:

  • First bind the procedure (method implementation) to find a selector reference. Because the same method can be implemented by different classes, the exact process it finds depends on the recipient.
  • It then invokes the procedure, which will receive the object (a pointer to its data) as well as the method.
  • Finally, it passes the return value of the procedure as its own return value.

3.1 Obtaining function pointer IMP

The key to messaging is the structure the compiler builds for each class and object. Each class structure contains the following two basic elements:

  • Pointer to the superclass.
  • A class scheduling table. Entries in this table associate a method selector with the class-specific address of the method it identifies. The selector of the setOrigin: : method is associated with the address of the setOrigin: (implementation) method, the selector of the display method is associated with the address of display, and so on.

When a new object is created, memory is allocated for it and instance variables are initialized. The first variable in an object variable is a pointer to its class structure. This pointer, called ISA, gives the object access to its class and through that class to all classes it inherits.

Schematic diagram:

Description:

  1. When a message is sent to an object, the messaging function follows the object’s ISA pointer to a class structure in which it looks for method selectors in the scheduling table.
  2. If no selector is found there, objc_msgSend follows the pointer to the superclass and tries to find the selector in its schedule table.
  3. Successive failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class.
  4. Once the selector is found, the function calls the method entered in the table and passes it the data structure that received the object.

Quick Lookup process

To speed up the messaging process, the runtime system caches selectors and addresses when using methods. Each class has a separate cache that can contain selectors for inherited methods as well as methods defined in the class. Before searching the scheduling table, the messaging routine first checks the cache of the receiving object class (in theory, a method used once might be used again). If the method selector is in the cache, the message passing is only slightly slower than the function call. Once a program has been running long enough to “warm up” its cache, almost all the messages it sends will find a caching method. The cache grows dynamically to accommodate new messages as the program runs

3.3 Using hidden functions

When objc_msgSend finds the procedure that implements the method, it calls the procedure and passes it all the parameters in the message. It also passes two hidden arguments to the procedure:

  • Receive the object
  • Method selector

These parameters provide each method implementation with explicit information about the two parts of the message expression that invoked it. They are called “hidden” because they are not declared in the source code that defines the method. They are inserted into the implementation when the code is compiled.

Although these parameters are not explicitly declared, the source code can still refer to them (just as it can refer to instance variables of the receiving object). Method references the receiving object as self and its own selector as _cmd. In the following example, _cmd represents the selector for the exception method, and self points to the object that received the exception message.

Code:

-strange {//_cmd: indicates an exception selector. //self: indicates the id of the object that received the exception message. Target = getTheReceiver(); SEL method = getTheMethod(); if ( target == self || method == _cmd ) return nil; return [target performSelector:method]; }Copy the code

Obtain method address

The only way to circumvent dynamic binding is to get the address of the method and then call it directly as if it were a function. This may be appropriate when a particular method will be executed many times in a row, and you want to avoid the overhead of messaging each time the method is executed. Using a methodForSelector defined in the NSObject class, you can request a pointer to the procedure that implements the method, and then use that pointer to call the procedure. MethodForSelector: The returned pointer must be carefully converted to the correct function type. The type conversion should include return types and parameter types.

Code:

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

Description:

  • The first two arguments passed to the procedure are the receive object (self) and the method selector (_cmd). These parameters are hidden in the method syntax, but must be explicit when the method is called as a function.
  • Use methodForSelector: bypassing dynamic binding saves much of the time required for message delivery. However, the savings are significant only if a particular message is repeated multiple times, as in the for loop shown above.

4. Dynamic method analysis

This chapter describes how to dynamically provide the implementation of a method.

You can implement the resolveInstanceMethod: and resolveClassMethod: methods to dynamically provide implementations for a given selector for instance and class methods, respectively. An Objective-C method is just a C function that takes at least two arguments self and _cmd. You can add a function to a class as a method using the function class_ addMethod. Therefore, the following features are considered:

void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
Copy the code

You can use resolveInstanceMethod to add it dynamically to a class as a method (called ResolveThisMethodDynamic), as follows:

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

Summary: Forwarding methods (as described in message forwarding) and dynamic method parsing are largely orthogonal. Classes have the opportunity to resolve methods dynamically before the forwarding mechanism takes effect. If the call respondsToSelector: or instanceRespondToSelector:, dynamic methods parser will have the opportunity to provide IMP selector in the first place. If you implement resolveInstanceMethod: but want to actually forward specific selectors through a forwarding mechanism, return NO for those selectors.

Dynamic loading

Objective-c programs can load and link new classes and categories at run time. The new code is incorporated into the program and is the same as the classes and categories loaded at the beginning. Dynamic loading can be used to do many different things. For example, individual modules in the system preferences application are loaded dynamically. In Cocoa environments, dynamic loading is often used to customize applications. Others can write modules that the program loads at run time, just as Interface Builder loads custom palettes and the OSX System Preferences application loads custom preference modules. Loadable modules extend the functionality of an application. They contribute in ways you allow them to, but fail to anticipate or define yourself. You provide the framework, but someone else provides the code

5. Message forwarding

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 a second chance to process the message before declaring an error.

forwarding

If the message is sent to the object does not handle the message, before announced errors, runtime send the object a forwardInvocation: message, including NSInvocation object as its only parameter, NSInvocation object to encapsulate the original message and its parameters passed. You can implement the forwardInvocation: method to provide a default response to the message, or otherwise avoid errors. As the name implies, forwardInvocation: usually used to forward a message to another object. To understand the scope and intent of forwarding, imagine the following scenario: First, suppose you are designing an object that can respond to a message called Negotiate, and you want its response to include a response from another object. This can be done easily by passing negotiation messages to other objects in the body of the negotiation method being implemented. Further, suppose you want the object’s response to the Negotiate message to be exactly the same as it would be implemented in another class. One way to do this is to have your class inherit the method from another class. However, it may be impossible to arrange things this way. There may be a good reason why your class and the class that implements Negotiate are in different branches of the inheritance hierarchy. Even if your class cannot inherit a negotiated method, you can still “borrow” the method by implementing a version of the method that simply passes the message to an instance of another class:

- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] ) return [someOtherObject negotiate];
return self;
}
Copy the code

This can be a bit cumbersome, especially if there are many messages that need to be passed from one object to another. You must implement a method to override every method you want to borrow from another class. Also, you probably don’t want to know where you wrote the entire code. This collection may depend on run-time events and may change as new methods and classes are implemented in the future.

The second opportunity provided by the forwardInvocation is that message provides a less AD hoc solution to this problem and is dynamic rather than static. It works like this: when an object because there is no method to match the selector in the message and can’t response message, the runtime system by sending forwardInvocation: message notification object. Each object inherits a forwardInvocation: method from the NSObject class. However, the method version of NSObject just calls doesNotRecognizeSelector:. By rewrite NSObject version and implement their own version, you can use the forwardInvocation: message provides the opportunity to forward the message to the other objects.

To forward the message, the forwardInvocation: method simply:

  • Determine the location of the message
  • And send the location there along with the original message

You can send messages using the invokeWithTarget: method:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector: [anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
Copy the code

The return value of the forwarded message is returned to the original sender. All types of return values can be passed to the sender, including IDS, structures, and double-precision floating-point numbers.

ForwardInvocation: the method can act as a distribution center for unrecognized messages to different receivers. Or it can be a relay station that sends all messages to the same destination. It can convert one message into another, or simply “swallow” some messages so that there is no response and no error. The forwardInvocation method can also combine multiple messages into a single response. ForwardInvocation: what is done depends on the implementers. However, it provides the opportunity to link objects in the forward chain, and provides the possibility for programming.

Note: forwardInvocation: methods can only handle messages if they do not call existing methods in the nominal receiver. For example, if you want your object to forward the Negotiate message to another object, it cannot have its own negotiate method. If so, the message will never reach the forwardInvocation:.

Forwarding and multiple inheritance

Message forwarding can be used to simulate multiple inheritance, where an object responds to a message by forwarding it as if it had inherited another class and used its methods

Description: Warrior invoked Diplomat’s Negotiate method through message forwarding, seemingly inheriting from Diplomat

Proxy objects

Message forwarding allows message processing on behalf of more objects with a single lightweight object (message broker object).

Other types of message broker objects also exist. For example, suppose you have an object that needs to manipulate a large amount of data — it might need to create a complex image or read the contents of a file from disk. Creating one of these objects is time consuming, and you might want to delay its creation until it’s really needed, or until system resources are free. At the same time, you want at least one reserved object to interact with other objects in your program. In this case, you can create a lightweight proxy object for that object. The proxy object can have some functions of its own, such as responding to data query messages, but its main function is to represent an object and forward the message to the represented object when the time comes. When the proxy object’s forwardInvocation: method receives a message that needs to be forwarded to the represented object, the proxy object guarantees that the represented object already exists or creates it otherwise. All messages sent to the represented object pass through the proxy object, which is the same as the represented object to the program.

Forwarding and inheritance

Although forwarding mimics inheritance, the NSObject class never confuses the two. For example, in NSObject, the methods respondsToSelector and isKindOfClass only appear in the inheritance chain, not in the message forward chain, and if we want to make those methods transparent, which means that the message forward method also returns YES, we need to override those methods, Return YES.

forwarding

6. Type coding

To cooperate with the runtime system, the compiler encodes both the method return type and the parameter type into a string associated with the method selection.

Encoding fetch: use @encode(type name)

Common encoding format:

7. Property declaration

When the compiler encounters a Property declaration, it generates some descriptive metadata associated with the class, classification, or protocol of the Property. These descriptive metadata are the attributes of the Property. You can access metadata through functions that exist in classes or protocols, such as getting the type encoding of an Attribute through @encode, returning attributes as an array of C strings, and so on. To distinguish between a property and an attribute, I call a property an attribute and an attribute a feature

I’ve only written here a detailed example of how to recognize and use the attribute features you can read directly in the text.

7.1 Attribute types and related functions

The Property type defines an opaque handle to the objC_property structure that describes the Property.

Structure:

typedef struct objc_property *Property;
Copy the code

API:

Concrete implementation:

7.2 Attribute type encoding

The property_getAttributes function returns the name of the Property, the @encode encoding, and other attributes.

Concrete implementation:

@property (nonatomic ,assign ,readonly) int age;
@property (nonatomic ,copy,readwrite) NSString *name;
Copy the code

Results:

Description:

  1. It starts with the letter T, followed by @encode and a comma
  2. If the property has readonly modifier, the string contains R and comma
  3. If the attribute has a copy or retain modifier, the string contains C or &, respectively, followed by a comma.
  4. If the property defines custom getter and setter methods, the string has G or S followed by the corresponding method name and comma
  5. The string ends with V followed by the name of the property.