Article 11th: understanding the role of objc_msgsend
Calling methods on objects is a frequently used function in OC. In OC terminology, this is called "pass a message ). A message contains a "name" or "selector", which can receive parameters and may return values.
Since OC is a superset of C, it is best to understand the function call method of C language. The C language uses static binding, which means that the function to be called at runtime can be determined during compilation. The following code is used as an example:
#import <stdio.h>void printHello(){ printf("Hello world\n");}void printGoodBye(){ printf("Goodbye world\n");}void doTheThing(int type){ if(type == 0){ printfHello(); }else{ printGoodbye();} return 0; }
When compiling the code, the compiler knows that the program has the printhello and printgoodbye functions, so it directly generates instructions to call these functions. The function address is actually hardcoded in the instruction. What if I write the code below?
#import <stdio.h>void printHello(){ printf("Hello world\n");}void printGoodBye(){ printf("Goodbye world\n");}void doTheThing(int type){ void (*fun)(); if(type == 0){ fun = printfHello(); }else{ fun = printGoodbye();} fun(); return 0; }
In this case, you need to use the dynamic binding, because the function to be used knows the runtime can be determined. The commands generated by the compiler in this environment are different from those in the previous example. In the first example, the IF and else statements contain function call commands. In the second example, there is only one function call instruction, but the address to be called cannot be hard-coded in the instruction, but must be read at runtime.
In oC, if a message is sent to an object, dynamic binding mechanism is used to determine the method to be called. At the underlying layer, all methods are common C language functions. However, after an object receives a message, the method to which it should be dropped is completely determined during the runtime, and can even be changed when the program is running, these features make OC a real dynamic language.
You can write a message to an object as follows:
id returnValue = [someObject messageName:parameter];
In this example, someobject is called "receiver" and messagename is called "Select Sub ". The sub-parameters are collectively referred to as "messages ". After seeing the message, the compiler converts it to a standard C language function call. The called function is the core function of the message passing mechanism, called objc_msgsend. Its prototype is as follows:
void objc_msgSend(id self ,SEL cmd,...);
This is a function with edges of the number of parameters. After conversion, the function just now is converted to the following:
id returnValue = objc_msgSend(someObject,@selector(messageName),parameter);
The objc_msgsend function calls an appropriate method based on the receiver and the sub-type selected. To complete this operation, you need to search for its "list of methods" (list of methods) in the class to which the receiver belongs. If you can find a method that matches the selected sub-Name, jump to its implementation code. If it cannot be found, continue to search up along the inheritance system, and wait until the method with the same name is found before the jump .. If no matching method is found, execute "message forwarding"
Therefore, it seems that many steps are required to call a method. Fortunately, objc_msgsend caches the matching results in the "quick ing table. In fact, message dispatch is not the bottleneck of the application.
The preceding content only describes the call process of some messages. Other "edge cases" must be handled by another function in the OC runtime environment:
● The structure of the message to be sent to objc_msgsend_stret should be returned.
● The objc_msgsend_fpret message returns a floating point number.
● Objc_msgsend_super sends messages to the superclass.
As mentioned earlier, once a function such as objc_msgsend finds the method implementation to be called, it will "Jump to the past ". This is because every method of the OC object can be regarded as a simple C function. The prototype is as follows:
<Return_type> class_selector (ID self, Sel _ cmd ,...)
There is a table in every class, And the Pointer Points to this function, and the sub-name is the "key" used in the Table query ". Objc_msgsend and other functions use this table to find the method to be executed and jump to its implementation. Note that the prototype is similar to objc_msgsend. This is not a coincidence. It uses the tail-call optimization technology to make the skip to method implementation easier.
When writing an OC program, developers should understand its underlying working principles without worrying about these issues. How the code is executed, and you can understand why objc_msgsend always appears in the stack information during debugging.
[Highlights of this section]
● A Message consists of a receiver, a selection subscriber, and parameters. Sending a message to an object is equivalent to "calling a method" on this object"
● All messages sent to an object must be processed by the dynamic message dispatch system. The system will find the corresponding method and execute its code
Article 3: Understanding the message forwarding mechanism Article 3 describes the object message transmission mechanism and emphasizes its importance. Another important issue in this section is what happens when an object receives messages that cannot be interpreted. To make the class understand a message, we must implement the method. If not, the message forwarding mechanism is enabled. If you see such an error on the console, message forwarding is Enabled:
-[__NSCFNumber lowercaseString]:unrecognized selector sent to instance 0x87*** Terminating app due to uncaught exception 'NSInvalidArgumentException',reason:'-[__NSCFNumber lowercaseString]:unrecognized selector sent to instance 0x87'
The preceding exception message is thrown by the "doesnotrecognizedselector:" method of nsobject. The next exception indicates that the Message Receiver _ nscfnumber cannot understand the lowercasestring selector. In this benefit, the message forwarding process ends with an application crash. developers can set hooks during the forwarding process to execute the reservation logic without the utility crashing. Message Forwarding is divided into two phases. In the first stage, ask the receiver for the class to see if the method can be dynamically added. The "unknown Selection Sub" has been processed. This is called "dynamic method resolution ". The second stage involves the "Complete Message forwarding mechanism" dynamic method. After an object receives a message that cannot be interpreted, it first calls the following methods of its class: + (bool) resolveinstancemethod :( SEL) the selector parameter of this method is an unknown selector. Its return value is of the boolean type, indicating whether this class adds an instance method to process this selector. Before continuing to execute the forwarding mechanism, this class has the opportunity to add a method to process this selection. If the unimplemented method is not an instance method but a class method, the system will call another method during the runtime. The premise for "resolveclassmethod" to use this method is that the implementation code of the related method has been written, just wait for the running time to dynamically insert it into the class. This scheme is usually used to implement the @ dynamic attribute. The following code shows how to use "resolveinstancemethod:" To implement the @ dynamic attribute:
id autoDictionaryGetter(id self,self _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,"[email protected]:@"); }else{ class_addMethod(self.selector,(IMP)autoDictionaryGetter,"[email protected]:@"); } return YES; }return [super resolveInstanceMethod:selector];}
First, convert the selection into a string, and then check whether it represents the setting method. If the prefix is set, it indicates "setting method", otherwise it is "getting method ". The current recipient of the backup recipient has a second chance to process the unknown sub-selection. In this step, the runtime system will ask whether the message can be transferred to another receiver for registration.
-(id) forwardingTargetForSelector:(SEL)selector
The method parameter indicates an unknown Selection Sub-item. If the current receiver can find the standby object, it will be returned. If not, the complete nil message forwarding will be returned.
If the forwarding algorithm has come to this step, the only thing you can do is to start the complete message forwarding mechanism. First, create an nsinvacaton object to seal all the details related to the unprocessed message. This object includes the Selection Sub-, target, and parameters. When an nsinvocation object is triggered, the "message dispatching system" will initiate the request and assign the message to the target object.
This step will call the following method to forward messages:
-(void) forwardInvocation:(NSInvocation*)invocation
This method can be implemented easily: you only need to change the call target so that the message can be called on the new target. However, this implementation method is equivalent to the method implemented by the "backup receiver" solution, so few people use this simple implementation method. A useful implementation method is to change the message content in some way before sending a message, such as appending another parameter or changing the sub-selection.
When this method is implemented, if you find that a call operation should not be processed by this class, you need to call a method of the same name as the super class. In this way, every class in the inheritance system has the opportunity to process the call request until nsobject. If the nsobject class method is finally called, the method will then call "doesnotrecognizeselector:" to throw an exception. The next exception indicates that the selection of sub-objects fails to be processed.
Message forwarding process
Describes the steps for message forwarding:
The recipient has the opportunity to process messages at each step. The later the step, the higher the message processing cost.
Article 13th: the "method allocation technology" is used to test the "black box method" Article 11th, which explains: After an OC object receives a message, the method to be called must be resolved at runtime. You may ask: Can the method corresponding to the given sub-name be changed at runtime? That's right. If you can make good use of this feature, you can play a huge advantage, because we do not need the source code or inherit the subclass to override the method to change the function of this class. In this way, the new function will take effect in all instances of this class, rather than only the subclass instances that overwrite the related methods. Method swizzling ). The method list of the class maps the sub-names to the corresponding method implementation, so that the "dynamic message distribution system" can be called by Zhaodong. These methods are expressed in the form of function pointers. These pointers are called imp, and their prototype is as follows:
id(*IMP)(id,SEL,..)
The nsstring class can be used to select Sub-items such as lowercasestring, uppercasestring, and capitalizedstring. The selected words in this ing table are mapped to different imp. For example
Several methods provided by the OC runtime system can be used to operate this table. A developer can add a selection sub to it, change the method implementation corresponding to a Selection Sub-, and exchange the pointer mapped to the Selection Sub. After several operations, the method table of the class will be named like this
In the new ing table, a selection sub named newselector is added, and the implementation of capitalizedstring is also changed, while the implementation of lowercasestring and uppercasestring is interchangeable. No subclass is required for the above modifications. If you modify the layout of the "method table", It will be reflected on all nsstring instances in the program. Now you can see the power of this feature.
The following describes how to swap two methods for implementation. You can use the following functions to implement the exchange method:
void method_exchangeImplementations(Method m1,Method m2)
The two parameters of this function represent the implementation of the two methods to be exchanged, and the method implementation can be obtained through the following functions:
Method class_getInstanceMethod(Class aClass,SEL aSelector)
The procedure is as follows:
Method originalMethod = class_getInstanceMethod([NSStringclass],@selector(lowercaseString));Method swappedMethod = class_getInstanceMethod([NSStringclass],@selector(uppercaseString));method_exchangeImplementations(originalMethod,swappedMethod);
In practical application, you can add the log technical functions for those black box methods. For example, what should I do if I want to add logs to the lowercasestring interface of nsstring?
@interface NSString (EOCMyAddtions)-(NSString*)eoc_myLowercaseString;@end@implementation NSString(EOCMyAdditions)-(NSString*) eoc_myLowercaseString{ NSString *lowercase = [self eoc_mylowercaseString]; NSLog(@"%@ =>%@",self,lowercase); return lowercase;}@end
This Code seems to be stuck in an endless loop of recursive calls, but remember that this method is intended to be exchanged with the lowercasestring method. Therefore, during runtime, The eoc_mylowercasestring selection child actually corresponds to the original lowercasestring method implementation. Finally, use the following code to call the soul:
Method originalMethod = class_getInstanceMethod([NSStringclass],@selector(lowercaseString));Method swappedMethod = class_getInstanceMethod([NSStringclass],@selector(roc_myLowercaseString));method_exchangeImplementations(originalMethod,swappedMethod);
After the appeal code is executed, log is output if the lowercasestring method is called on the nsstring instance.
[Highlights of this section]
● During running, you can add or replace the method implementation corresponding to the sub-selection in the class.
● Use another implementation method to replace the reason. This process is called "method allocation". Developers often use this technology to add new features to the original implementation.
● Generally, modification methods must be implemented at runtime only when the program is debugged. This method should not be abused.
Article 14th: Understanding the intention of "Class Object"
OC is actually a dynamic language. Article 3 explains how the system finds and calls the implementation code of a method in the runtime, and article 3 describes the principle of message forwarding: if the class cannot immediately respond to a selection subitem, the message forwarding process is started. However, what is the receiver of a message? Is it the object itself? How does the system know the type of an object during runtime? The object type is not bound to the compiler, but to be searched at runtime. There is also a special type ID, which can refer to any OC object type.
Let's talk about some basic knowledge to see what the nature of the OC object is. Each OC object is a pointer to a piece of memory data. Therefore, when declaring a variable, the type must be followed by an asterisk (*)
NSString *pointerVariable = @"Some thing";
Anyone who has compiled C language programs knows what it means. This variable "points to" (point to) nsstring instance. This applies to all OC objects. If you want to declare the OC object on the stack, the compiler reports an error:
Sting stackVariable = @"Some thing";//error: interface type cannot be statically allocated
For the generic ID type, since it is already a pointer, we can write as follows:
id genericTypeString = @"Some thing";
The data structure used to describe the OC object is defined in the header file of the Runtime Library. The ID type is also defined here:
typedef struct objc_object{ Class isa;} *id;
It can be seen that the first member of every object struct is a class variable. This variable defines the class to which the object belongs, usually called the is a pointer. For example, if all objects in the preceding example are a nsstring, the "is a" Pointer Points to nsstring. The Class Object page is defined in the header file of the Runtime Library:
typedef struct objc_class *class;struct objc_class{ Class isa; Class super_class; const char* name; long version; long instance_size; struct objc_ivar_list *ivars; struct objc_method_list **methodLists; struct objc_cache *cache; struct objc_protocol_list *protocols};
This structure map stores the metadata of the class. For example, the class instance implements several methods and information such as how many instance variables are available. The first variable of this struct is also the ISA pointer, which indicates that the class itself is also an OC object. There is also a variable named super_class In the struct, which defines the superclass of this class. The type of the tired object (that is, the type pointed to by the ISA pointer) is another class called metaclass, which is used to indicate the metadata of the SHU class object. "Class methods" are defined here, because these methods can be understood as tired object instance methods. Each class has only one "Class Object", and each "Class Object" has only one "Meta class" related to it ".
Assume that a subclass named someclass is inherited from nsobject, as shown in figure
The super_class pointer establishes an inheritance relationship, while the ISA pointer describes the class to which the instance belongs. You can use this layout diagram to perform "type information query ". We can check whether the object can respond to a Selection Sub-, whether it complies with a certain protocol, and whether the object is located in the part of the "class inheritance system.
Query type information in the class inheritance system
You can use the type information query method to view the class inheritance system. "Ismemberofclass:" can be used to determine whether an object is an instance of a specific class. "Iskindofclass:" is used to determine whether an object is a certain type or its derived instance.
NSMutableDictionary *dict = [NSMutableDictionary new];[dict isMemberOfClass:[NSDictionary class]];//no[dict isMemberOfClass:[NSMutableDictionary class]];//yes[dict isKindOfClass:[NSDictionary class]];//yes[dict isKindOfClass:[NSArray class]];//no
Because OC uses the "dynamic type system" (dynamic typing), it is very useful to query the type information of the class to which the object belongs. When obtaining an object from collecting, it is usually the ID type, so you can use the query type information method.
[Highlights of this section]
● Every instance has a pointer to a class object to indicate its type. These class objects constitute the inheritance system of the class.
● If the object type cannot be determined during compilation, you should use the type query method to find out
● Try to use the type information query method to determine the object type, instead of comparing the class object directly, because some objects may implement the message forwarding function.