一文彻底搞懂kotlin inline

小憩第55篇原创文章

Kotlin语言相信大家已经玩的很溜了,但大家有没有注意到它内部源码大量使用了inline,那么Kotlin为什么要使用inline它的作用又是什么呢?

如果你只是注意到了,但从来没有进行深入探究,相信这篇文章能够帮你找到答案。

inline

inline 是作用在函数方法上面的,例如 Kotlinlet 方法

public inline fun  T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

那它的作用是什么呢?

inline 主要是对闭包 block 做优化,为了对比它做的优化,我对应定义一个没有 inline 的方法

public fun  T.ret(block: (T) -> R): R {
    return block(this)
}

然后我同时调用这两个方法

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        let {
            it.a()
        }

        ret {
            it.b()
        }

    }

    fun a() {

    }

    fun b() {

    }

    fun  T.ret(block: (T) -> R): R {
        return block(this)
    }
}

再通过 ASShow Kotlin Bytecode ,来看它们反编译的二进制代码

public final class MainActivity extends AppCompatActivity {
   private HashMap _$_findViewCache;

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(1300009);
      boolean var3 = false;
      boolean var4 = false;
      // inline修饰的let
      MainActivity it = (MainActivity)this;
      int var6 = false;
      it.a();
      // 没有inline修饰的ret
      this.ret(this, (Function1)null.INSTANCE);
   }

   public final void a() {
   }

   public final void b() {
   }

   public final Object ret(Object $this$ret, @NotNull Function1 block) {
      Intrinsics.checkParameterIsNotNull(block, "block");
      return block.invoke($this$ret);
   }

   ...
}

不懂的还是要看源码,程序员的终结武器

在这里我们发现通过 inline 修饰的方法,会通过平坦式的方式直接在后面按执行顺依次调用。

而没有使用 inline 修饰的方法,则会为 block 方法创建一个 Function1 实例。

简单的理解就是未使用 inline 修饰的方式,会对带有函数式参数的方法,创建对于函数的实例,再将这个实例传递到方法参数中。该参数方法最终在原方法的内部被显示调用。

所以 inline 做的优化就是将带有函数参数的方法简化成没函数式参数的直接调用。好处是提高程序的性能。

当然需要注意的是,避免使用 inline 内联大型函数,减少方法中代码的增长。

非局部返回

inline 还有一个好处是,对于 whilefor 等语句,被 inline 修饰的函数支持局部返回

还是上面的例子

while (--i > 0) {
    let {
        return // success
    }
}
 
while (--i > 0) {
    ret {
        return // error: return is not allow here
    }
}

简单的理解就是,使用 inline 修饰的函数,可以直接在循环语句中通过 return 跳出循环体。而非 inline 函数是不支持的,它支持跳出方法体。

原因也很简单,回头再看之前的反编译的二进制代码,因为使用 inline 修饰的方法是平铺式直接按顺序调用,并没有包含在方法体中,所以如果 return 的话就相当于直接在循环体中 return

while(--i > 0) {
 return
 ...
}

而未使用 inline 修饰的方法,是在另外的方法体中进行调用,所以它的 return 只能是返回到方法体。

reified

使用 inline 修饰的函数还有一个好处是可以使用 reified 来修饰函数的泛型,让函数的泛型具体化

inline fun  T.det(block: (T) -> R): R {
    val a = 0
    if (a is T) { // success

    }
    return block(this)
}

fun  T.ret(block: (T) -> R): R {
    val a = 0
    if (a is T) { // error: Cannot check for instance of erased type: T

    }
    return block(this)
}
 
// 或者
inline fun  membersOf() = T::class.members

传统的泛型是会在程序运行的过程中进行擦除操作,而使用 reified 修饰的泛型,通过反编译二进制表现就是将泛型替换成具体的类型,不进行类型擦除。

$i$f$det = false;
int a$iv = 0;
if (Integer.valueOf(a$iv) instanceof MainActivity) {
   this.a();
}
 
MainActivity it = (MainActivity)this;
int var7 = false;
it.b();

noinline

有了 inline 自然也有对立的 noinline

对于多个函数方法参数,可以使用 noinline 来指定某个函数方法参数不使用 inline 的特性

inline fun  T.net(block: (T) -> R, noinline noBlock: () -> Unit): R {
    noBlock()
    return block(this)
}

这样就只有 block 会执行 inline 的优化。

crossinline

还有一种情况,如果使用了 inline 修饰的函数,被使用到了嵌套的内联函数中,直接使用是会报错的,需要为函数参数添加 crossinline 修饰符

inline fun  T.cet(block: (T) -> R, crossinline noBlock: () -> Unit): R {
    Runnable {
        noBlock()
    }
    return block(this)
}

今天有关 kotlininline 部分就分析到这,希望你有所收获。

推荐阅读

数组:面试中的疑难点

Jetpack:DataStore必知的几个优点

Android Startup最新进展