高階函數實現的原理:函數類型其實是生成了一個對象 。
inline翻譯成中文的意思就是內聯,在kotlin里面inline被用來修飾函數,表明當前函數在編譯時是以內嵌的形式進行編譯的,從而減少了一層函數調用棧:
inline fun fun1() {Log.i("tag", "1")
}//調用
fun mainFun() {fun1()
}//實際編譯的代碼
fun mainFun() {Log.i("tag", "1")
}
這樣寫的一點好處就是調用棧會明顯變淺:
但是這個好處對應用程序的優化影響非常小,幾乎可以忽略不計。甚至可能會由于多處調用代碼重復編譯導致編譯字節碼膨脹從而造成包體積變大的問題,這就得不償失。
我們都知道kotlin允許函數可以作為另一個函數的入參對象進行調用,在實際調用處入參的函數體會被創建為一個對象:
fun fun1(doSomething: () -> Unit) {Log.i("tag", "1")doSomething()
}//調用
fun mainFun() {fun1 {Log.i("tag", "2")}
}//實際編譯的代碼
fun mainFun() {val f = object: Function0<Unit> {override fun invoke() {Log.i("tag", "2")}}fun1(f)
}
一般情況下上圖所示的調用邏輯并沒有什么問題,創建一個小對象并不會對性能造成什么影響,但是如果我們將fun1放入for循環中呢:
fun mainFun() {for (i in 0..1000) {fun1 {Log.i("tag", "2")}}
}
在短時間內就會在mainFun函數中循環創建1000個f對象,這樣應用進程的內存會瞬間飆升并造成某些性能上的嚴重問題,這就類似于為什么不讓在onDraw函數中創建局部對象。
而作為fun1函數的創建者,我們無法知道調用者會在什么場景以及時機去調用fun1函數,一旦出現上述重復創建大量函數對象的場景那么就會有嚴重的性能問題,而且這也是kotlin高階函數的一個性能隱患。所以,基于這個問題kotlin提供了inline關鍵字來解決。
inline關鍵字可以將函數體內部的代碼內聯到調用處,甚至還可以將函數體內部的內部的代碼也內聯過去,而這個內部的內部的指的就是函數內部的函數類型的參數:
inline fun fun1(doSomething: () -> Unit) {Log.i("tag", "1")doSomething()
}//調用
fun mainFun() {for (i in 0..1000) {fun1 {Log.i("tag", "2")}}
}//實際編譯的代碼
fun mainFun() {for (i in 0..1000) {Log.i("tag", "1")Log.i("tag", "2")}
}
這樣就避免了函數類型的參數所造成的臨時函數對象的創建,我們就可以在界面高頻刷新、大量循環的場景下放心調用fun1函數了。
擴展:
Kotlin的 noinline和crossinline關鍵字-CSDN博客
總結
總的來說,inline關鍵字讓函數以內聯的方式進行編譯避免創建函數對象來處理kotlin高階函數的天然性能缺陷。同時,之前的文章中提到的kotlin的泛型實化,也是利用了inline關鍵字可以內嵌函數代碼的特性而衍生出來的全新功能。