본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[도서 정리] Android Development with Kotlin - Extension Functions and Properties

by 돼지왕 왕돼지 2018. 12. 16.
반응형

Android Development with Kotlin - Extension Functions and Properties




이 정리글은 Kotlin in Action 책을 보고 실무에 Kotlin 을 사용하던 사람이 몰랐던 내용이나 remind 하고 싶은 내용을 위주로 정리한 글입니다.

제대로 내용을 파악하시려면 책을 구매해서 읽어보세욤~


Also, android with kotlin, apply, companion object extension, Comparator, compareby, comparebydescending, Count, dispatch receiver, DSL, extension function, extension function inline, Extension functions and properties, extension property, extension property backing field, extension receiver, extension 단점, extension 순기능, function type with receiver, generatesequence, GroupBy, lambda with receiver, let, member extension function, member extension property, oneach, operator overloading, priority extension function member function, run, SEQUENCE, sorted, sortedbydescending, sortedwith, Streaming, sumby, Take, With


-

extension function 들도 inline 가능하다.



-

extension function 과 같은 이름의 member function 이 있다면,

항상 member function 이 우선순위를 갖는다.

여기서 member function 이라 하면 super class 에 있는 member function 들도 해당한다.


이 말은 extension function 은 기본 동작을 변경할 수는 없고, 기능 추가의 개념으로만 쓰인다는 것.



-

아래 케이스를 통해 동일 이름의 extension function 이 있을 경우 define 된 type 의 것이 불린다는 규칙을 알 수 있다.

그래서 lib 을 설계하거나 할 때, subclass 에 동일 이름의 extension function 을 사용하지 않도록 주의해야 한다. 의도치 않은 shadow 효과를 낼 수 있다.

abstract class A 
class B: A() 

fun A.foo() { println("foo(A)") } 
fun B.foo() { println("foo(B)") } 

val b = B() 
b.foo() // prints: foo(B) 
(b as A).foo() // 1, prints: foo(A) 
val a: A = b 
a.foo() // 1, prints: foo(A)



-

companion object 에도 extension 을 추가할 수 있다.

fun A.Companion.foo() { … } // A 에 empty 더라도 companion object 가 있어야 한다.


이렇게 추가된 extension 은 static 함수처럼 사용 가능하다.

A.foo() // instance 에 부르는 것이 아니다.


-

operator overloading 도 가능하다.



-

extension 은 순기능도 있지만, 새로운 developer 가 왔을 때 새롭게 API 를 배워야 하는 단점도 있다.



-

extension property 는 backing field 가 없는 case 에만 설정될 수 있다.

read-only 면 getter 만 생성되고, 그렇지 않으면 getter, setter 가 extension 으로 생성된다고 보면 된다.



-

언제 extension function 을 써야 할지 언제 extension property 를 써야할지 고민이라면. 아래를 보라.

다음 조건을 충족하는 경우에만 property 로 쓰는 것이 좋다.


error 를 던지지 않음.

O(1) complexity 를 가짐

계산이 오래 걸리지 않음.

여러번 호출해도 항상 같은 결과를 return



-

member extension function & property 도 가능하다.

이들은 top level 에 정의된 녀석들이 아니라 class 안에 정의된 녀석들이다.


이들은 기본적으로 해당 class 와 children 에서만 접근할 수 있다.


top-level 에 private access level 로 정의한 것도 비슷하지만.

top-level private 은 해당 파일에 종속적인 visibility 이다.


또한 member extension 은 function 과 비슷하게 member 변수들에도 접근 가능하다.


그렇다면 member extension 이 일반 function 에 대해 장점이 무엇이 있을까?

extension 하는 receiver context 를 물고 가서 코드를 짧게 가져갈 수 있다.



-

member extension function 의 경우 extension receiver 뿐만 아니라, dispatch receiver 에 대한 암시적 참조를 가지고 있다.

동일한 function signature 를 가진 경우 extension receiver 가 항상 우선권을 갖고 호출이 된다.

dispatch receiver 는 this@MainActivity 와 같이 명시적으로 호출해주어야 한다. ( 유일한 경우는 필요 없다. )



-

forEach 의 경우 iterate 하고 끝나는데..(return 이 Unit) onEach 는 extension receiver 를 그대로 return 한다

onEach 는 보통 logging 용으로 잘 사용하고 Kotlin 1.1 부터 사용 가능하다.



-

collection 을 index 와 함께 처리하려면 아래 api 들을 잘 사용하면 된다.

Collection.withIndex()
Collection.filterIndexed{ i, v -> … }
Collection.mapIndexed{ i, v ->… }
Collection.forEachIndexed{ i, v -> … }



-

class User(val points: Int) 
val users = listOf(User(10), User(1_000), User(10_000))
val points = users.map { it.points }. sum()


요 녀석은 쓸 데 없이 list 를 한번 더 생성하기 때문에.. sumBy 를 쓰면 좋다.

val points = users.sumBy{ it.points }


값 치환을 하려면 sumByDouble 등과 같은 API 를 쓰면 된다.



-

count 는 predicate 를 만족하는 갯수를 return 하는데, predicate 를 명시하지 않으면 그냥 collection 의 size 가 나온다.



-

sorted() 는 sorted list 를 return 한다. 기본 오름차순이다.

내림차순으로 하려면 sortedByDescending{ 기준 } 을 호출해주면 된다.



-

take() 함수를 통해 앞선 몇개만 추출해낼 수 있다.



-

comparator 가 있다면 sortedWith( comparator ) 를 통해 sorting 할 수도 있다.

그리고 comparator 를 만들지 않아도 되도록 compareBy 와 compareByDescending 이라는 함수들도 제공한다. 저 함수들이 comparator 를 만들어준다



-

groupBy function 을 이용해 쉽게 map 을 만들 수도 있다.

val grouped = listOf("ala", "alan", "mulan", "malan") 
    .groupBy { it.first() } 
println(grouped) // Prints: {'a': ["ala", "alan"], "m": ["mulan", "malan”]}



-

streaming 에 사용하는 sequence 를 collection 을 통해서가 아닌 직접 생성할 수도 있다.

generateSequence(1) { it + 1 } // 1부터 1을 stepping 해서 sequence 를 만들어냄


-

function type with receiver ( lambda with receiver ) 는 일반 extension function 과 동일하지만, receiver 를 this 로 받을 수 있다는 차이점이 있다.

var power: Int.(Int) -> Int


-

let 은 보통 ?. operator 와 함께 사용되며, let 안에 it 으로 receiver 가 전달되고. return 값이 return 된다.

also 는 it 으로 receiver 가 전달되고, receiver 가 그대로 return 된다.


apply 는 it 대신 receiver 가 context 로 전달이 되고, receiver 가 그대로 return 된다

with 는 it 대신 receiver 가 context 로 전달되고, return 값이 return 된다. ( extension 이 아님 )

run 은 it 대신 receiver 가 context 로 전달되고, return 값이 return 된다. ( extension 임 )



-

DSL 을 사용하면 addTextWatcher 를 멋지게 구현할 수 있다.

class TextWatcherConfig : TextWatcher { 
 
  private var beforeTextChangedCallback: (BeforeTextChangedFunction)? = null // 1 
  private var onTextChangedCallback: (OnTextChangedFunction)? = null // 1 
  private var afterTextChangedCallback: (AfterTextChangedFunction)? = null // 1 
 
  fun beforeTextChanged(callback: BeforeTextChangedFunction){  // 2 
    beforeTextChangedCallback = callback 
  } 
 
  fun onTextChanged(callback: OnTextChangedFunction) { // 2 
    onTextChangedCallback = callback 
  } 
 
  fun afterTextChanged(callback: AfterTextChangedFunction) { // 2 
    afterTextChangedCallback = callback 
  } 
 
  override fun beforeTextChanged (s: CharSequence?, start: Int, count: Int, 
  after: Int) { // 3 
    beforeTextChangedCallback?.invoke(s?.toString(), start, count, after) // 4 
  } 
 
  override fun onTextChanged(s: CharSequence?, start: Int, before: 
  Int, count: Int) { // 3 
    onTextChangedCallback?.invoke(s?.toString(), start, before, count) // 4 
   
  } 
 
  override fun afterTextChanged(s: Editable?) { // 3 
    afterTextChangedCallback?.invoke(s) 
  } 
} 
 
private typealias BeforeTextChangedFunction = (text: String?, start: Int, count: Int, after: Int)->Unit 
private typealias OnTextChangedFunction = (text: String?, start: Int, before: Int, count: Int)->Unit 
private typealias AfterTextChangedFunction = (s: Editable?)->Unit 

fun TextView.addOnTextChangedListener(config: TextWatcherConfig.()->Unit) { 
    val textWatcher = TextWatcherConfig() 
    textWatcher.config() 
    addTextChangedListener(textWatcher) 
} 


위의 코드들이 있다면.. 아래와 같이 쓸 수 있다.

searchView.addOnTextChangedListener { 
   onTextChanged { text, _, _, _ -> 
       presenter.onSearchChanged(text) 
   } 
}

훨씬 간결하게 쓸 수 있고, 사용하지 않는 param 도 감출 수 있고, default function impl 도 주입할 수 있고, 사용하지 않는 function 들의 empty override 도 줄일 수 있다.




반응형

댓글