[52 Effective ways to write high-quality iOS code] (iii) messages and run-time
Reference book: "Effective Objective-c 2.0", "English" Matt Galloway
Sneak Peek
11. Understanding the role of objc_msgsend
12. Understanding the message forwarding mechanism
13. Debug "black Box Method" with "Method leveling technology"
14. Understanding the purpose of "class object"
Directory
- 52 effective ways to write high-quality iOS code three messages and a run time
- Sneak Peek
- Directory
- 11th understand the role of Objc_msgsend
- 12th Understanding the message forwarding mechanism
- 13th the method of adjusting the black box by means of the technique of blending
- 14th Understanding the purpose of the class object
11th: Understanding the role of objc_msgsend
Calling a method on an object is a feature that is often used in objective-c, which is called message passing in objective-c terms. The message has a name or selector, can accept parameters, and may have a return value.
Because Objective-c is a superset of C, we first use the C language as an example to illustrate the message passing mechanism in OBJECTIVE-C:
#import <Stdio.H>voidPrinthello () {print ("hello,world!\n");}voidPrintgoodbye () {print ("goodbye,world!\n");}//Static binding methodvoidStaticbindingmethod () {if(type == 0) {Printhello (); }Else{Printgoodbye (); }return 0;}//Dynamic Binding modevoidDynamicbindingmethod () {void(*FNC) ();if(type == 0) {FNC=Printhello; }Else{FNC=Printgoodbye; } FNC ();return 0;}
With static bindings, the compile time determines which functions should be called at run time, in this case, the IF and ELSE statements have function call directives. With dynamic binding, the called function is not determined until the run time, in this case, there is only one function call instruction, and the function address of the call is not deterministic and needs to be read at run time.
In Objective-c, if a message is passed to an object, a dynamic binding mechanism is used to determine the method that needs to be called. Objective-c is a real dynamic language. Sending a message to an object can be written like this:
returnValue = [someObject messageName:parameter];
In this example, Someobject is called a receiver, MessageName is called a selector, and the selector and parameters are combined to be called messages. When the compiler sees this message, it converts it to a standard C-language function call:
...);// 编译器将上面例子中的消息转换为如下函数id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
There are also special cases that are dealt with by other functions like objc_msgsend:
objc_msgSend_stret // 待发送的消息返回的是结构体objc_msgSend_fpret // 待发送的消息返回的是浮点数objc_msgSendSuper // 向超类发送消息,也有等效于上述两个函数的函数用于处理发送给超类的相应消息
If the last operation of a function is to invoke another function, then the tail call optimization technique can be applied. The compiler generates the script required to flip to another function and does not push a new stack frame into the call stack. A tail-call optimization can only be performed if the last operation of a function simply calls another function and does not return its value to the other. This optimization is critical to avoid stack overflows that occur prematurely.
12th: Understanding the message forwarding mechanism
Due to the dynamic nature of the objective-c, it is not possible to send an error message to the class at compile time, because the method can continue to be added to the class during the run time, so the compilation period is not known to be implemented in the class in the end. When an object receives a message that cannot be interpreted, it initiates a message-forwarding mechanism.
Message forwarding is divided into two stages, the first stage is called dynamic method resolution, that is, to consult the recipient, the class belongs to see if it can dynamically add methods to handle the current unknown selector. In the second stage, the runtime system will request the receiver to forward the message to other objects for processing, two small steps, the first step for fast forwarding, if fast forwarding can not be processed, then the second small step standard forwarding (full forwarding).
// 动态方法解析调用的实例方法和类方法+ (BOOL)resolveInstanceMethod:(SEL)sel;+ (BOOL)resolveClassMethod:(SEL)sel;// 快速转发调用的方法- (id)forwardingTargetForSelector:(SEL)aSelector;// 标准转发调用的方法- (void)forwardInvocation:(NSInvocation *)anInvocation;
The recipient has the opportunity to process the message at each step, but the further the step, the greater the cost of processing the message.
The following is an example of a dynamic method resolution that implements a method similar to getter and setter in a dynamic manner:
//eocautodictionary.h#import <Foundation/Foundation.h> @interface eocautodictionary : nsobject @property(nonatomic,Strong)NSString*string;@property(nonatomic,Strong)NSNumber*number;@property(nonatomic,Strong)NSDate*date;@property(nonatomic,Strong)IDOpaqueobject;@end//EOCAUTODICTIONARY.M#import "EOCAutoDictionary.h" #import <objc/runtime.h> @interface eocautodictionary ()@property(nonatomic,Strong)nsmutabledictionary*backingstore;@end @implementation eocautodictionary //attribute is declared as @dynamic, the compiler does not automatically generate instance variables and access methods for it@dynamicstring, number, date, opaqueobject;-(ID) init{if(( Self= [SuperInit]) {_backingstore = [nsmutabledictionaryNEW]; }return Self;}//dynamic method parsing+ (BOOL) Resolveinstancemethod: (SEL) sel{NSString*selectorstring = Nsstringfromselector (SEL);//Select dynamically added method based on whether selector name starts with set if([Selectorstring hasprefix:@"Set"]) {Class_addmethod ( Self, SEL, (IMP) Autodictionarysetter,"[Email protected]:@]); }Else{Class_addmethod ( Self, SEL, (IMP) Autodictionarygetter,"@@:"); }return YES;}//Implement GetterIDAutodictionarygetter (ID Self, SEL _cmd) {eocautodictionary *typedself = (eocautodictionary*) Self;nsmutabledictionary*backingstore = typedself. Backingstore;the method name of the//getter is the name of the property NSString*key = Nsstringfromselector (_cmd);return[Backingstore Objectforkey:key];}//Implement settervoidAutodictionarysetter (ID Self, SEL _cmd,IDValue) {Eocautodictionary *typedself = (eocautodictionary*) Self;nsmutabledictionary*backingstore = typedself. Backingstore;NSString*selectorstring = Nsstringfromselector (_cmd);nsmutablestring*key = [selectorstring copy];//Setter's method name is converted to a property name, you need to remove the end of the colon, set at the beginning, and convert the capital letter from the beginning of the property name to lowercase //If the setter method for the date property is named SetDate:[Key Deletecharactersinrange:nsmakerange (key. Length-1,1)]; [Key Deletecharactersinrange:nsmakerange (0,3)];NSString*lowercasefirstchar = [[Key substringtoindex:1] lowercasestring]; [Key Replacecharactersinrange:nsmakerange (0,1) Withstring:lowercasefirstchar];if(value) {[Backingstore setobject:value forkey:key]; }Else{[Backingstore Removeobjectforkey:key]; }}@end
The implementation of fast forwarding and standard forwarding can be referenced separately:
Quick forward Example
Standard forwarding Example
13th: "Method Deployment Technology" debugging "black Box Method"
The method list of the class maps the name of the selector to the relevant method implementation, enabling the dynamic message dispatch system to find the method that should be called. The OBJECTIVE-C runtime system provides several ways to manipulate the selector's mapping table, to add selectors, to change the method implementation of the selector, or to exchange pointers mapped by two selectors. This is the method leveling.
Here's how to swap the case-conversion method in NSString with method leveling techniques:
#import <Foundation/Foundation.h> #import <objc/runtime.h> intMainintargcConst Char* argv[]) {@autoreleasepool {//Get two methods from a class and swap themMethod Originalmethod = Class_getinstancemethod ([NSStringClass],@selector(lowercasestring)); Method Swappedmethod = Class_getinstancemethod ([NSStringClass],@selector(uppercasestring)); Method_exchangeimplementations (Originalmethod, Swappedmethod);NSString*string = @"This is the StRiNg";NSString*lowercasestring = [string lowercasestring];NSLog(@"lowercasestring =%@", lowercasestring);NSString*uppercasestring = [string uppercasestring];NSLog(@"uppercasestring =%@", uppercasestring); }return 0;}
Operation Result:
2016-07-2514:21:25.058 OCTest[31026:1747037ISSTRING2016-07-2514:21:25.059 OCTest[31026:1747037isstring
You can use this method to add logging to a completely opaque black-box method, which is very helpful for debugging the program. The following is the LowerCaseString method to add logging capabilities:
#import <Foundation/Foundation.h> #import <objc/runtime.h> //Add and implement a new method in the NSString classification @interface nsstring(eocmyadditions)- (NSString*) eoc_mylowercasestring;@end @implementation nsstring (eocmyadditions)- (NSString*) eoc_mylowercasestring{//appears to be trapped in a dead loop, but after the run-time exchange, the LowerCaseString method is actually called NSString*lowercase = [ SelfEoc_mylowercasestring];NSLog(@"%@ =%@", Self, lowercase);returnlowercase;}@endintMainintargcConst Char* argv[]) {@autoreleasepool {//Exchange methodMethod Originalmethod = Class_getinstancemethod ([NSStringClass],@selector(lowercasestring)); Method Swappedmethod = Class_getinstancemethod ([NSStringClass],@selector(eoc_mylowercasestring)); Method_exchangeimplementations (Originalmethod, Swappedmethod);NSString*string = @"This is the StRiNg";///Now call the LowerCaseString method to output the log[String lowercasestring]; }return 0;}
Operation Result:
2016-07-2514:30:29.847 OCTest[31568:1753042iSStRiNgisstring
Note: It is not advisable to misuse the method when it is only necessary to debug the program at run time.
14th: Understanding the purpose of "class object"
Object types are not bound at compile time, but are found at run time. In the run-time View object Type This operation is also called type information query, this powerful and useful feature is built into the NSObject protocol, all objects that inherit from the common root class (NSObject and Nsproxy) obey this protocol.
Ismemberofclass: The ability to determine whether an object is an instance of a particular class, and Iskindofclass: the ability to determine whether an object is an instance of a class or its derived class, for example:
NSMutableDictionary *dict = [NSMutableDictionary new];[dict isMemberOfClass:[NSDictionary// NO[dict isMemberOfClass:[NSMutableDictionary// YES[dict isKindOfClass:[NSDictionary// YES[dict isKindOfClass:[NSArray// NO
The following is a comma-delimited string generated by the type information query based on the objects stored in the array:
-(nsstring*) Commaseparatedstringfromobjects: (nsarray*) array{nsmutablestring *string= [nsmutablestringNew]; for(IDObject inchArray) {if([ObjectIskindofclass:[nsstring class]]) {[stringAppendFormat:@"%@,",Object]; }Else if([ObjectIskindofclass:[nsnumber class]]) {[stringAppendFormat:@ "%d,", [ObjectIntvalue]]; }Else if([ObjectIskindofclass:[nsdata class]]) {NSString *base64encoded =/ * Base64 encodes data * /[stringAppendFormat:@"%@,", base64encoded]; }Else{//Handling Unsupported types} }return string;}
To compare class objects with the equivalent, do not use the "IsEqual:" Method, because the class object is a singleton, within the scope of the application, each class has only one instance, and the other way to accurately determine the type of object is to use "= =":
... */if ([object class] == [EOCSomeClass class]){ // code}
But a better approach is still to use the type information query method, because it correctly handles objects that use the messaging mechanism. If you call the class method on a proxy object, the proxy object itself (the subclass of Nsproxy) is returned, not the class to which the object accepting the proxy belongs. If you switch to Iskindofclass: Type information Query method, then the proxy object will forward this message to the object accepting the proxy, that is, the return value of this message is the same as the result of querying its type directly on the object accepting the proxy.
[52 Effective ways to write high-quality iOS code] (iii) messages and run-time