반응형

Kotlin은 자바에는 없는 Scope(스코프 : 범위) Function 이라는 문법이 존재합니다. 그 이름 처럼 특정 객체의 범위(scope)를 만들어주는 함수로서 객체의 멤버 여러개를 접근해야 할 때 그 객체만의 범위를 만들어주는 함수로서 코드의 가독성이 아주 좋아집니다.

 

♣ Scope function :

- 객체의 멤버를 실수없이 편하게 사용할 수 있는 범위(영역)을 만들어내는 함수 - 람다식을 이용하여 영역처리, 가독성 향상

 

스코프 함수에 대한 문법을 소개하기 전에 이 함수의 효용성을 느낄 수 있도록 클래스를 하나 만들고 그 멤버들을 사용하는 상황은 살펴보겠습니다.

 


 

실습을 위해 Test10ScopeFunction.kt 코틀린 파일을 만들겠습니다.

프로그램의 시작함수 main()를 작성하겠습니다.

 

1. 스코프 함수의 등장

자바에는 없었던 스코프 함수가 왜 등장하게 되었는지를 느껴보기 위해 멤버변수가 여러개인 클래스를 하나 설계해 보겠습니다.

 

어떤 그룹에 소속한 사람들(크루원)의 [이름, 나이, 주소] 정보를 관리하는 앱을 만들어야 한다고 예를 들어보겠습니다.

사람 한명당 [이름, 나이, 주소] 3개의 데이터를 저장해야 하기에 그룹화 하여 관리하도록 Crew 라는 이름으로 클래스를 설계해 보겠습니다. 편의상 별도의 .kt 파일보다는 main()함수 아래에 작성하겠습니다.

//시작함수
fun main(){


}//main 함수 종료...

//어떤 그룹의 크루원 정보 저장 클래스 
class Crew{
    
    //property [ 이름, 나이, 주소 ]
    var name:String?= null
    var age:Int?= null
    var address:String?= null

    //method - property 출력 기능
    fun show(){
        println("$name : $age , $address")
    }
}

 

이제, Crew 클래스를 사용하기 위해 객체로 생성 한 후 멤버변수에 값을 대입하고 출력해보는 코드를 작성해 보겠습니다. 

//시작함수
fun main(){

    //Crew클래스 객체 생성 및 멤버 여러개를 사용해보기
    val crew= Crew()    
    crew.name="sam"
    crew.age= 20
    crew.address="seoul"
    crew.show()

}//main 함수 종료...

//어떤 그룹의 크루원 정보 저장 클래스 
class Crew{
    
    //property [ 이름, 나이, 주소 ]
    var name:String?= null
    var age:Int?= null
    var address:String?= null

    //method - property 출력 기능
    fun show(){
        println("$name : $age , $address")
    }
}

//**출력**
sam : 20 , seoul

 

이미 객체에 대한 학습을 진행했다면 어렵지 않게 객체를 생성하고 멤버를 사용하기 위해 . 연산자로 멤버에 접근하여 사용했을 겁니다. 여기까지는 특별한 내용이 없습니다. 자바에서도 많이 했던 작업입니다.

 

근데. 조금 생각해 볼까요? 위 Crew 객체의 멤버는 총 4개 입니다. property 3개, method 1개 입니다.

이 멤버 4개(변수3,메소드1)를 사용할때 마다 객체명.xxx 라고 쓰는게 생각보다 번거롭고 코드의 실수가능성도  많으며 가독성도 떨어집니다. 위 코드를 보면 멤버를 사용할 때 마다 crew.neme , crew.age, crew.xxx 형태로 매번 crew 라고 쓰는거 좀 짜증입니다. 다행히 지금은 객체명이 다소 짧은 crew 였습니다. 4글자에 불과하죠. 근데 만약 더 길다면? 예를 들어 sharedPreferences 처럼 긴 이름를 사용해야 한다면 매번 작성하기 너무 번거롭습니다. 복사붙이기로도 해소하기 곤란한 내용이며 타자 실수도 나올 가능성이 많습니다. 이를 위해 등장한 문법이 Scope(스코프:범위) functioin(함수) 입니다.

 

스코프 함수의 종류는 5개 정도 있는데 일단, 이 중에 가장 많이 사용되는 apply 를 이용하여 간략하게 소개하겠습니다.

코틀린의 모든 객체는 기본적으로 스코프함수를 사용할 수 있으며 함수이지만 통상 범위 영역을 만들기위해 람다식으로 { } 를 작성합니다.

//시작함수
fun main(){

    //Crew클래스 객체 생성 및 멤버 여러개를 사용해보기
    val crew= Crew()    
    crew.name="sam"
    crew.age= 20
    crew.address="seoul"
    crew.show()
    
    //스코프 함수로 멤버 여러개 사용해보기
    var crew2= Crew()
    crew2.apply {
        //이 apply{} 영역 안에서는 this가 crew2를 의미함. 또한 this는 생략이 가능함
        this.name= "robin"
        age= 25
        address="busan"
        show()
    }

}//main 함수 종료...

 

코드에서 볼 수 있듯이 특정 객체(crew2)의 멤버 범위(scope) 영역을 만들어주는 스코프함수 apply의 중괄호 {} 영역 안에서는 객체명을 사용할 필요 없이 마치 class 내부에서 처럼 this 키워드를 사용할 수 있습니다. 이 this 참조변수가 바로 crew2를 의미하게 되는 문법입니다. 즉, crew2.name 대신에 this.name 으로 작성이 가능하며 나아가 class의 중괄호 {} 안에서 그랬듯이 this 키워드는 생략이 가능하기에 this.age 를 범위 안에서는 this 없이 그냥 age만 작성할 수 있어 코드가 매우 간결해 지는 특징이 있는 문법입니다.

위처럼 영역을 묶었기에 참조변수명을 잘못 기입하는 실수를 줄일 수 있고 다른 개발자가 볼때도 crew2에 대한 설정들을 하나의 영역에 묶어서 가독성이 좋아집니다.

 

※ 원래는 아래처럼 객체생성후 apply 라는 이름의 함수를 추가하는 것입니다. 일종의 확장함수(extension) 같습니다.  이를 람다식처럼 줄여서 ()를 생략한 문법인 것 입니다. 거의 대부분 람다식으로 작성합니다.

// 원래는 이런식으로 객체생성후 apply 라는 이름의 함수를 추가하는 것임( 일종의 확장함수 ) -- 이를 람다식처럼 줄여서 ()를 생략한 문법임
crew2.apply(){
}

 

 

2. 스코프 함수의 종류

스코프 함수는 크게 2가지 분류로 구분할 수 있습니다.

 

1) 영역안에서 this 키워드로 본인을 참조하는 scope function : apply, run
2) 영역안에서 this 키워드 대신 it으로 본인객체를 참조하는 scope function : also, let   [ 마치 람다식 처럼 ]

 

두 분류의 차이를 대략적으로 알아보고 자제한 내용을 이어서 소개하겠습니다.

영역안에서 this 키워드로 본인을 참조하는 것은 위에서 살펴보았으니 it 키워드로 본인을 참조하는 스코프 함수 중 also 를 이용하여 crew의 멤버값을 사용해 보겠습니다.

//시작함수
fun main(){

    //Crew클래스 객체 생성 및 멤버 여러개를 사용해보기
    val crew= Crew()    
    crew.name="sam"
    crew.age= 20
    crew.address="seoul"
    crew.show()
    
    //스코프 함수로 멤버 여러개 사용해보기
    var crew2= Crew()
    crew2.apply {
        //이 apply{} 영역 안에서는 this가 crew2를 의미함. 또한 this는 생략이 가능함
        this.name= "robin"
        age= 25
        address="busan"
        show()
    }
    
    //영역안에서 it을 사용하는 스코프 함수
    val crew3= Crew()
    crew3.also {
        // also{} 영역 안에서는 it이 crew3을 의미함. it은 생략 불가
        it.name= "hong"
        it.age= 30
        it.address="paris"
        it.show()
    }

}//main 함수 종료...

 

코드에서 확인 가능하듯이 영역안에서 this 를 it 으로 변경한 것만 차이가 있고 기능적인 부분은 똑같기에 상황에 따라 어떤 스코프 함수를 사용하든 상관없습니다. 경우에 따라 더 적합한 것이 다르기에 지금 당장 어떤 것을 써야할 지 확실히 구분하려 할 필요는 없습니다. 경험이 쌓이면 자연스럽게 선택하여 사용이 가능해 집니다. 조금해 하지 마세요.

 

그런데. 이렇게 2개의 분류가 있으니 스코프 함수도 2개면 될 것 같은데 각 분류마다 2개씩 존재하는 이유가 뭘까요?

if 표현식을 기억하시나요? if문의 마지막 실행문의 결과 값을 리턴하여 사용할 수 있습니다.

스코프 함수도 리턴이 있습니다. 단지 함수 축약표현에 의해 생략된 것입니다. 

이 리턴값이 다릅니다. 한 종류는 객체 본인의 참조값을 리턴하고, 또 다른 종류는 마지막 실행문의 값이 리턴됩니다.

 

정리하면.

  리턴값이 본인객체 리턴값이 마지막 실행문의 값
영역안에서 this 키워드 사용 apply run
영역안에서 it 키워드 사용 also let

 

 

각각의 사용 모습을 알아보겠습니다.

 

1) 영역안에서 this 키워드 사용

 

1.1) apply - 본인객체를 return

// 1.1) apply - 본인객체를 return
val crew4:Crew= Crew()
val crew5:Crew= crew4.apply {
    name="kim"   //this. 키워드 생략 가능
    age= 40
    address="newyork"
    //별도의 리턴 명시가 없지만 무조건 return this 인것임
}
crew5.show()

 

1.2) run - 마지막 실행문이 return 값

// 1.2) run - 마지막 실행문이 return 값
val crew6:Crew= Crew()
val len= crew6.run {
    name="park"
    age= 45
    address="tokyo"
    name?.length //마지막 실행문이 리턴값  - 글자수 4 리턴됨
}
println("이름의 글자수 : $len")

 

 

2) 영역안에서 it 키워드 사용

 

2.1) also -  본인객체를 return

//2.1) also - it 키워드를 사용하며 본인객체를 return
val crew7:Crew= Crew()
val crew8:Crew= crew7.also {
    it.name="lee"
    it.age= 50
    it.address="incheon"
    //별도의 리턴 명시가 없지만 무조건 return this 인것임
}
crew8.show()

 

2.2) let - 마지막 실행문이 return 값

//2.2) let - it 키워드를 사용하며 마지막 실행문이 return 값
val crew9:Crew= Crew()
val n:String?= crew9.let {
    it.name="choi"
    it.age=20
    it.address="LA"
    it.name?.uppercase()  //마지막 실행문의 결과가 리턴   -- 이름을 대문자로 변경한 결과값 "CHOI" 리턴됨
}
println("이름의 대문자 출력 : $n")

 

 

 

추가로. 위 4개의 scope 함수의 문법적 표기법이 다소 다른 scope 함수가 존재합니다. 

영역을 만들려는 객체에게 스코프함수를 적용하는 것이 아니라 스코프함수의 영역에 객체를 전달하는 형태의 모습입니다. 

 

3) with 

스코프함수 run과 비슷한 동작을 하지만 문법적 사용이 다른 스코프 함수 입니다.

//** scope function 중 run과 비슷한 동작을 하는 with가 있음. 사용문법이 약간 다름.... **
val crew10:Crew= Crew()
with(crew10){      //범위 안에 객체를 파라미터로 전달받는 형태
    name="aaa"
    age=20
    address="HANOI"
    show()
    name?.lowercase()  //리턴값을 마지막 실행문의 결과
}

 

 

5가지 스코프 함수를 알아봤는데 앞서 설명했듯이 스코프 함수를 많이 사용하다 보면 자연스럽게 상황에 맞는 종류의 스코프함수를 선택하여 사용하게 됩니다. 지금부터 완벽하게 효율적으로 구분하여 사용하려고 노력하지 않아도 됩니다.


 

스코프 함수는 앱 개발에서 같은 객체에게 여러작업을 할때 유용하게 사용할 수  있습니다.

[ex. AlertDialog.Builder, Notification.Builder, SharedPreference.Editor ....]

 

마지막으로, 안드로이드 앱 개발에서 스코프 함수를 사용하는 코드의 예를 보여드리겠습니다.

현재 파일은 일반 코틀린 파일이기에 따라해도 에러가 발생합니다. MainActivity.kt 액티비티 안에서 사용하는 것을 가정하여 코드만 소개하는 것이니 눈으로만 보시며 스코프함수의 효용성을 느껴보시길 기대합니다.

 

- 스코프 함수를 사용하지 않고 다이얼로그를 생성하는 코드  ( 스코프 함수 X )

val builder:AlertDialog.Builder= AlertDialog.Builder(this)
builder.setTitle("aaa")
builder.setMessage("bbbb")
builder.setPositiveButton("OK", null)
builder.setNegativeButton("CANCEL", null)
val dialog=builder.create()
dialog.show()

 

- 스코프 함수를 사용하여 다이얼로그를 생성하는 코드  ( 스코프 함수 O )

val builder2:AlertDialog.Builder= AlertDialog.Builder(this)
val dialog2= builder2.run{
    setTitle("sss")
    setMessage("asdfasdfa")
    setPositiveButton("확인", null)
    setNegativeButton("취소", null)
    create()      // 빌더가 생성하는 dialog 객체가 리턴됨
}
dialog2.show()
반응형

+ Recent posts