Suspend 함수와 유사성

Kotlin의 suspend는 다른 suspend 함수 내부에서만 호출될 수 있고, suspend 함수 또한 호출 컨텍스트를 필요로 한다.

이는 suspend 함수 끼리 묶여 있을 수 있도록 강제하며, Kotlin Compiler가 모든 계산 과정에 걸쳐 런타임 환경을 주입하고 전달하는 기회를 제공하게 된다.

이 런타임 환경에선 매개변수 목록의 끝에 Continuation이라는 추가 매개변수가 각 susepnd 함수에 추가된다.

이 매개변수는 암시적이며 비가시적인 매개변수이다. 이전에 알아보았던 Composer과 비슷한 형태를 가지고 있다.

Continuation은 프로그램에게 어떻게 실행을 계속 이어서 할지 알려주는 콜백과 같으며 이런 특징을 통해 언어 차원에서 몇 가지의 강력한 기능을 제공한다.

Continuation은 Kotlin Runtime이 프로그램의 여러 중단점에서의 실행을 일시 중단하고 재개하는데 필요한 정보들을 가지고 있다.
마찬가지로 Composable 함수는 표준 Kotlin 함수를 재시작이 가능하고, 상태로부터 반응할 수 있도록 만들어 준다.

이렇게나 비슷한데 왜 Compose는 suspend로 구현되지 않았냐면 이 두 기능이 지향하는 패턴은 매우 유사하지만, 언어 수준에서 완전히 다른 기능을 담당하기 때문이다.

Continuation 인터페이스는 실행을 중단하고 재개하는 것에 대우 구체적이며, 콜백 인터페이스로 모델링 되어 있으며, Kotlin은 실행 지점 간 번갈아가며 작업을 수행하고, 중담점을 조절하며, 중단점 사이에서 데이터를 공유하는 등 기본적인 구현을 제공한다.

하지만 Compose의 경우엔 런타임에서 다양한 방법으로 최적화될 수 있는 대규모 함수 호출 그래프에서 인메모리 표현을 생성하는 목표를 가지고 있기 때문이다.

Composable 함수의 색깔

표준 함수에서는 Composable 함수를 호출할 수 없다.
왜냐하면 Composable 함수는 표준 함수와 완전히 다른 목표를 가지고 있기 때문이다.

Composable 함수는 프로그램 로직을 작성하기 위해 설계된 것이 아니라 모든 노드들을 포함한 트리의 변경사항을 기술하기 위해 존재하기 때문이다.

Composable 함수의 장점 중 하나는 논리적으로 UI를 작성하는 데 있다. 이 말은 표준 함수에서 Composable 함수를 호출해야 하는 경우도 있다는 뜻이다.

이 말은 지금까지 설명해온 말과는 다르게 혼동되는 말 일 것이다.
Composable 함수는 다른 Composable 함수에서만 호출된다면서 뭔 표준함수? 싶겠지만 대부분의 그 규칙은 유지되지만 예외사항이 있다는 것이다.

    @Composable  
    fun SpeakerList(speakers: List<Speaker>) {  
        Column {  
            speackers.forEach {  
                Speacker(it)  
            }  
        }  
    }

forEach문은 분명 표준 함수인데도 불구하고 Composable 함수인 Speaker가 호출되어도 아무 문제가 없다.

그 이유는 forEach함수는 인라인 함수이기 때문이다.
컬렉션 연산자들은 인라인으로 선언되어 있기 때문에 호출한 부분에 람다식을 inline 시켜 마치 비 Composable 함수에서 Composable 함수를 호출하는 행위가 아닌 것처럼 효과를 준다.

위 코드는 Speacker 함수의 호출을 SpeakerList 본문 내에서 인라인 시키며, 둘 다 Composable 함수 이기에 허용된다.
함수 컬러링 문제를 우회하기 위해서 inline을 활용할 수 있고, 이를 통해 트리가 Composable로만 이루어 질 수 있도록 한다.

Composable 함수 타입

@Composable 어노테이션은 컴파일 시점에 함수의 타입을 변경한다.

함수의 구문적 관점으로 보면 Composable 함수의 타입은 @Composable (T) -> A 이다.

여기서 A는 Unit일 수 있고, 함수가 값을 반환하는 경우에는 다른 타입을 가지게 될 수 도 있다.
개발할 때에는 Kotlin에서 일반적인 람다를 선언하는 것처럼 Composable 람다를 선언할 수 있다.

val textComposable: @Composable (String) -> Unit = {  
    Text(text = it)  
}  


@Composable  
fun Name(name: String) {  
    textComposable(name)  
}  

또는, Composable 함수는 @Composable Scope.() -> A와 같은 형태의 타입을 가질 수 있는데, 이는 특정 Composable로만 정보 범위를 지정하는데 자주 사용된다.

inlint fun Box(  ...,  content: @Composable BoxScope.() -> Unit) {  
    Layout(  
        content = { BoxScopeInstance.content() },  
    )  
}

언어적인 관점에서 볼 때, 이러한 타입은 컴파일러에게 정보를 제공하여 빠른 정적 검증을 수행하고,
때로는 편리한 코드를 생성하며, 런타임에 활용되는 데이터 사용 방식을 제한 및 정제하기 위해 존재한다.

@Composable 어노테이션은 런타임 시 Composable 함수의 유효성을 검사하고 사용하는 방법을 변경하는데,
이것이 Composable 함수가 Kotlin의 표준 함수와 다른 타입으로 간주되는 이유이다.

+ Recent posts