noinline,crossinline详解

1.noinline

话不多说,直接看个例子:

//函数是内联的,但是参数action不是内联的
inline fun lambdaFun(noinline action: (() -> Unit)){
    Log.i("test", "testLambdaFun: 调用前")
    action()
    Log.i("test", "testLambdaFun: 调用后")
}

然后我们调用:

//调用
fun testHello(){
    lambdaFun {
        Log.i("test", "testLambdaFun: 调用中")
    }
}

直接反编译:

//反编译代码
public final class TestFun {
   public final void testHello() {
       //创建了匿名内部类实例
      Function0 action$iv = (Function0)null.INSTANCE;
      int $i$f$lambdaFun = false;
      Log.i("test", "testLambdaFun: 调用前");
      action$iv.invoke();
      Log.i("test", "testLambdaFun: 调用后");
   }
}

这里我们清晰的看出lambdaFun内部的代码进行了复制铺平到调用地方,但是对于action却没有复制铺平,原因也非常简单,因为它是noinline修饰的,那它有什么用呢 我们接着分析。

2.使用noinline的原因

我们前面说了inline会让函数类型参数进行复制和铺平,那这个参数也就不再是函数类型参数了,毕竟它变成了几行代码,所以这就是局限性,当我们还要把它作为函数类型参数或者返回时,就要使用noinline了。

还是直接看例子:

//定义高阶函数,非内联
fun lambdaFun1(action: () -> Unit){
    action()
}

然后我们定义一个内联函数:

//内联函数
inline fun lambdaFun(action: (() -> Unit)){
    Log.i("test", "testLambdaFun: 调用前")
    action()
    //调用高阶函数
    lambdaFun1(action)
    Log.i("test", "testLambdaFun: 调用后")
}

这种使用很常见,但是我们会发现这段代码无法编译:


image.png

原因也非常简单,想把action传递给lambdaFun1,那这个action必须函数类型参数,但是在被inline修饰的函数,其参数也会被铺平,也就不会再是函数类型了,所以这里的action要使用noinline来修饰:

//使用noinline修饰参数
inline fun lambdaFun(noinline action: (() -> Unit)){
    Log.i("test", "testLambdaFun: 调用前")
    action()
    lambdaFun1(action)
    Log.i("test", "testLambdaFun: 调用后")
}

高阶函数除了把函数类型参数当做其他高阶函数的参数外,还可以作为返回值,同样这时也不能把函数类型参数给铺平为lambda表达式,我们看例子:


image.png

这里我想返回这个action,遗憾的是,和前面一样,这个action会被铺平,将不再是函数类型参数了,所以必须把action用noinline修饰:

//因为返回值要使用action,要保留action为函数类型
inline fun lambdaFun(noinline action: (() -> Unit)):() -> Unit{
    Log.i("test", "调用前")
    action()
    Log.i("test", "调用后")
    return action
}

3.return难题

inline和noinline说完,我们来说一个return问题,为啥return会有不一样呢 我们来慢慢细说。

3.1非内联高阶函数

先定义一个高阶函数:

fun lambdaFun(action: (() -> Unit)){
    Log.i("test", "testLambdaFun: 调用前")
    action()
    Log.i("test", "testLambdaFun: 调用后")
}

然后进行调用,在lambda中想使用return语句:

image.png

这里直接使用return语句无法使用,提醒使用return@lambdaFun,这当然可以理解,那我只想使用return呢 有没有办法,当然可以,把lambdaFun定义为内联函数。

3.2 内联函数

改成内联函数:

inline fun lambdaFun(action: (() -> Unit)){
    Log.i("test", "testLambdaFun: 调用前")
    action()
    Log.i("test", "testLambdaFun: 调用后")

}

然后再进行调用return语句:

image.png

这里居然不报错了,由于内联函数会把函数体和lambda给复制铺平到调用地方,所以这里的return必然是返回调用函数了,而不是lambdaFun函数。

3.3 return和inline之间的约定

所以这里为了解决lambda表达式中的return语句问题,Kotlin直接规定,在非inline函数中,return无法使用(必须return@xxx指明返回的函数),只有在inline函数中可以使用return语句,这样就不会有异议,根据inline的特性,这个return必然是返回调用者函数。

3.4 crossinline

既然我们了解了return问题,以及它return和inline之间的约定,也就是为了不产生歧义,那我们继续看问题,引出crossinline这个修饰词了。

直接看例子,再定义一个高阶函数:

//新的函数,参数是函数类型
fun lambdaFun1(postAction: () -> Unit){
    postAction()
}

然后在前面定义的函数中,调用这个函数:

//调用lambdaFun1,并且使用lambda
inline fun lambdaFun(action: (() -> Unit)){
    Log.i("test", "testLambdaFun: 调用前")
    lambdaFun1 {
        action()
    }
    Log.i("test", "testLambdaFun: 调用后")
}

千万注意,这里和前面说noinline的例子是不一样的,这里是使用lambda表达式,在表达式内调用action()也就是其invoke函数,而不是把action这个函数类型参数进行传递,和下面是不一样的:

inline fun lambdaFun(action: (() -> Unit)){
    Log.i("test", "函数调用前")
    //这里期望把action还是当做类型参数,而不是铺平
    lambdaFun1(action)
    Log.i("test", "函数调用后")
}

上面要区分开,现在我们是讨论第一种情况,这个代码能编译吗,我们看一下:


image.png

注意这里的报错信息:无法内联action参数,原因是它可能包含return语句。
哦?为什么呢?原因非常简单,这里的lambdaFun是内联函数,但是lambdaFun1是非内联的,根据前面return和内联函数之间的约定,只有内联函数可以使用return,否则不行,那这就尴尬了,不可能我每个内联函数都必须调用内联函数吧,这显然不符合逻辑。

3.5 突破限制,加强inline功能

因为上面的代码,永远不知道action会不会包含return语句,所以inline受限严重,这里只有突破这个限制才可以,这里采用的方式是把action加个crossinline修饰符。

然后会发现:


image.png

这里居然不报错了

3.6 crossinline的作用

crossinline的作用仅仅是当有被这个修饰的参数会告诉IDE来检查你写的代码中有没有包含return,假如有的话会编译不过,就是这么简单暴力。

4.总结

最后还是总结一下子,这3个关键字涉及的东西还真不少。

  • 为了解决每次调用高阶函数时给它传递lambda时都会创建匿名内部类的问题引入了inline。
  • inline修饰的函数,不仅会把函数体的内容进行复制铺平,还会把函数类型参数的内容复制铺平。
  • 当在内联函数中想把某个函数类型的参数进行传递或者返回,这时就不能把这个参数给铺平,所以使用noinline修饰这个参数。
  • 在lambda中使用return语句会造成歧义,不知道返回哪一层,所以Kotlin直接定义非inline函数时不能使用return,当是inline函数时,return是返回调用着那一层函数。
  • 在内联函数中,调用非内联的高阶函数,把lambda传递到非内联函数中,将和上面造成冲突,无法判断这个参数的lambda中有没有return,为了解决这个问题,引入了crossinline。
  • crossinline的目的是告诉IDE,这个参数的lambda不能写return语句。

你可能感兴趣的:(noinline,crossinline详解)