iOS message forwarding

Source: Internet
Author: User
Tags deprecated

message Forwarding is a powerful technique that can greatly increase the expressiveness of objective-c. What is message forwarding? In short, it allows unknown messages to be trapped and responded to. In other words, whenever an unknown message is sent, it?? will be sent to your code in a good package, and you can do whatever you want at this point.

Why is it called "forwarding"? When an object does not have any action in response to a message, it "forwards" the message. The reason for this is that this technique is intended to "forward" the object so that other objects can process the message for them.


1. Classes, objects, methods

Before we start using the messaging mechanism, we can contract our terminology. For example, many people don't know what "method" and "message" are, but it's important to understand how a messaging system works at a low level.

    • method : A section of actual code related to a class, and give a specific name. Cases:- (int)meaning { return 42; }
    • message : The name and a set of parameters that are sent to the object. Example: Send to a 0x12345678 object meaning and have no parameters.
    • Selector : A special way of representing a message or method name, expressed as a type sel. Selectors are inherently opaque strings, they are managed, so you can compare them by using simple pointers equal to improve speed. (implementations may be different, but this is basically what they look like outside.) ) For example: @selector(meaning) .
    • message Sending : The process of receiving information and finding and executing appropriate methods.
The method of 1.1 oc and the function of C

The Objective-c method is eventually generated as a C function with some additional parameters. The methods in Objective-c are hidden two parameters by default: self and _cmd . You may know that self it is passed as an implicit argument, and it eventually becomes a definite argument. The little-known implicit parameter _cmd , which holds the selector for the message being sent, is the second such implicit argument. In summary, self point to the object itself, pointing to the _cmd method itself. Give two examples to illustrate:

    • Example 1: - (NSString *)name This method actually has two parameters: self and _cmd .

    • Example 2: - (void)setValue:(int)val This method actually has three parameters: self , _cmd and val .

The syntax of the OBJECTIVE-C function call you write at compile time will be translated into a C function call objc_msgSend() . For example, the following two lines of code are equivalent:

    • Oc
[array insertObject:foo atIndex:5];
    • C
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
Class 1.2, object, C expression of method

In Objective-c, classes, objects, and methods are a struct of C, and from the Objc/runtime.h and objc/objc.h header files, we can find their definitions:

    • Objc_class
struct Objc_class {class _nonnull Isa Objc_isa_availability; #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 * ' */  
    • Objc_object
/// Represents an instance of a class.struct objc_object {    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;};
    • Objc_method
struct objc_method {    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;    char * _Nullable method_types                            OBJC2_UNAVAILABLE;    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;}                                                            OBJC2_UNAVAILABLE;
    • Objc_method_list
struct objc_method_list {    struct objc_method_list * _Nullable obsolete             OBJC2_UNAVAILABLE;    int method_count                                         OBJC2_UNAVAILABLE;#ifdef __LP64__    int space                                                OBJC2_UNAVAILABLE;#endif    /* variable length structure */    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;}    
1.3 Message Sent

What's going on in the C language function? How did the compiler find this method? The main steps for sending messages are as follows:

    1. First check that this selector is not to be ignored. such as Mac OS X development, with garbage collection will not ignore retain,release these functions.
    2. Detecting whether the target of this selector is NIL,OC allows us to execute any method on a nil object without crash, because the runtime is ignored.
    3. If the above two steps are passed, start looking for the implementation of this class imp, first look up from the cache, if found to run the corresponding function to execute the corresponding code.
    4. If the cache does not find a corresponding method in the list of methods to find the class.
    5. If the method list in the class cannot be found in the method list of the parent class, the NSObject class is always found.
    6. If you don't find it, you'll start to go into dynamic method resolution, which will say
2. Dynamic Features: Method parsing and message forwarding

Without the implementation of a method, the program hangs out and throws an exception at run time unrecognized selector sent to … . But before the exception is thrown, the Objective-c runtime will give you three chance to save the program:

    • Method resolution
    • Fast forwarding
    • Normal forwarding
2.1 Dynamic Methods Analysis: Method Resolution

First, the OBJECTIVE-C runtime invokes + (BOOL)resolveInstanceMethod: or gives + (BOOL)resolveClassMethod: you the opportunity to provide a function implementation. If you add a function and return YES, the runtime restarts the message sending process. or foo, for example, you can do this:

void fooMethod(id obj, SEL _cmd)  {    NSLog(@"Doing foo");}+ (BOOL)resolveInstanceMethod:(SEL)aSEL{    if(aSEL == @selector(foo:)){        class_addMethod([self class], aSEL, (IMP)fooMethod, "[email protected]:");        return YES;    }    return [super resolveInstanceMethod];}

Here the first character v represents the function return type void , the second character represents the type of self @ id , and the third character : represents the type of _cmd SEL . These symbols can be found in the developer documentation in Xcode for type encodings to see what the symbols correspond to, and more detailed official document portals are here, no longer listed here.


2.2 Fast forwarding: fast rorwarding

Before the message forwarding mechanism was executed, the runtime system allowed us to replace the recipient of the message with another object. Through the - (id)forwardingTargetForSelector:(SEL)aSelector method. If this method returns nil or self, the message forwarding mechanism () is entered, - (void)forwardInvocation:(NSInvocation *)invocation otherwise the message will be resent to the returned object.

- (id)forwardingTargetForSelector:(SEL)aSelector {    if(aSelector == @selector(foo:)){        return [[BackupClass alloc] init];    }    return [super forwardingTargetForSelector:aSelector];}
2.3 Message Forwarding: Normal Forwarding
- (void)forwardInvocation:(NSInvocation *)invocation {    SEL sel = invocation.selector;    if([alternateObject respondsToSelector:sel]) {        [invocation invokeWithTarget:alternateObject];    } else {        [self doesNotRecognizeSelector:sel];    }}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];    if (!methodSignature) {        methodSignature = [NSMethodSignature signatureWithObjCTypes:"[email protected]:*"];    }    return methodSignature;}

forwardInvocation:The method is a distribution center that does not recognize the message, forwards the unrecognized message to a different message object, or forwards it to the same object, or translates the message into another message, or simply "eats" some of the messages, so there is no response and no error. For example, in order to avoid direct flash, we can give the user a hint in this method when the message cannot be processed, and it is also a friendly user experience.

Where are the parameters invocation coming from? forwardInvocation:before the message is sent, the runtime sends a methodSignatureForSelector: message to the object and takes the returned method signature to generate the Nsinvocation object. So rewrite forwardInvocation: the method at the same time methodSignatureForSelector: , or you will throw an exception. When an object is unable to respond to a message because there is no corresponding method implementation, forwardInvocation: the runtime notifies the object with a message. Each object inherits the forwardInvocation: method, and we can forward the message to other objects.

3. Application Combat: Message forwarding 3.1 Specific crash prevention treatment

Here's a piece of code that causes a crash because there is no way to implement it:

    • Test2viewcontroller
- (void)viewDidLoad {    [super viewDidLoad];    setBackgroundColor:[UIColor whiteColor]];    self.title = @"Test2ViewController";        //实例化一个button,未实现其方法    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];    button.frame = CGRectMake(50, 100, 200, 100);    button.backgroundColor = [UIColor blueColor];    [button setTitle:@"消息转发" forState:UIControlStateNormal]; [button addTarget:self action:@selector(doSomething) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button];}

To solve this problem, you can specifically create a classification that addresses this problem:

    • Nsobject+crashloghandle
#import "NSObject+CrashLogHandle.h"@implementation NSObject (CrashLogHandle)- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {    //方法签名    return [NSMethodSignature signatureWithObjCTypes:"[email protected]:@"];}- (void)forwardInvocation:(NSInvocation *)anInvocation {    NSLog(@"NSObject+CrashLogHandle---在类:%@中 未实现该方法:%@",NSStringFromClass([anInvocation.target class]),NSStringFromSelector(anInvocation.selector));}@end

Because the method of the parent class is replicated in the category, the following warning appears:


The solution is in the resource file in Xcode's build phases, after the corresponding file-W, ignoring all warnings.


3.2 Apple System API iterations caused by the crash processing 3.2.1 compatible system API iterations for legacy scenarios

With an annual update iteration of iOS systems and hardware, some of the more powerful or more readable APIs will likely discard and replace the original API. At the same time, we also need to be in the existing app version of the old API compatibility, of course, there are many ways to version compatibility, the following author will enumerate several commonly used:

    • Judging by the ability to respond
if ([object respondsToSelector: @selector(selectorName)]) {    //using new API} else {    //using deprecated API}
    • Based on whether the current version of the SDK has the required classes to determine
if (NSClassFromString(@"ClassName")) {        //using new API}else {    //using deprecated API}
    • Judging by the operating system version
#define isOperatingSystemAtLeastVersion(majorVersion, minorVersion, patchVersion)[[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: (NSOperatingSystemVersion) {    majorVersion,    minorVersion,    patchVersion}]if (isOperatingSystemAtLeastVersion(11, 0, 0)) {    //using new API} else {    //using deprecated API}
New scenarios for 3.2.2 Compatible system API iterations

* * Requirement: * * Assume that there is now a class that was written in the past, as shown below, with one row because the system API is outdated and causes the crash code:

    • Test3viewcontroller.m
- (void)viewDidLoad {    [super viewDidLoad];    setBackgroundColor:[UIColor whiteColor]];    self.title = @"Test3ViewController";        UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, 375, 600) style:UITableViewStylePlain];    tableView.delegate = self;    tableView.dataSource = self;    tableView.backgroundColor = [UIColor orangeColor];        // May Crash Line    tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;        [tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"UITableViewCell"];    [self.view addSubview:tableView];}

One of the lines warns, Xcode also gives the recommended solution, if you click Fix it will automatically add code to check the system version as shown in:


* * Scenario 1:** manually adding version judgment logic

Previous adaptation processing can be judged based on the operating system version

if (isOperatingSystemAtLeastVersion(11, 0, 0)) {    scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;} else {    viewController.automaticallyAdjustsScrollViewInsets = NO;}

* * Scenario 2:** message forwarding

The IOS11 Base SDK takes the latest API directly and with the runtime's message forwarding mechanism to implement a single line of code in different versions of the operating system to take different message invocation mode

    • Uiscrollview+forwarding.m
#import "Uiscrollview+forwarding.h"#import "Nsobject+adapterviewcontroller.h" @implementation Uiscrollview (Forwarding)-(Nsmethodsignature *) Methodsignatureforselector: (SEL) Aselector {//1 nsmethodsignature *signature = nil;if (Aselector = = @selector (Setcontentinsetadjustmentbehavior:)) {signature = [Uiviewcontroller instancemethodsignatureforselector: @selector (Setautomaticallyadjustsscrollviewinsets:)]; }else {signature = [super Methodsignatureforselector:aselector];} return signature;} -(void) Forwardinvocation: (nsinvocation *) aninvocation {//2 BOOL automaticallyadjustsscrollviewinsets = NO; Uiviewcontroller *topmostviewcontroller = [self cm_topmostviewcontroller]; Nsinvocation *viewcontrollerinvocation = [Nsinvocation invocationWithMethodSignature:anInvocation.methodSignature] ; 3 [viewcontrollerinvocation Settarget:topmostviewcontroller]; [Viewcontrollerinvocation Setselector: @selector (setautomaticallyadjustsscrollviewinsets:)]; [Viewcontrollerinvocation setargument:&automaticallyadjustsscrollviewinsets atIndex:2];//4 [ Viewcontrollerinvocation Invokewithtarget:topmostviewcontroller]; 5} @end     
    • Nsobject+adapterviewcontroller.m
#import "Nsobject+adapterviewcontroller.h" @implementation NSObject (Adapterviewcontroller)-(Uiviewcontroller *) cm_    Topmostviewcontroller {Uiviewcontroller *RESULTVC;     RESULTVC = [self cm_topviewcontroller:[[uiapplication sharedapplication].keywindow rootviewcontroller];While (resultvc.presentedviewcontroller) {RESULTVC = [self cm_topViewController:resultVC.presentedViewControll    ER]; } return RESULTVC;} -(Uiviewcontroller *) Cm_topviewcontroller: (Uiviewcontroller *) VC { if ([VC Iskindofclass:[uinavigationcontroller Class]] { return [self cm_topviewcontroller:[(Uinavigationcontroller *) VC Topviewcontroller]];} Else if ([VC Iskindofclass:[uitabbarcontroller Class]]) { return [self cm_topviewcontroller:[( Uitabbarcontroller *) VC Selectedviewcontroller]]; } else { return VC;}} @end        

When we call the new API in IOS10, because there is no specific API implementation, we forward its original message to the top uiviewcontroller of the current stack to invoke the low version API.

About [self cm_topmostViewController]; , the results obtained after execution can be viewed as follows:


Overall process for Scenario 2:

    1. Returns a corresponding method signature for the message that is being forwarded (the signature is later used to encode the forwarded message object (Nsinvocation *) aninvocation)

    2. Start message Forwarding ((Nsinvocation *) Aninvocation encapsulates the invocation of the original message, including the method name, method parameters, and so on)

    3. Since the API for forwarding calls differs from the API of the original call, here we create a new Nsinvocation object for the message invocation viewcontrollerinvocation and configure the corresponding target and selector

    4. Configure the required parameters: Because each method is actually default to two parameters: Self and _cmd, so we want to configure the other parameters from the third parameter to start the configuration

    5. Message forwarding

3.2.3 Verification vs. new scheme

Pay attention to the test, select the IOS10 system Simulator to verify (not download simulators), after installation, as follows:


    • Do not annotate and import the Uiscrollview+forwarding class

    • Comment out the function code of uiscrollview+forwarding

Will collapse as shown:


4. Summary 4.1 Simulation multiple inheritance

Interview Digging Pit : does OC support multiple inheritance? Well, if you say that you don't support multiple inheritance, do you have a way of simulating multiple inheritance features?

Forwarding and inheritance are similar, can be used for OC programming to add some more inherited effects, an object to forward the message, as if he put another object in the law or "inherit" the same. Message forwarding makes up for the nature of OBJC not supporting multiple inheritance, and also avoids the fact that multiple inheritance causes a single class to become bloated and complex.

Although forwarding can implement inheritance, nsobject must be very rigorous on the surface, such as respondsToSelector: and such isKindOfClass: methods will only consider the inheritance system, do not consider the forwarding chain.

4.2 Message Mechanism Summary

Sending a message to an object in Objective-c follows several steps:

    1. Try to find the message in the dispatch table of the object class. If found, jump to the corresponding function imp to execute the implementation code;

    2. If not found, Runtime will send +resolveInstanceMethod: or +resolveClassMethod: try to resolve the message;

    3. If the Resolve method returns No,runtime, send -forwardingTargetForSelector: allows you to forward the message to another object;

    4. If no new target object is returned, Runtime sends -methodSignatureForSelector: and -forwardInvocation: messages. You can send a -invokeWithTarget: message to forward the message manually or send a -doesNotRecognizeSelector: throw exception.

iOS message forwarding

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.