preface

Earlier we said inline, not to mention that it’s nice to use,

# Kotlin inline, noinline, Crossinline complete analysis 1

It also has two brothers, Noinline and Crossinline, which we will continue to analyze.

The body of the

Noinline is the inverse of inline, which means not inline. Of course not, inline is used to modify functions, and noinline can only modify function parameters, that is, functions are inline, but function type parameters are not inline.

Now that we know this definition, let’s see what we can do with it.

noinline

Without further ado, let’s look at an example:

// The function is inline, but the action argument is not
inline fun lambdaFun(noinline action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    action()
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Then we call:

/ / call
fun testHello(){
    lambdaFun {
        Log.i("zyh"."TestLambdaFun: In call")}}Copy the code

Direct decompilation:

// Decompile the code
public final class TestFun {
   public final void testHello() {
       // Create an anonymous inner class instance
      Function0 action$iv = (Function0)null.INSTANCE;
      int $i$f$lambdaFun = false;
      Log.i("zyh"."TestLambdaFun: Before call");
      action$iv.invoke();
      Log.i("zyh"."TestLambdaFun: after call"); }}Copy the code

Here we can clearly see that the code inside lambdaFun is copied and flattened to the call location, but the action is not. The reason is very simple, because it is noinline.

Reasons for using noinline

We said that inline causes the function type argument to be copied and smoothed, so the argument is no longer a function type argument. After all, it becomes several lines of code, so this is the limitation. When we want to use it as a function type argument or return, we use noinline.

Let’s go straight to the example:

// Define higher-order functions, non-inline
fun lambdaFun1(action: () -> Unit){
    action()
}
Copy the code

Then we define an inline function:

// inline functions
inline fun lambdaFun(action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    action()
    // Call higher-order functions
    lambdaFun1(action)
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

This is a common use, but we will find that this code does not compile:

To pass an action to a lambdaFun1, the action must take a function type argument. However, in a function that is inline, the argument will be flattened and no longer be a function type. Therefore, the action will use noinline:

// Use the noinline modifier parameter
inline fun lambdaFun(noinline action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    action()
    lambdaFun1(action)
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

A higher-order function can take its type arguments as arguments to other higher-order functions, and can also return values. Similarly, the function type arguments cannot be smoothed out as lambda expressions.

Here I want to return this action. Unfortunately, as before, this action will be flattened out and will no longer be a function type parameter, so we must use the noinline modifier:

// Because the return value will use action, keep action as function type
inline fun lambdaFun(noinline action: (() -> Unit)):() -> Unit{
    Log.i("zyh"."TestLambdaFun: Before call")
    action()
    Log.i("zyh"."TestLambdaFun: after call")
    return action
}
Copy the code

As you can imagine, the function will be inlined and the action will compile to an anonymous inner class:

public final void testHello() {
   Function0 action$iv = (Function0)null.INSTANCE;
   int $i$f$lambdaFun = false;
   Log.i("zyh"."TestLambdaFun: Before call");
   // Action will not be inline
   action$iv.invoke();
   Log.i("zyh"."TestLambdaFun: after call");
}
Copy the code

Return problem

“Inline” and “noinline”, let’s talk about a return. Why does a return differ?

Non-inline higher-order functions

Let’s define a higher-order function:

fun lambdaFun(action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    action()
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Then make the call, in lambda you want to use the return statement:

I can’t use the return statement directly, so I’m reminded to use return@lambdaFun, which is understandable, but I just want to use return. Is there any way, of course, to define lambdaFun as an inline function?

Inline function

Change to inline function:

inline fun lambdaFun(action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    action()
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Then call the return statement:

Return testHello (lambdaFun); return testHello (lambdaFun); return testHello (lambdaFun);

The convention between return and inline

In order to solve the problem of return statements in lambda expressions, Kotlin directly states that return cannot be used in non-inline functions (the return function must be specified at return@xxx), and that return statements can only be used in inline functions, so that there is no objection. By the nature of inline, this return must return the caller function.

crossinline

Now that we know about the return problem and its convention between return and inline, to avoid ambiguity, let’s move on to the crossinline modifier.

Let’s go straight to the example and define a higher-order function:

// A new function whose argument is a function type
fun lambdaFun1(postAction: () -> Unit){
    postAction()
}
Copy the code

Then, in the function defined earlier, call this function:

// Call lambdaFun1 and use lambda
inline fun lambdaFun(action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    lambdaFun1 {
        action()
    }
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Use a lambda expression to invoke action() instead of passing action as a function type parameter. This is not the same as the following example:

inline fun lambdaFun(action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    // Action is expected to be a type parameter, not a smooth one
    lambdaFun1(action)
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Now let’s talk about the first case, does this code compile? Let’s see:

Note the error message: The action argument cannot be inlined because it may contain a return statement.

Oh? Why is that? The reason is very simple. LambdaFun here is an inline function, but lambdaFun1 is non-inline. According to the previous convention between return and inline functions, only an inline function can use return, otherwise it cannot. This is clearly illogical.

Break the limit and enhance inline functionality

Because the above code never knows if an action will contain a return statement, the inline is severely limited. The only way to break this limit is to add a Crossinline modifier to the action.

Then you’ll find:

It’s inlining, it’s not inlining, it’s going to drive the compiler crazy, it’s not, it’s just solving the problem.

The role of crossinline

Crossinline is only useful if there are arguments modified by this and it tells the IDE to check if your code contains a return, and if so it will compile but that’s it.

Let’s go straight to the example and compare. First of all, there’s no crossinline:

inline fun lambdaFun(action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    lambdaFun1 {
        action()
    }
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

When called:

We can write the return statement normally, when we add crossinlie to the argument:

inline fun lambdaFun(crossinline action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    lambdaFun1 {
        action()
    }
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Call again:

Don’t write directly, ha ha, really simple and violent.

The subtotal

In fact, we have already talked about why Crossinline appeared and the problems it solved, and the most direct way to solve the problem by violence, but let’s summarize the situation here.

That is, when you want to put the execution of an argument in an inline function into another function, the other function is separated from the outermost function that calls the inline function, and you have to be careful that the argument is not crossinline qualified.

Of course we don’t have to worry about this, the IDE will automatically alert you when your code logic has problems.

conclusion

Finally sum up all of a sudden, these 3 keywords involved in the thing is really many.

  • Inline was introduced to solve the problem of creating anonymous inner classes every time a higher-order function is called and a lambda is passed to it.

  • A function that is inline will not only copy the contents of the function body, but also the contents of the function type parameters.

  • When a function type parameter is passed or returned in an inline function, it cannot be smoothed out, so use noinline to modify the parameter.

  • Using a return statement in a lambda would create ambiguity as to which layer to return, so Kotlin cannot use return when defining a non-inline function directly. When a function is inline, return returns the layer to which the function is called.

  • In order to solve this problem, crossinline is introduced. In order to solve this problem, crossinline is used to call a non-inline higher-order function and pass the lambda to a non-inline function.

  • The purpose of crossinline is to tell the IDE that the lambda of this argument cannot write a return statement.