그야말로 별 생각없이 단순작업을 하다 벌어진 일이었습니다.

Java로 작성된 코드들을 일부 Kotlin으로 변환하고 문법을 조금씩 손을 보는 작업을 진행 중이었죠.

컴파일 오류도 없고 빨간 줄 하나 없이 잘 빌드되었고 문제가 없는 줄 알았습니다.

하지만 앱에는 내부적으로 문제가 있었죠. 그 원인은 이런 것이었습니다.

 

Java 클래스 2개가 있었고 일부 동일한 멤버변수들이 가지고 있는 클래스들이었습니다.

이 클래스들을 서로 다른 레이어의 Model로 있으면 레이어 간 데이터 전달시 서로 Mapping되는 역할이 있었죠.

그러다보니 동일한 멤버들을 서로의 멤버에 부어서 변환을 해주는 메서드들이 있었고요.

여기서 apply로 빌더처럼 데이터를 전달하다가 문제가 발생하였습니다.

 

해당코드를 샘플로 다시 작성 해봤습니다.

class Human {
    var sleep: Int = 0
    var eat: Int = 0
    var play: Int = 0
}

class Student {
    var sleep: Int = 0
    var eat: Int = 0
    var study: Int = 0
    
    fun getHuman(): Human {
        return Human().apply {
            sleep = sleep
            eat = eat
        }
    }
}

Human 과 Student 클래스가 있고 Student 클래스를 Human으로 맵핑하면서 동일한 데이터를 전달하는 과정입니다.

아래를 보면 바로 Human 생성 후 return을 하는 것이 보이고 apply로 필요한 데이터를 전달하는 것을 볼 수 있습니다.

하지만 이때 sleep과 eat 변수들을 주의깊게 보질 않았던 것이 문제였습니다.

        return Human().apply {
            sleep = sleep
            eat = eat
        }

위에서 보이는 sleep과 eat는 apply 내에서 this 즉 Human의 멤버들을 뜻하게 됩니다.

그래서 제가 Human에 넘기려던 Student의 데이터들이 제대로 복사가 안되었던 것입니다.

this.sleep = sleep 도 결과는 동일하였습니다.

그래서 아래와 같이 변경하였습니다.

        return Human().also {
            it.sleep = sleep
            it.eat = eat
        }

apply를 also로 변경하고 Human은 it으로 명시적으로 지정을 해주었습니다.

Kotlin의 Scope 함수를 잘 이해하고 조금만 신경 썼으면 틀릴 부분은 아니었습니다.

다만 IDE에서도 문법적으로는 문제가 없어 컴파일 오류로 잡아주질 않아 자칫 잘못하면 헤매기 쉬웠던 문제였죠.

문법들이 편해지는 만큼 방심하기 쉬워지는 것을 다시 한번 느끼긴 했습니다.

 

// 오랫만에 남기는 포스팅이라 뭐라 끝을 맺을지는 모르겠습니다... 그냥 끝!!!

Serializable 과 Parcelable 이란?

Android 에서 작업시 Activity 간 이동이나 다른 앱 Compnent 이동시 Intent를 사용함.

이때 데이터 객체 등을 전달하기 위해 Class를 직렬화하는 부분들을 추가하여 사용함.

Serializable 이나 Parcelable을 사용하게 됨.

보통 개발자 편의성이나 유지보수는 Serializable이 우위고, 런타임시 성능은 Parcelable이 낫다고들 해 옴.

참고로 Serializable에서 직렬화 프로세스를 직접 구현하면 성능까지 더 우위라는 포스팅도 있음.

[https://medium.com/@limgyumin/parcelable-vs-serializable-%EC%A0%95%EB%A7%90-serializable%EC%9D%80-%EB%8A%90%EB%A6%B4%EA%B9%8C-bc2b9a7ba810](https://medium.com/@limgyumin/parcelable-vs-serializable-정말-serializable은-느릴까-bc2b9a7ba810)

위 링크의 포스팅에 잘 설명이 되어 있고 특히 아래 내용은 공감되는 부분이었음.

제 개인적 생각에는 0.000042 밀리초 빠르게 앱을 실행하는것에 집중하기 보다는 차라리 내가 만드는 이 앱 이 사용자가 원하는 일을 잘 처리하고 만족할 만한 결과를 내도록 집중하는것이 더 가치가 있는 일이 아닐까 싶습니다.

Kotlin에서 Parcelable 적용

 

어쨋든 Parcelable을 사용한다고 치고 Kotlin에서 사용시 팁을 정리하겠음.

 

class User {
    val name: String? = null
    val email: String? = null
}

 

위 클래스를 Parcelable을 구현하면 아래와 같음.

import android.os.Parcel
import android.os.Parcelable

class User() : Parcelable {
    var name: String? = null
    var email: String? = null

    constructor(parcel: Parcel) : this() {
        parcel.run {
            name = readString()
            email = readString()
        }
    }

    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.run {
            writeString(this@User.name)
            writeString(this@User.email)
        }
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<User> {
        override fun createFromParcel(parcel: Parcel): User {
            return User(parcel)
        }

        override fun newArray(size: Int): Array<User?> {
            return arrayOfNulls(size)
        }
    }
}

 

위에서 보다시피 보일러플레이트 코드가 늘어나며, 향후 유지 보수시에도 수정할 부분들이 추가됨.

다행히 Android Extensions 플러그인에서는 실험실 기능으로 아래와 같이 편의기능을 제공함.

1. 실험실 기능 on (app/build.gradle)

android {
    compileSdkVersion 28
    defaultConfig {
		...
    }

    androidExtensions {
        experimental = true
    }

}

 

2. 클래스에 @Parcelize 와 Parcelable 추가 (User.kt)

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
class User(val name: String? = null, val email: String? = null) : Parcelable

원래 소스와 달라진 부분은?

프로퍼티들을 주생성자에 추가함.

단순히 @Parcelize 와 Parcelable 을 추가하면 주생성자에 선언된 프로퍼티들만 직렬화 처리를 함.

그렇다면 아래와 같이 프로퍼티를 주생성자에 넣지 않는다면?

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

@Parcelize
class User() : Parcelable {
    val name: String? = null
    val email: String? = null
}

컴파일 에러는 발생 안함.

다만 런타임시 제대로 프로퍼티들이 직렬화 처리가 안되어 데이터가 없음.

만약 주생성자에서 선언된 것 외의 프로퍼티를 처리해야 한다면 아래와 같이 추가적으로 처리해줘야 함.

import android.os.Parcel
import android.os.Parcelable
import kotlinx.android.parcel.Parceler
import kotlinx.android.parcel.Parcelize

@Parcelize
class User() : Parcelable {
    var name: String? = null
    var email: String? = null

    constructor(parcel: Parcel): this() {
        parcel.run {
            name = readString()
            email = readString()
        }
    }
    private companion object : Parceler<User> {
        override fun User.write(parcel: Parcel, flags: Int) {
            parcel.writeString(name)
            parcel.writeString(email)
        }

        override fun create(parcel: Parcel): User {
            return User(parcel)
        }
    }
}

결국 Android Extensions 플러그인의 기능을 안쓰는 것과 큰 차이는 안나는 듯.

결론

Kotlin에서 Parcelable 사용시 Android Extension 플러그인의 실험실 기능을 사용하면 편해짐.

대신 프로퍼티를 꼭 주생성자에 추가하고 사용하자.

 

  형태 인자 반환
let fun <T, R> T.let(block: (T) -> R): R 호출한 객체 블록 결과값
apply fun T.apply(block: T.() -> Unit): T 호출한 객체 내 메서드 및 속성 객체 자체 반환
run fun <T, R> T.run(block: T.() -> R): R 호출한 객체 내 메서드 및 속성 블록 결과값
with fun <T, R> with(receiver: T, block: T.() -> R): R 호출한 객체 내 메서드 및 속성 블록 결과값

run과 with가 유사하지만 run에서만 Safe Calls를 지원함.

모든 프로그래밍 언어를 공부하기 시작할때 겪는 어려움들이 여러가지 있습니다.


그 중 개인적으로 제일 어려운 것이 프로그래밍 언어를 실제 사용해보기 위한 환경설정이었습니다.


하지만 요즘 핫한 언어들은 다양하고 쉬우면서 초보자들이 따라하기 좋은 튜토리얼 환경을 지원하는 경우들이 많은 것 같습니다.


예를 들어 코틀린이라던지 Kotlin이라던지 등등요. (사실 별로 아는게 없습니다;;)


https://try.kotlinlang.org/




무언가 직관적인 위 URL을 따라가보세요.


가셔서 Kotlin 문법을 사용해보시면 됩니다.


Kotlin Konas online 이라는 것으로 단계별 튜토리얼을 작성하며, 실제 코드가 제대로 작성되는지 체크도 가능하게 지원이 됩니다.


저도 아직 문서나 깨작거리며 보는 중이라 Konas는 진행을 못해보았지만요. 


문서보다 직접 코드를 만지며 배우고자 하시는 분들에게 큰 도움이 될 것 같네요.


일단 저는 문서에 나오는 코드를 깨작거려보고자 위 링크로 들어가 아래와 같이 코드를 넣어봅니다.



fun main(args: Array) {
    val a: Int = 1
    val b = 2
    val c: Int
    c = 3
    println("a = $a, b = $b, c = $c")
}


자 이제 첫 Kotlin 코드도 넣어봤으니, 우측 상단에 Run 버튼을 눌러봅니다.



어라, 무언가 이상하네요. 무언가 알 수 없는 에러가 발생합니다. 


아래 Problems View 를 확인하니 Error 문구가 쓰여있네요. 더블클릭해 해당 위치를 확인합니다.


Test.kt의 TestStart 라는 저도 모르는 아이에서 에러가 발생했네요.


보아하니 원래 Task.kt의 start()라는 함수를 호출하려다 제가 Task.kt의 코드를 함수명채로 바꾸다 보니 에러가 발생한 것이네요.


그러고 보니 좌측 메뉴에 보이는 Kotlin Konas라는 애들은 주제마다 Task.kt와 Test.kt로 구성이 되어 있네요.


아마 주제마다 UnitTest 형식으로 사용자의 결과물을 호출하여 값까지 체크하려고 하는 것 같습니다.


그렇다면 주제와 무관한 코드를 확인하고자 했을때엔 Test.kt도 수정해야되는 불편함이 생길 것 같네요. 이건 아닌것 같습니다.


하여 좌측 메뉴에 Kotlin Konas 위 Examples를 눌러봅니다.


이 아이를 누르니 또 다양한 주제들이 리스트로 나오고 있네요.


그 중에서 Hello, world! 그리고 Simplest version 항목을 선택해 봅니다.


역시 이곳에 확인하고자 하는 코드를 넣어봅니다.


그리고 마찬가지로 우측 상단의 Run 버튼을 눌러봅니다.


아 이제야 아래 Console 창을 통해 제가 원하는 결과를 확인할 수가 있네요.


앞으로 간단한 Kotlin 코드들은 이렇게 확인해 볼 수 있을 것 같네요.


원래 안드로이드에 대한 되새김질을 어느정도한 후에 코틀린을 시작하려 했었는데요.


어떤 계기에 의해 막연하게라도 코틀린을 접해봐야겠다는 생각이 들었습니다.


코틀린 쪽 문서들을 훑어보면서 겉핥기식 스터디를 시작하려 합니다.


당분간 아래 링크를 자주 쓰게 될 것 같네요. ㅋ


https://try.kotlinlang.org



+ Recent posts