Message transmission mechanism in oc-Appendix: Expansion of the performselector Method

Source: Internet
Author: User

There are some methods for passing functions in various languages: function pointers can be used in C language, and function references, imitation functions, and Lambda are available in C ++, and Selector is also available in objective-C) and block.
However, most of the APIs in ios sdk are selector, so this article focuses on selector.

Objective-C is different from other object-oriented languages I have come across. It emphasizes message transmission rather than method calls. Therefore, you can pass any message to an object without the need to process the message during compilation.
Obviously, since the method address cannot be determined during the compilation period, you need to locate the method during the runtime. The objective-C Runtime calls the method through the "id objc_msgsend (ID thereceiver, Sel theselector,...)" function. Thereceiver is the call object, theselector is the message name, and ellipsis is an indefinite parameter in C language.

The message name here is Sel type, which is defined as struct objc_selector *. However, the document does not reveal what the objc_selector is, but provides the @ selector command to generate:

Sel selector = @ selector (Message );

@ Selector is calculated during the compilation period, so it is not a function call. Further tests show that Mac OS X 10.6 and IoS are both C-style strings (char *):

Nslog (@ "% s", (char *) selector );

You will find that the message name is "message.

Below is a test class:


@ Interface Test: nsobject
@ End

@ Implementation Test

-(Nsstring *) inttostring :( nsinteger) number {
Return [nsstring stringwithformat: @ "% d", number];
}

-(Nsstring *) doubletostring :( double *) number {
Return [nsstring stringwithformat: @ "% F", * number];
}

-(Nsstring *) pointtostring :( cgpoint) point {
Return [nsstring stringwithformat: @ "{% F, % f}", point. X, point. Y];
}

-(Nsstring *) intstostring :( nsinteger) number1 second :( nsinteger) number2 third :( nsinteger) Number3 {
Return [nsstring stringwithformat: @ "% d, % d, % d", number1, number2, Number3];
}

-(Nsstring *) doublestostring :( double) number1 second :( double) number2 third :( double) Number3 {
Return [nsstring stringwithformat: @ "% F, % F, % F", number1, number2, Number3];
}

-(Nsstring *) combinestring :( nsstring *) string1 withsecond: string2 withthird: string3 {
Return [nsstring stringwithformat: @ "% @, % @, % @", string1, string2, string3];
}

@ End

Then test objc_msgsend:


# Import <objc/message. h>
// Introduce this header file if you want to use objc_msgsend.

Test * test = [[test alloc] init];
Cgpoint point ={ 123,456 };
Nslog (@ "% @", objc_msgsend (test, @ selector (pointtostring :), point ));
[Test release];

The result is "{123.000000, 456.000000 }". In addition, like previously guessed, the following call is also possible:

Nslog (@ "% @", objc_msgsend (test, (SEL) "pointtostring:", point ));

As you can see, you should find that this implementation method can only determine the message Name and number of parameters, and the parameter type and return type are erased. Therefore, the compiler can only warn you of incorrect parameter types during compilation, but cannot prevent you from passing parameters with incorrect types.


Next, let's take a look at some methods provided by the nsobject protocol for passing messages:

  • -(ID) performselector :( SEL) aselector
  • -(ID) performselector :( SEL) aselector withobject :( ID) anobject
  • -(ID) specify mselector :( SEL) aselector withobject :( ID) anobject withobject :( ID) anotherobject

Didn't you think it was speechless? Why must the parameter be an object? Why does one support up to two parameters?

Fortunately, the selector itself does not care about the parameter type, so it is okay to pass a thing that is not an object:

Nslog (@ "% @", [test your mselector: @ selector (inttostring :) withobject :( ID) 123]);

However, double and struct cannot be passed in this way, because they occupy different bytes and pointers. If you have to use javasmselector, you can only change the parameter type to pointer:

-(Nsstring *) doubletostring :( double *) number {
Return [nsstring stringwithformat: @ "% F", * number];
}

Double number = 123.456;
Nslog (@ "% @", [test your mselector: @ selector (doubletostring :) withobject :( ID) (& number)]);

The parameter type is done, but it takes time to support multiple parameters. Ideally, we can implement these two methods:

@ Interface nsobject (extend)

-(ID) specify mselector :( SEL) aselector withobjects :( nsarray *) objects;
-(ID) specified mselector :( SEL) aselector withparameters :( void *) firstparameter ,...;

@ End

First look at the former, nsarray requires that all elements must be objects and cannot be nil, so the applicability is still limited. But don't underestimate it, because you will find that you cannot use objc_msgsend to implement it, because you cannot predict the number of parameters when writing code.
Now it's nsinvocation:

@ Implementation nsobject (extend)

-(ID) performselector :( SEL) aselector withobjects :( nsarray *) Objects {
Nsmethodsignature * Signature = [self methodsignatureforselector: aselector];
Nsinvocation * invocation = [nsinvocation invocationwithmethodsignature: Signature];
[Invocation settarget: Self];
[Invocation setselector: aselector];

Nsuinteger I = 1;
For (ID object in objects ){
[Invocation setargument: & object atindex: ++ I];
}
[Invocation invoke];

If ([Signature methodreturnlength]) {
ID data;
[Invocation getreturnvalue: & Data];
Return data;
}
Return nil;
}

@ End

Nslog (@ "% @", [test your mselector: @ selector (combinestring: withsecond: withthird :) withobjects: [nsarray arraywithobjects: @ "1", @ "2 ", @ "3 ",
Nil]);

Note the following three points:

  1. Because there are two implicit parameters, self (call object) and _ cmd (selector), the index should start from 2 when setting parameters.
  2. Because the parameter is an object, you must pass the pointer, that is, & object.
  3. If methodreturnlength is 0, it indicates that the return type is void. Therefore, you do not need to obtain the return value. When the returned value is an object, we do not need to create a buffer. However, if it is a C-style string, array, and other types, you need to manually malloc and release the memory.


Then we can implement the following 2nd methods:

-(ID) specified mselector :( SEL) aselector withparameters :( void *) firstparameter ,...{
Nsmethodsignature * Signature = [self methodsignatureforselector: aselector];
Nsuinteger length = [Signature numberofarguments];
Nsinvocation * invocation = [nsinvocation invocationwithmethodsignature: Signature];
[Invocation settarget: Self];
[Invocation setselector: aselector];

[Invocation setargument: & firstparameter atindex: 2];
Va_list arg_ptr;
Va_start (arg_ptr, firstparameter );
For (nsuinteger I = 3; I <length; ++ I ){
Void * parameter = va_arg (arg_ptr, void *);
[Invocation setargument: & Parameter atindex: I];
}
Va_end (arg_ptr );

[Invocation invoke];

If ([Signature methodreturnlength]) {
ID data;
[Invocation getreturnvalue: & Data];
Return data;
}
Return nil;
}

Nslog (@ "% @", [test your mselector: @ selector (combinestring: withsecond: withthird :) withparameters: @ "1", @ "2", @ "3"]);

Nsinteger number1 = 1, number2 = 2, Number3 = 3;
Nslog (@ "% @", [test metrics mselector: @ selector (intstostring: Second: Third :) withparameters: number1, number2, Number3]);

It is similar to the previous implementation, but because the parameter length is unknown, [Signature numberofarguments] is used. Of course, you can also convert sel into a string (nsstringfromselector () can be used), and then find the number.
Va_start, va_arg, and va_end are used to process variable parameters. If you are familiar with the C language, you can understand them.

However, because you do not know the parameter type, you can only set it to void *. This program also reported a warning that void * is incompatible with the nsinteger type. If you change the parameter to double, an error is reported. Unfortunately, I do not know how to determine whether a void * Pointer Points to the C data type or to an objective-C object, so it is best to encapsulate it into an objective-C object. If you only need to be compatible with the C type, you can remove the setargument parameter and directly pass the pointer in:

Nsinteger number1 = 1, number2 = 2, Number3 = 3;
Nslog (@ "% @", [test your mselector: @ selector (intstostring: Second: Third :) withparameters: & number1, & number2, & Number3]);

Double number4 = 1.0, number5 = 2.0, number6 = 3.0;
Nslog (@ "% @", [test your mselector: @ selector (doublestostring: Second: Third :) withparameters: & number4, & number5, & number6]);
[Test release];

Multiple parameters can also be supported in this method for the callback mselector: withobject: afterdelay: method added to the nsobject class.

Next we will talk about the omitted _ cmd, which can also be used for recursive calls. The following uses the Fibonacci series as an example:

-(Nsinteger) maid :( nsinteger) n {
If (n> 2 ){
Return [self MAID: n-1] + [self MAID: N-2];
}
Return n> 0? 1: 0;
}

With _ cmd, the implementation becomes like this:

Return (nsinteger) [self generated mselector: _ cmd withobject :( ID) (n-1)] + (nsinteger) [self generated mselector: _ cmd withobject :( ID) (N-2)];

Or directly use objc_msgsend:

Return (nsinteger) objc_msgsend (self, _ cmd, n-1) + (nsinteger) objc_msgsend (self, _ cmd, N-2 );

However, it is very difficult to use objc_msgsend every time. Is there a way to call the method directly? The answer is yes, So imp is needed. IMP is defined as "ID (* IMP) (ID, Sel ,...)", That is, a function pointer pointing to a method.

Nsobject provides methodforselector: method to obtain imp, so you only need to make a slight modification:

-(Nsinteger) maid :( nsinteger) n {
Static imp func;
If (! Func ){
Func = [self methodforselector: _ cmd];
}

If (n> 2 ){
Return (nsinteger) func (self, _ cmd, n-1) + (nsinteger) func (self, _ cmd, N-2 );
}
Return n> 0? 1: 0;
}

Now the running time is 1/4 less than just now.

By the way, we will show the powerful dynamics of objective-C and add a sum: And: Method to the test class:

Nsinteger sum (ID self, Sel _ cmd, nsinteger number1, nsinteger number2 ){
Return number1 + number2;
}

Class_addmethod ([test class], @ selector (sum: And :), (IMP) sum, "I @: II ");
Nslog (@ "% d", [test sum: 1 and: 2]);

The last parameter of class_addmethod is the return value and parameter type of the function. For details, see type encodings.

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.