Kotlin Inline Function Examples

İbrahim Ethem Şen
5 min readApr 15, 2023

--

Kotlin programlama dilinde Inline,Reified,Noinline ve Crossinline keyword’lerin fonksiyonlardaki kullanımını anlatmaya çalışacağım.

Programlama yaparken yazdıklarımızı çalıştırırken genel olarak iki başlıkta değerlendirebiliriz. Harici olarak işlemler vardır bunları düşünmeden

Compile Time(Derleme Zamanı) ve Run Time(Çalışma Zamanı) olarak düşünelim.

  • Compile Time : Programlama dili kodunun derleyici ile makinenin anlayıp çalıştırabileceği bir koda dönüştürülmesidir.
  • Run Time : Programın çalıştırıldığı zamandır. Android uygulamasını kullandığımız an gibi düşünebiliriz.

Kodlarımız çalıştırılırken bir sıra takip edilerek çalıştırılır. Keyword’ler dikkate alınarak çeşitli işlemler yapılır. Fonksiyon olan bir noktada fonksiyonun bulunduğu noktaya giderek işlemi gerçekleştirir ve geriye kaldığı yerden devam eder.

İşlemi tamamladıktan sonra aynı yerden devam etmesinden işlemleri Back Stack de tuttuğunu anlıyoruz. Kotlin de Inline keyword ’ü bu durumu değiştiriyor.

Inline : Compile Time’da fonksiyonun body’sini çağırıldığı yere gömer. Örnekler üzerinden inceleyelim.

override fun onCreate(savedInstanceState: Bundle?) {
...
repeatInlineFunction(2){
println("Android Kotlin")
}
}

inline fun repeatInlineFunction(times: Int, action: () -> Unit) {
for (index in 0 until times) {
action()
}
}

repeatInlineFunction’u Inline keyword ile birlikte kullandığımız için kodumuz derlenirken fonksiyonun body’si çağırıldığı yere gömüldüğünden aslında şu şekilde gözükecektir.

override fun onCreate(savedInstanceState: Bundle?) {
...
for (index in 0 until 2) {
println("Android Kotlin")
}
}

Daha iyi anlayabilmek için Android Studio’da ki Show Kotlin ByteCode’u kullanalım.Burada Kotlin kodunun byte kod halinin Java da nasıl gözüktüğünü görebiliyoruz. Şunu unutmayalım birebir olarak Kotlin kodunu Java olarak vermiyor.

Show Kotlin ByteCode

Dikkat edersen burada çağırılan bir fonksiyon yok. Doğrudan for ile işlemler yapılmış. Parametrede verdiğimiz 2 değeri de gözükmekte.

Inline fonksiyonlarının bu şekilde kullanılmasının Avantajı ne olur ?

  • Reified : Fonksiyonlarımızın Generics olarak kullanılabilmesini sağlar.
  • Inline Fonksiyonlar Hızlıdır : Background da TrackBack stack ve bir dizi işlemin yapılması yerine fonksiyonun çağırıldığı yere Body’nin gömülmesi Inline fonksiyonları daha hızlı hale getirir.
  • Return : Inline fonksiyonlar çağırım yapmak yerine Body’i koda gömdüğü için return kullanabilir.

Reified

Inline fonksiyonların Body’leri Compile Time’da çağırılan yere gömülmekte. Ama Generics Type’ler Compile Time’da bilinmezler. Bir örnek üzerinden inceleyelim.

inline fun <T> reifiedFunction() {
//Error Cannot use 'T' as reified type parameter. Use a class instead.
print(T::class.simpleName)
}

Reified kullandığımızda Compile Time’da kodun çalıştırıldığı yere giderek gelmesi beklenen Type’ı belirliyor ve onu yerleştiriyor.

override fun onCreate(savedInstanceState: Bundle?) {
...
reifiedFunction<Int>()
reifiedFunction<String>()
}
inline fun <reified T> reifiedFunction() {
print(T::class.simpleName)
}

Kotlin Byte Code ile Java koduna bakalım.

Reified Show Kotlin ByteCode

Java koduna baktığımızda çağırdığımız yerde kodun belirlediğimiz String-Int type’leri ile oluşturulduğunu görüyoruz.

Projelerde Inline-Reified’i kullandığımız bir örnek olarak CI/CD — Remote Config’de Gson extension’u için kullanımı

inline fun <reified T> FirebaseRemoteConfig.fetchToLiveData(
key: String,
gson: gson
): LiveData<T> {
val liveData = MutableLiveData<T>()
this.fetchAndActivate().addOnCompleteListener {
if (it.isSuccessful) {
val keyString = this.getString(key)
val type = object : TypeToken<T>() {}.type
val jsonModel = gson.fromJson<T>(keyString, type)
liveData.postValue(jsonModel)
}
}
return liveData
}

Return :

override fun onCreate(savedInstanceState: Bundle?) {
...
returnFunction()
}
private fun returnFunction() : Int{
returnNoinline(2){
if (it == 1){
return //'return' is not allowed here
}
}
returnInline(2){
if (it == 1){
return 3
}
}
return 0
}
fun returnNoinline(times: Int, action: (Int) -> Unit) {
for (index in 0 until times) {
action(index)
}
}
inline fun returnInline(times: Int, action: (Int) -> Unit) {
for (index in 0 until times) {
action(index)
}
}

Inline fonksiyonumuz içinde bir değer return edebilirken. normal fonksiyonumuz için böyle bir işlem yapamayız. Bu işlemi Body’nin gömülmesi sayesinde yapıyoruz.

NoInline-CrossInline

  • Noinline : İşlemin Inline olmasını istemediğimiz zaman kullanıyoruz.
  • Crossinline : Inline olmasını istediğimiz ama farklı bir scope da işlemi yapmak istediğimizde kullanıyoruz.

Bir örnek üzerinden inceleyelim. Bir EditText’e Focus-Unfocused durumu için bir extension yazalım

inline fun EditText.customizedOnFocusChangeListener(
functionFocus:() -> Unit,
functionUnfocused: () -> Unit
){
functionFocus() // OK
functionUnfocused() // OK
}

EditText Scope’u içinde çağırdığımızda High-Order’larda herhangi bir sorun yaşamıyoruz. Şimdi OnFocusChangeListener ekleyelim.

inline fun EditText.customizedOnFocusChangeListener(
functionFocus:() -> Unit,
functionUnfocused: () -> Unit
){
this.onFocusChangeListener = View.OnFocusChangeListener { _, focus ->
if (focus){
//Error Can't inline 'functionFocus' here: it may contain non-local returns.
functionFocus()
}else{
//Error Can't inline 'functionUnfocused' here: it may contain non-local returns.
functionUnfocused()
}
}
}

High-Order’ı artık farklı bir Scope da çağırıyoruz. CrossInline ve NoInline kullanımını uyguladığımız noktalar burası.

override fun onCreate(savedInstanceState: Bundle?) {
edittext.customizedOnFocusChangeListener({
println("crossInline")
}){
println("noInline")
}
}

inline fun EditText.customizedOnFocusChangeListener(
crossinline functionFocus:() -> Unit,
noinline functionUnfocused: () -> Unit
){
this.onFocusChangeListener = View.OnFocusChangeListener { _, focus ->
if (focus){
functionFocus()
}else{
functionUnfocused()
}
}
}

Artık kodlarımızı Inline ama farklı Scope’lar içerisinde kullanabiliyoruz. Veya Inline olmasını engelleyebiliyoruz. functionFocus parametresi Inline ama farklı Scope olacakken functionUnfocused parametresi hiç bir zaman Inline olmayacak. Bunu daha iyi anlamak için bir de Java koduna bakalım.

NoInline-CrossInline

CrossInline’da Body koda gömülürken NoInline’da fonksiyon çağırılıyor.

Inline Fonksiyonların Avantajlarına kısaca baktık. Şimdi kullanmamamız gereken yere bakalım.

Inline fonksiyonlar Body’si kod içine gömüldüğü için kodun hızlı şekilde büyümesine sebep olur. Tekrardan ilk örneğimize dönelim. ve birden fazla Inline fonksiyon kullanalım.

override fun onCreate(savedInstanceState: Bundle?) {
...
repeatInlineFunction(2){
println("Android Kotlin")
}
repeatInlineFunction(2){
println("Android Kotlin")
}
repeatInlineFunction(2){
println("Android Kotlin")
}
repeatInlineFunction(2){
println("Android Kotlin")
}
}

inline fun repeatInlineFunction(times: Int, action: () -> Unit) {
for (index in 0 until times) {
action()
}
}

Compile Time’da baktığımızda kod aslında şu şekilde gözüküyor.

override fun onCreate(savedInstanceState: Bundle?) {
...
for (index in 0 until 2) {
println("Android Kotlin")
}
for (index in 0 until 2) {
println("Android Kotlin")
}
for (index in 0 until 2) {
println("Android Kotlin")
}
for (index in 0 until 2) {
println("Android Kotlin")
}
}

Inline fonksiyonların birbiri içinde çağırıldığı bir case düşünürsek kod hızlıca büyüyecektir. Bu durumun önüne geçmek için kullandığımız yerlerde iç içe kullanımına dikkat etmeliyiz.

  • Inline fonksiyonları genellikle çok sık kullanılan küçük olan işlemlerde kullanırız. Örneğin yazı içinde kullandığımız for yapısı Kotlin de kullandığımız repeat’a ait.
  • Reified kullanmamız gereken durumlarda Inline kullanıyoruz.

Dikkat edersek Collections da kullandığımız filter,map gibi işlemler. Scope fonksiyonlar apply,with,run… Inline olarak tanımlanmıştır.

Görüş öneri veya eleştirileriniz var ise linkedIn veya Twitter’dan bildirirseniz sevinirim.

--

--