Android Base64 Performance Benchmark

도입

안드로이드에서 String나 ByteArray를 Base64 로 인코딩 / 디코딩 하는 라이브러리는 많다. 기본적으로 안드로이드가 제공하는 Base64도 있고, Apache의 Commons-codec에도 있고, JDK 1.8에 (이제야) 구현된 Base64가 있다.

그러면 ‘어떤 것을 사용하는 것이 더 성능이 나올까?’ 라는 단순한 질문이 나올 수 있는데, 이미 자바로는 2014년에 비교한 Base64 encoding and decoding performance 라는 글이 있다.

다만 해당 글에는 안드로이드에선 사용하지 못하는 DataTypeConverter 나 sun 패키지가 있어 참고로 하기에는 어려운 점이 많다.

그래서 이번 기회로 안드로이드에서 사용할 수 있는 Base64 라이브러리 6종에 대해 성능 비교를 하려고 한다.

비교 대상

성능 비교 방법

비교 방법으로는 크게 3가지가 있다.

  • ByteArray <-> ByteArray 인코딩/디코딩
  • ByteArray <-> String 인코딩/디코딩
  • 이미지 / 비디오 파일 인코딩

ByteArray <-> ByteArray 인코딩/디코딩

fun testBytes(bufferSize: Int): HashMap<String, TestResult> {
    val r = Random(125)
    val buffers = ArrayList<ByteArray>()
    for (i in 0 until bufferSize) {
        val buf = ByteArray(bufferSize)
        r.nextBytes(buf)
        buffers.add(buf)
    }

    val results = HashMap<String, TestResult>()
    for (codec in byteCodecList) {
        val name = codec.javaClass.simpleName
        results[name] = testByteCodec(codec, buffers)
    }

    return results
}

@Throws(IOException::class)
private fun testByteCodec(codec: Base64ByteCodec, buffers: List<ByteArray>): TestResult {
    val encoded = ArrayList<ByteArray>()
    val result = ArrayList<ByteArray>()

    val encodeTime =
        measureTimeStopWatch { for (buf in buffers) encoded.add(codec.encodeBytes(buf)) }

    val decodeTime =
        measureTimeStopWatch { for (ar in encoded) result.add(codec.decodeBytes(ar)) }

    return TestResult(encodeTime.toDouble(), decodeTime.toDouble())
}

buffersize 만큼의 사이즈를 가지는 ByteArray를 bufferSize개 만큼 생성하고, 이를 각 라이브러리마다 인코딩과 디코딩 시간을 측정한다. 예를 들어 1024로 지정했다면 length가 1024인 ByteArray를 1024개를 생성하여 인코딩 시간과 디코딩 시간을 측정하는 방식이다.

여기서 Base64ByteCodec 는 인터페이스로 이 인터페이스를 구현하는 클래스는 AndroidImpl, ApacheImpl, IHarderImpl, Java8Impl, MiGImpl 총 5개로 Guava는 아쉽게도 지원하지 않는다.

class AndroidImpl : Base64Codec, Base64ByteCodec {
    private val flag = Base64.DEFAULT

    override fun decodeBytes(base64: ByteArray): ByteArray {
        return Base64.decode(base64, flag)
    }

    override fun encodeBytes(data: ByteArray): ByteArray {
        return Base64.encode(data, flag)
    }

    override fun encode(data: ByteArray): String {
        return Base64.encodeToString(data, flag)
    }

    override fun decode(base64: String): ByteArray {
        return Base64.decode(base64, flag)
    }
}

내부 구현체는 이런 식으로 testByteCodec 메서드에서 decodeBytes, encodeBytes 등의 메서드를 호출하면 각 라이브러리의 코드가 호출되는 형식이다.

ByteArray <-> String 인코딩/디코딩

위 ByteArray <-> String 와 비슷한 방식이나 호출되는 메서드만 encode, decode가 불린다.

이미지 / 비디오 파일 인코딩

fun testFile(file: File): HashMap<String, TestResult> {
    val results = HashMap<String, TestResult>()
    val fileBytes = file.readBytes()

    for (codec in byteCodecList) {
        val name = codec.javaClass.simpleName
        results[name] = testByteCodecFile(codec, fileBytes)
    }

    // Guava doesn't support ByteArray -> ByteArray. so we ignore them.
    return results
}

@Throws(IOException::class)
private fun testByteCodecFile(codec: Base64ByteCodec, buffer: ByteArray): TestResult {
    val encodeTime = measureTimeStopWatch { codec.encodeBytes(buffer) }
    return TestResult(encodeTime.toDouble(), 0.0)
}

파일을 객체로 받아서 각 라이브러리마다 인코딩 시간만을 측정한다. (디코딩은 측정하지 않았다.)

벤치마크 결과

테스트 기기는 Galaxy S8, 데이터의 정확성을 위해서 전체 테스트 셋트를 3번 반복하여 나온 평균치로 산정했다.

ByteArray <-> ByteArray 인코딩/디코딩

전체적으로 Java8Impl 와 MiGImpl 가 비슷한 속도를 보이고, AndroidImpl는 디코딩에서는 강점을 보이는 반면 인코딩에서는 Apache와 다를바가 없는 속도를 보여주었다.

ByteArray <-> String 인코딩/디코딩

ByteArray <-> ByteArray를 지원하지 않는 Guava가 추가되었는데, 수치가 많이 튄 것을 볼 수 있다. 어떤 한번만 그런 것이 아닌 지속해서 발생하는 것으로 봐서는 테스트 방법이 적절치 않았다고 판단하거나 Guava가 다른 라이브러리와는 조금 다른 구현체를 가지고 있는 것 같다.

ByteArray <-> ByteArray때와 마찬가지로 인코딩 때는 Java8Impl, MiGImpl가 강점을 보이고 디코딩 때에는 Java8Impl 와 AndroidImpl가 강점을 보였다.

이미지 인코딩 (1.5MB JPG / 5MB PNG / 10MB JPG)

1.5MB에서는 5개 라이브러리가 큰 차이를 보이고 있지는 않았으나, 5MB와 10MB에는 Java8Impl – MiGImpl 가 서로 비슷하고, AndroidImpl – IHarderImpl > ApacheImpl 가 서로 비슷했다.

비디오 인코딩 (10MB MP4 / 30MB MP4)

이미지 인코딩의 5MB, 10MB 이미지와 큰 차이는 보이지 않았다. 마찬가지로 Java8Impl 와 MiGImpl가 서로 비슷했다.

정리

결론은 아래와 같이 정리된다.

  • JDK 1.8을 사용하고,  minSDKVersion을 26 이상으로 설정할 수 있다면 JDK 1.8 Base64가 평균적으로 제일 나은 성능을 보여준다.
  • 현실적으로  minSDKVersion를 그정도까지 높이는 것이 어렵다면, MiG Base64 가 제일 현실적이다.
  • Base64 디코딩을 주로 한다면 Android Base64가 그나마 대안이 될 수 있다. 다만 인코딩은 Apache와 크게 다를바가 없음을 알아두자. 그리고 클라이언트 입장에서는 인코딩을 자주 하지, 디코딩을 자주 하지는 않는다.
  • Apache, IHarder는 사용하지 않는 것으로 한다.
  • Guava의 경우 이번 테스트에서는 수치가 튀었지만, 구성하는 데에 문제가 없다면 굳이 바꿀 필요는 없을 것 같다.

벤치마크시에 사용된 샘플 앱은 Github에 공개되어 있다.

JetBrains Day 서울 2018 참가 후기

Shot on Galaxy S8 @세종대학교 광개토관

11월 10일 GDG Devfest Seoul에 이어 11월 22일 목요일 JetBrains Day 서울 2018에 참가하였습니다. 사이트에 따르면 약 1200명 규모로, 지하 1층과 지하 2층에서 진행되었습니다.

Shot on Galaxy S8

이 중 Keynote, Github의 DevOps, ChatOps 소개, Kotlin – What’s New, Kotlin/Anywhere, Kotlin에서 제공하는 Coroutines을 사용하는 방법, Kotlin @ Coupang Backend 적용기 에 참여하였습니다.

이번 글에서는 들었던 세션에 대해 정리를 하려 합니다.

Keynote – 기조연설: 개발자의 생산성을 높이기 위한 장벽 제거

강연자는 Hadi Hariri님으로, 본인에게 있어서는 코틀린을 처음 도입하려 할 때 이 분의 강의(Advance Kotlin Programming) 를 듣고 공부하였기에 실제로 강연을 듣는 것이 감격했습니다.

아무튼, 본 강연에서는 다른 전통적인 기업보다 JetBrains이 일하는 방식에 대해 소개하였는데, 이전 기업들이 9시에 출근해서 5시까지 근무하는 등 다소 fix된 근무를 하는 것이 아닌, 언제 어디서나 자신이 자유의지를 가지고 개발한다는 것(Breaking down the working hours barrier) 입니다.

실제로 어떤 개발자는 밤에 출근해서 다음날 아침에 가는 일도 많다고 하는데, 이는 모니터링 같은 것이 아니라 그 사람이 원해서 그렇게 근무한다고 예제를 들었습니다.

다만 이 규칙이 통하지 않을 때가 있는데, 바로 미팅(Meeting)입니다. ‘여기서 미팅 좋아하시는 분이 있으신가요?’ 라고 질문하셨을때, 아무도 손을 들지 않았습니다. (이 때 사진을 찍어야 겠다고 농담을 하셨던 것 같습니다)

미팅을 좋아하는 사람은 없다고 합니다. 미팅을 왜 좋아하지 않는가 하면, 대부분 나쁜 미팅을 하기 때문입니다. 여담으로 나쁜 미팅은 X같다고도 합니다. (Bad Meetings suck.)

여기서 나쁜 미팅이란, 실제로 얻어지는 결과는 없고 의미를 생성할 수 없는 미팅을 말합니다. 나쁜 미팅이 있는가 하면 좋은 미팅도 있길 마련입니다. 좋은 미팅은 반대로 실제로 행동 가능한 Action Items 를 창출하는 미팅입니다.

좋은 미팅을 위해서는 미팅의 목적을 정의할 필요가 있는데, 여기에서는 Coordination(동등) 과 Status Update(상태 업데이트) 가 있습니다. Coordination의 경우에는 가급적 단일 주제로 이야기 하며, 상기에서도 언급한 Action Items 를 가지고 나올 수 있도록 하는 것입니다.

Status Update는 나는 ‘오늘(이번주) 은 이 것을 했어’ 라고 자신이 수행한 업무에 대해 공유하는 것 입니다. 단순히 공유하는 것 뿐이라면 이 것은 가치를 생성하지는 않습니다.  서로를 믿고, 상호협력적으로 소통이 가능해야 합니다. Status Update를 돕기 위해서는 Tracking Tools나 Logging Standups를 사용할 수 있는데, 이 것으로 ‘목표 달성을 인지하는 것‘ 이 가능합니다.

무엇보다 Status Update를 하기 위해서는 여러 방법으로 서로를 알아가는 것이 좋습니다. 누가 어떤 일을 하는지 알 필요가 있습니다. 서로를 알아가는 방법에는 여러 방법이 있는데, 이메일을 최소화하고 이슈트래커를 활성화하는 것입니다.

여기서 이슈트래커는 단순히 디버그나 개발 이슈 등 기술적 문제뿐만이 아니라 일상적 이슈에도 사용하는 것입니다. 이메일은 다소 정중(Polite) 해서 내가 관심을 가지지 않는 것인데도 수신할 수 있습니다. 반면 이슈트래커는 자신이 관심이 있는 이슈라면 그 곳에서 댓글로 통해 이야기를 나눌 수 있으며, 관심이 떨어진 경우에는 빠질 수 있습니다.

그러면 이메일 대신 Slack는 어떠한가 싶으면, Slack협업 도구로서는 굉장히 좋은 도구임에도 불구하고 자칫하면 비생산성이 될 수 있습니다. (온라인 표시가 뜨기 때문에, 누군가 메세지를 보냈을 때 읽지 않는다면 ‘왜 이 것을 읽지 않죠?’ 라며 묻는 경우가 있다고 한다.)

이메일이나 이슈트래커, Slack등은 정보에 대해 발행하는 것(Push) 입니다. 반대로 정보를 수신하는 것(Pull) 에도 관심을 가져야 합니다. 여기에는 Wiki 가 대표적인 예로 들 수 있습니다. 다만 정보가 많으면 많을 수록 좋은 것이 아닌, 사람이 소화할 수 있는 정도여야 합니다.

다른 방법으로는 Internal Newsletter(사보) 가 있을 수 있습니다. 다른 팀에 속한 인원이 어떻게 일하고 있는지도 가끔은 인지할 필요하기 있기 때문입니다. 또, Internal Conference가 있을 수 있습니다. 이 컨퍼런스는 기술에 대한 것 뿐만이 아닌 세일즈, 마케팅 등 기술과 관련되지 않은 것도 상관 없습니다.

이러한 방법으로 미팅을 좋은 미팅으로 만들어도, Management 가 문제가 될 수 있습니다.

JetBrains 에서는 자잘한 관리나 보고가 없고, 어떠한 행동을 할 때 권한이 필요 없다고 합니다. 위의 사진과 같은 계층을 가진 회사라면 상사가 단순히 목표를 중시하며 목표를 달성하지 못하면 심한 말을 하는 경우가 있습니다. (흔히 이 사례로 KPI가 있는데, KPI를 도입하면 개발에 의해 생성되는 부가가치에 의미 없이, KPI 달성 그 자체가 목표가 되는 경우가 있다고 합니다.)

이러한 장벽을 없애면, 개발자에게 돌아오는 것은 자유 그 자체입니다. 다만 자유에는 비용이 있길 마련입니다.

자가 관리를 통해 개발자 자신을 관리해야 하며, 책임감 있게 업무를 수행해야 하며 어떤 일을 우선적으로 할 것인지에 대한 우선순위를 정해야 하고, 안될 때는 ‘NO’ 라고 말할 수 있어야 하고, 열정을 관리할 필요가 있습니다. (지나친 열정은 문제를 일으킬 수 있습니다.) 그리고 중요한 것은, 자신이 지금 하고 있는 일이 가치를 더할 수 있는 일인지 스스로에게 자문하는 것입니다.

그리고 이 자유는 사원과 사원간의 믿음이 있어야만 성립합니다. 그 사람을 믿지 못한다면 그 사람이 어떤 것을 해도 믿지 않을 것입니다.

상기 언급하였듯이 관리가 없으면 리더십은 있는가? 라고 질문할 수 있습니다. 여기서 리더십에 대한 정의가 필요할 것 같습니다.

먼저, 병목을 없애주는 힘을 가지고, 위임하는 법을 배우고, 가이드라인을 제공할 수 있어야 되며 남이 이야기하는 것을 경청하는 법을 아는 사람이 리더십을 가진 사람이라고 할 수 있습니다.

이러한 과정을 거쳐 문화를 만들어 냈다면, 문화를 유지하는 것도 중요합니다. 문화는 주입되는 것이므로 당신이 지켜낼 필요가 있습니다.

문화를 지켜내기 위해서는 피드백에 중점을 두고, 앉아서 변화를 기대하지 않고, 문화를 보호하지만 변화에는 열려있어야 합니다.

Trust in people. Care for people.
We'll do our best.

마지막으로, 이 슬라이드를 끝으로 강연은 종료되었습니다.

기조 연설인 만큼 어떠한 강연보다 길게 진행되었는데, 오늘 들은 강연 중에서는 제일 인상에 깊었던 강연입니다. 회사가 성립하기 위해서는 지속적인 성과를 낼 필요도 있지만, 서로를 믿고, 보호하고, 소통하면서 더 나은 부가가치를 생성할 필요도 있습니다.

지금 다니는 회사는 다소 산출물만 강요하는 특징이 있어 매일매일 하루가 끝나면 ‘드디어 오늘 하루가 끝났다…’ 라며 지치고는 하지만, 그래도 직원들 끼리는(임원진은… (._.;; )  서로를 믿고 의지하기 때문에 힘내서 업무를 진행할 수 있는 것도 있습니다.

그러면서 느낀 것이, 다음 회사는 서로를 좀 더 믿고 의지해주는 회사로 가고 싶다고 생각했습니다.

이 강연이 끝나고 안영회님의 ‘소프트웨어를 모르는 대한민국의 위기’란 주제로 강연이 열렸지만, 같이 온 일행이 너무 늦게 온 탓에 (9시 20분에 역에서 모이기로 해놓고, 정작 9시 45분에 도착해서) 부스를 못 돌아본 것도 있기 때문에 이 시간을 이용해서 부스를 돌았습니다.

Github의 DevOps, ChatOps 소개

점심시간 1시간 반을 거치고, 다음 강연인 ‘Github의 DevOps, ChatOps 소개’ 에 대해 참가하였습니다.

이번 강연은 Github에 근무하고 계시는 Christian Lewis 님으로, Github 가 Github를 어떻게 배포하고 관리하는지에 대해 설명했습니다.

Github는 지난 몇 년동안 급격하게 성장했음에도 불구하고 서비스를 유지하고 있는 큰 요소라 하면 4가지가 있는데, Common Philosophy (공통된 개발 철학)과 Asynchrous Communication(비동기적 소통), Continous Delivery(CD), ChatOps가 있습니다.

강연 제목에도 나와있듯이 ChatOps는 Github가 Github를 관리하기 유지하고 관리하기 위해 사용하는 요소로 주로 Hubot를 이용합니다.

Hubot는 대화형 봇의 일종으로 배포나 일련의 작업 루틴을 자동화하는 봇입니다. 작게는 이미지 검색이나 지도 검색, 때로는 소리 지르기(!) 등 일에 즐거움을 가져오는 반면, 사이트를 배포하거나 production에 통합하는 과정, 그리고 CI/CD와의 통합 등을 해낼 수 있습니다.

Hubot를 통해 특정 저장소의 기록을 트래킹할 수 있고, 누군가 배포를 시행하려고 할 때 하여금 다른 사원이 리뷰할 수 있게 도와주고, 배포가 된후 문제가 발생했을 때 누구보다도 빠르게 그 문제를 알아채서 알려주기도 합니다.

모든 작업은 브랜치에서 작업되기에 production에는 영향이 가지 않고, 수 많은 리뷰와 문제점 파악을 거친 후에 production에 반영되기 때문에 production에는 영향이 없습니다.

Shot on Galaxy S8

브랜치를 생성하고, PR을 열어 논의나 결론을 내고, Production에 병합할 것인지를 정합니다.

전체적으로 production을 실패시키지 않고 Pull request나 테스트를 통하여 서비스의 전체 유지성을 확보할 수 있습니다. 또한, 브랜치 안에서 실험, 테스트를 하고 문제가 발생했을 때 쉽게 버릴 수 있으므로 리스크 없이 개발 진행이 된다는 것이 포인트입니다.

Kotlin – What’s New

Shot on Galaxy S8

이번 강연은 지난달에 Kotlin 1.3이 정식 출시되면서 출시된 기능에 대해 정리한 강연으로, 기조연설때 강연을 하셨던 Hadi Hariri님이 진행하셨습니다.

Hadi Hariri의 강연 방식은 직접 코드를 보여주고 실행하면서 진행하는 것이 큰 포인트인데, 이번 강연에도 그 사항이 적용되었습니다.

이 강연에서 정리된 Kotlin 1.3 변경점은 다음과 같습니다.

  • Main의 args 파라미터를 제거해도 구동이 가능하도록 변경함
  • 코루틴 (1.3정식) -> 코루틴이 구동될 범위를 스코프로 정의할 수 있도록 추가되었습니다. CoroutineScope를 상속하는 것으로 스코프를 정의할 수 있는데, 기본으로 전역으로 구동되는 GlobalScope가 있습니다. 다만 이 GlobalScope는 사용하는 것이 그렇게 권장되지는 않는데, 이는 개발자로 하여금 코루틴이 구동될 범위를 제한할 필요가 있기 때문입니다.
  • when 내부에 변수 할당 가능 -> 코틀린의 목적인 간결하고 분명한 코드를 위해, when 안에 변수를 할당할 수 있습니다. 할당된 변수는 when 문단에서만 사용이 가능합니다.
  • 변수를 immutable 하게 유지 -> when을 statement가 아닌 expression을 사용하는 것이 권장된다고 설명하셨는데, 변수를 var로 두고 when 을 사용해서 케이스에 맞게 값을 수정하는 것이 아닌 val value = when (somethingPredicate) 방식으로 값을 할당해 모든 케이스에 대응하는 것입니다.
  • 인터페이스에 companion object를 가질 수 있도록 변경되었습니다. 자바에서 companion object 에 정의된 속성이나 함수에 접근하려면 @JvmStatic 나 @JvmField를 사용하면 됩니다.
  • Nested annotation -> @Routing, @Routing.Path 등 어노테이션 내에 어노테이션을 생성할 수 있습니다. 또한 루트 어노테이션에서 companion object를 정의할 수 있는데, 이 companion object를 Nested annotation 에서 사용할 수 있습니다.
  • Inline Class -> inline의 기본적인 기능은 복사+붙여넣기를 좀 더 아름답게 해주는 것으로, 고차함수를 사용할 때 유용합니다. (다만 인라인을 하게 되면 해당 변수에는 object가 없어 reference를 hold할 수 없습니다. 이를 위해 crossinline나 noinline를 파라미터마다 부착할 수 있습니다.) 이 inline를 Class에도 적용시킨 것이 Inline Class인데, 하나의 생성자에 하나의 속성을 가집니다. 즉, inline class Name(val name: String) 라고 선언했을 때 이 Name는 실제로는 String로서 작동합니다.
    • 다만 이 것이 typealias 와 어떻게 다르냐 하면, typealias 는 동의어 개념으로 런타임이 없습니다. 하지만 inline class는 런타임이 있어 object로 작동할 수 있습니다.
  • Unsigned Int
  • Experimental Annotation -> 해당 메서드가 실험적인 기능으로 어느 때에 바뀔 수 있다는 것을 알려주는 어노테이션입니다. 해당 메서드를 사용하기 위해서는 @UseExperimetnal 를 선언해야 합니다.
  • Contract -> text != null 와 text.isNotNull 가 의미적으로는 같음에도 불구하고 컴파일러가 text.isNotNull을 통과했음에도 불구하고 non-null 속성으로 Smart Cast하지 못하는 문제에 대해, Contract는 컴파일러에게 이 메서드를 통과하면 null이 아니라는 것을 개발자가 보증하도록 선언할 수 있는 기능입니다.
  • 멀티 플랫폼 (Kotlin/Native)
  • 리플렉션 -> 코틀린 리플렉션과 자바 리플렉션이 있는데, 코틀린 리플렉션을 사용하면 코틀린 자체의 기능을 얻어올 수 있다.
  • SAM Conversion 강화 -> 자바로 선언된 인터페이스를 사용하기 위해서는 object: Action으로 선언해야 하는데, 이제 고차함수로서 사용할 수 있게 되었습니다.
  • Intersection Type -> 유형 매개변수를 사용할 때 유형 매개변수로 들어온 타입이 어떤 타입인지 정의할 수 있습니다.
    문서: https://kotlinlang.org/docs/reference/generics.html#generic-constraints
  • JvmDefault: 코틀린으로 생성한 인터페이스를 자바에서 interop할 수 있게 변경

Kotlin/Anywhere

이번 강연에서는 2010년 Kotlin 이 시작된 날로부터 지금까지 여러 플랫폼을 대상으로 지원하는 것을 소개하였습니다. 이전 강연과 마찬가지로 강연자는 Hadi Hariri님입니다.

Kotlin이 현재 지원하는 플랫폼은 다음과 같습니다.

  • Kotlin/JVM (Server / Desktop / Mobile)
  • Kotlin/JS (Web)
  • Kotlin/Script (Build Script, Test Script, Commandline Utilities, Routing Scripts (Ktor), Type-safe Configuration files(yml 대체), REPL, Console for Data Science)
  • Kotlin/Native Beta (IOS, MacOS, Windows, Linux, WA 등)
Shot on Galaxy S8

이 중 Kotlin/Native에 좀 더 중점을 두었는데, Kotlin Native는 네이티브 바이너리로 컴파일 되면서 모바일, 데스크톱 뿐만이 아니라 시스템과 임베디드까지 지원이 가능합니다.

Shot on Galaxy S8

컴파일러로는 LLVM 5.0을 사용하는데, 코틀린 소스 코드를 코틀린 컴파일러로 통해 컴파일 하고, LLVM으로 플랫폼 코드로 변화시킵니다.

이로서 Kotlin 으로 대부분 플랫폼을 개발할 수 있어, Common Code를 가지고 각각의 플랫폼에 맞는 코드만 작성하는 등의 개발이 가능합니다.

Shot on Galaxy S8

여기에서 expect 와 actual가 등장하는데, expect는 플랫폼이 구현해야 될 것을 추상화 하는 키워드이고, 실제로 구현하는 것은 actual 키워드를 부착하여 구현합니다.

이 다음에는 실제로 Kotlin/JS나 Kotlin/Script, Kotlin/Native가 실제로 어떻게 동작하는지 예제를 통해 설명하였습니다.

Wrap up

… 왜 갑자기 두 개의 강연을 건너뛰기 했냐면, 집중이 잘 안되었습니다(..)

Shot on Galaxy S8

모든 강연이 끝나고 질문을 받으면서 행사는 마무리 되었습니다.

정리

GDG Devfest는 커뮤니티 주최였던 반면 JetBrains Day는 회사가 주최하는 것도 있어 부스 관리나 등록 데스크 등 다소 퀄리티가 높았습니다. 또한, 영어로 진행되는 강연이 반 이상이고 외국에서 온 손님도 있었기 때문에, 자리마다 무선 수신기가 있어 통역사가 실시간으로 통역하면서 진행했던 것도 다소 놀랐습니다.

Shot on Galaxy S8

또, 좋았던 것은 책상이 있었기에 노트 필기를 열심히 했다는 것입니다. GDG Devfest때도 있었지만 필기가 다소 대충이었는데, 이번에는 제대로 작성하기도 했었습니다.

Shot on Galaxy S8, 기조 연설 당시 필기

비록 평일이지만 많은 사람이 참가하여 의미 있던 시간이 아니었을까 생각해봅니다. 특히 인생에서 몇 손가락 안에 들 정도로 명사를 직접 만나 강연을 들은 것은 아마도 평생 잊혀지지 않을 것 같습니다.

ObjectBox Converter 베이스 모음집

도입

최근 진행하고 있는 개인 프로젝트인 ‘どこでもゆかりん’ 프로젝트에서 ObjectBox 를 중심으로 로직을 구성하는 기능이 있다.

ObjectBox에는 Converter란 개념이 있는데, ObjectBox가 지원하지 않는 타입을 사용할 경우에 지원하는 형태로 map할 수 있는 기능이다.

이 글에서는 해당 프로젝트에서 사용한 ObjectBox Converter의 베이스 형태를 작성해둔 것을 정리하려 한다.

Enum

import io.objectbox.converter.PropertyConverter

open class PropertyEnumConverter<T, R>(private val values: Array<T>, private val default: T,
                                       private val predicate: T.(R) -> Boolean,
                                       private val supplier: (T) -> R?) : PropertyConverter<T, R> {

    override fun convertToEntityProperty(databaseValue: R?): T? {
        if (databaseValue == null) return null
        for (state in values) {
            if (state.predicate(databaseValue)) {
                return state
            }
        }
        return default
    }

    override fun convertToDatabaseValue(entityProperty: T?): R? {
        if (entityProperty == null) return null
        return supplier(entityProperty)
    }
}
 class VoiceEngineConverter : PropertyEnumConverter<VoiceEngine, String>(
        VoiceEngine.values(),
            NONE, { this.id == it }, { it.id })

첫 번째 파라미터에는 해당 Enum의 전체값, 두 번쨰 파라미터에는 값이 없을 때의 기본값, 세번째는 Enum과 db에 있는 값을 map 할 때 사용할 조건, 마지막 네번째 필드는 map 할 값이다.

Json

보통 커스텀 클래스를 다른 엔터티의 값으로 사용하려 할 때 발생한다.

import com.google.gson.Gson
import io.objectbox.converter.PropertyConverter

open class PropertyJsonConverter<T>(private val cls: Class<T>) : PropertyConverter<T, String> {
    override fun convertToEntityProperty(databaseValue: String?): T? {
        if (databaseValue == null) return null
        return Gson().fromJson(databaseValue, cls) as T
    }

    override fun convertToDatabaseValue(entityProperty: T?): String? {
        if (entityProperty == null) return null
        return Gson().toJson(entityProperty)
    }
}
class PresetItemConverter : PropertyJsonConverter<PresetItem>(PresetItem::class.java)