결론 부터 얘기하자면
해당 글에서 얻을 내용이 많지 않고, 심지어 해결을 하지 못했습니다상황을 해결해나가는 과정을 적고자 합니다.
해결이 되지 않은 내용은
다음과 같이 상단에 해결 못했다고 안내글을 남기도록 하겠습니다.
TextField
와 Button
그리고 결과내용이 보일 Text
가 있는 화면이 있다
여기서 Button
을 누르면 remote API를 호출하고 검색 결과를 가져오고 화면에 출력하고 있는 list에 addAll을 한다
여기서 remote API 호출을 할 때 retrofit2을 사용했고
내부call.enqueue{}
로 수행하는데 내부적으로 비동기 처리를 하고있다.
kotlin code
val list = mutableListOf<Document>()
...
// button (onclick)
onSearch = {
val results = searchImage(query = query)
list.addAll(results)
Log.d("TAG", "onCreate: list $list")
}
그러면 기대하는 상황
D/REMOTE DATA SOURCE: API 응답 성공
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2016-03-13T20:12:36.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/yangdreamcommunity/BmPG/289, height=200.0, imageURL=https://t2.search.daumcdn.net/argon/0x200_85_hr/7cTaHuAj0uD, thumbnailURL=null, width=265.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2004-04-02T22:45:16.000+09:00, displaySitename=Daum카페, docURL=http://cafe.daum.net/luxury206/PsUB/30, height=350.0, imageURL=http://cafe159.daum.net/_c21_/pds_down_hdn?grpid=nnXB&fldid=Pl8Q&dataid=21&grpcode=luxury206&realfile=yk37.jpg, thumbnailURL=null, width=467.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2009-04-10T17:27:01.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/wolnam16/VFO/337, height=285.0, imageURL=http://cfile293.uf.daum.net/image/1175340D49DF05F3D6617D, thumbnailURL=null, width=401.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2023-04-10T18:59:17.000+09:00, displaySitename=Daum카페
...
D/TAG: onCreate: list [Document(collection=cafe, datetime=2016-03-13T20:12:36.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/yangdreamcommunity/BmPG/289, height=200.0, imageURL=https://t2.search.daumcdn.net/argon/0x200_85_hr/7cTaHuAj0uD, thumbnailURL=null, width=265.0), Document(collection=cafe, datetime=2004-04-02T22:45:16.000+09:00, displaySitename=Daum카페, docURL=http://cafe.daum.net/luxury206/PsUB/30, height=350.0, imageURL=http://cafe159.daum.net/_c21_/pds_down_hdn?grpid=nnXB&fldid=Pl8Q&dataid=21&grpcode=luxury206&realfile=yk37.jpg, thumbnailURL=null, width=467.0), Document(collection=cafe, datetime=2009-04-10T17:27:01.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/wolnam16/VFO/337, height=285.0, imageURL=http://cfile293.uf.daum.net/image/1175340D49DF05F3D6617D, thumbnailURL=null, width=401.0), Document(collection=cafe, datetime=2023-04-10T18:59:17.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/woori032/LzMX/9238, height=316.0, imageURL=https://t1.da ...
하지만 나온 결과
D/TAG: onCreate: list []
D/REMOTE DATA SOURCE: API 응답 성공
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2016-03-13T20:12:36.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/yangdreamcommunity/BmPG/289, height=200.0, imageURL=https://t2.search.daumcdn.net/argon/0x200_85_hr/7cTaHuAj0uD, thumbnailURL=null, width=265.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2004-04-02T22:45:16.000+09:00, displaySitename=Daum카페, docURL=http://cafe.daum.net/luxury206/PsUB/30, height=350.0, imageURL=http://cafe159.daum.net/_c21_/pds_down_hdn?grpid=nnXB&fldid=Pl8Q&dataid=21&grpcode=luxury206&realfile=yk37.jpg, thumbnailURL=null, width=467.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2009-04-10T17:27:01.000+09:00, displaySitename=Daum카페, docURL=https://cafe.daum.net/wolnam16/VFO/337, height=285.0, imageURL=http://cfile293.uf.daum.net/image/1175340D49DF05F3D6617D, thumbnailURL=null, width=401.0)
D/REMOTE DATA SOURCE: doc: Document(collection=cafe, datetime=2023-04-10T18:59:17.000+09:00, displaySitename=Daum카페
...
Flutter에서 Dart로 했다면..
searchImage()
가 자체 비동기 처리를 하고 있을 때
final list = <Document>[];
...
// button (onclick)
onSearch () async {
val results = await searchImage(query)
list.addAll(results)
log("TAG", "onCreate: list $list")
}
이렇게 위처럼 await
키워드 추가하면 끝인데..
searchImage 액션을 기다리고 list.add 하면 list에 결과가 잘 담길텐데..
kotlin에서는 그러면 어떻게 할까?
[kotlin 공식문서] 비동기 프로그래밍
https://kotlinlang.org/docs/async-programming.html
오래걸리는 함수로 인해 UI가 block된다고 가정
fun postItem(item: Item) {
val token = 오래걸리는첫번째함수()
val post = 두번째함수(token, item)
세번째함수(post)
}
fun 오래걸리는 함수(): Token {
...
return token
}
단점은?
공식문서에는 스레드 작성법이 따로 안 나와있지만Thread 작성법이 궁금하시다면 이 블로그를 참고
하나의 함수를 매개변수로 전달하고프로세스가 완료되면 함수가 호출되도록 하는 것
fun postItem(item: Item) {
오래걸리는첫번째함수 { token ->
두번째함수(token, item) { post ->
세번째함수(post)
}
}
}
fun 오래걸리는첫번째함수(callback: (Token) -> Unit) {
...
}
javascript와 같은 이벤트 루프 아키텍처에서 매우 일반적이지만Promise 또는 Reactive Extensions와 같은 다른 접근 방식을 사용합니다
Threading, CallBack, Future, promises, .. 등 비동기 작업에 다양한 접근 방식이 있지만 Kotlin에선 코루틴을 사용한다
[kotlin 공식문서] Coroutines
official Library
: kotlinx.coroutinesJetBrains에서 개발한 풍부한 코루틴용 라이브러리입니다
나머지 코드와 동시에 작동하는 코드 블록을 실행해야 한다는 점에서
개념적으로 스레드와 유사합니다
: 코루틴은 가벼운 스레드로 생각할 수 있지만 스레드와 다른 중요한 차이점이 많음
: 코루틴은 특정 스레드에 바인딩 되지 않음, 한 스레드에서 실행을 일시중단하고 다른 스레드에서 다시 시작할 수 있음
구조화된 동시성(Structured concurrency)
code:
fun main() = runBlocking {
launch { // new coroutine
delay(timeMillis = 3000L) // non-blocking delay for 3 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello")
}
result:
Hello
(3초 뒤)
World!
코드 분석:
runBlocking{}
일반 코드와 코투틴 코드를 연결해주는 coroutine builder
: 내부의 모든 코루틴이 실행을 완료할 때 까지 호출 기간 동안 이를 실행하고 있는 main thread가 blocked(차단됨)을 의미
: 스레드는 값 비싼 리소스이고 스레드를 차단하는 것은 비효율적이며 바람직하지 않아서 실제 코드 내에선 거의 사용되지 않고, runBlocking
은 자주 보임
launch{}
coroutine builder
: 새로운 코루틴을 시작
: 단독으로 못씀
(꼭 연결해주는 runBlocking{}
과 같은 coroutine builder 안에 사용 해야 됨)
delay(timeMillis = 3000L)
special suspending function
: 특정시간 코루틴을 일시 중단
함수 추출(extract function) 리팩토링을 하면
suspending function이 생김
fun main() = runBlocking {
launch { doWorld() }
println("Hello")
}
// suspending function 정지 함수
suspend fun doWorld() {
delay(1000L)
println("World!")
}
delay
와 같은 suspending 함수를 사용할 수 있음coroutineScope
빌더를 사용해, 범위를 선언runBlocking
과 마찬가지로 body와 모든 자식이 완료될 때 까지 기다림runBlocking
vs coroutineScope
runBlocking{}
대기를 위해 현재 스레드 차단
: 일반 함수
coroutineScope{}
대기를 위해 일시 중단하고
다른 용도를 위해 기본 스레드를 해제
: 정지 함수
두개의 코루틴을 실행하면?
code:
fun main() {
runBlocking {
doWorld()
println("Done")
}
}
suspend fun doWorld() {
coroutineScope {
launch {
delay(2000L)
println("World 2")
}
launch {
delay(1000L)
println("World 1")
}
println("Hello")
}
}
result:
Hello
World 1
World 2
Done
코드 분석:
coroutineScope{}
내 일반 코드(print)와 코루틴 코드(launch) 있음
launch{}
2개의 코드블럭과 println("Hello")
동시에 실행
시작부터 1초 뒤 "World1" 프린트 되고, 시작부터 2초 뒤 "World2" 프린트
coroutineScope
는 모든 함수 처리가 완료 된 후에 반환됨
: 그래서 2초 뒤 "World2" 프린트 후 doWorld이 반환 후 "Done" 프린트
명시적 작업..? 이라는 뜻이 맞는지 정확히 모르겠네요..ㅠ
아무튼 lanch{}
가 Job 타입 인데
그 안에 job.join()
을 넣어 코루틴에게 기다리라고 명령 합니다.
code:
fun main() {
runBlocking {
println("---START---")
val job = launch { // launch a new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello")
job.join() // wait until child coroutine completes
println("Done")
}
println("---END---")
}
result:
---START---
Hello
World!
Done
---END---
job.join()
이 빠졌다면 "Hello" 출력 후 바로 "Done"이 출력 되었을 텐데job.join()
을 넣어 "World!" 출력을 기다리고 끝나고 나서야 "Done " 출력JVM 스레드보다 코루틴은 자원 집약도가 낮습니다.
5만개의 서로 다른 코루틴을 실행해 매우 적은 메모리를 사용하면서 "A" 출력
code:
fun main() {
runBlocking {
repeat(50000) { // 함수를 5만번 실행
launch {
print("A")
}
}
}
result:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ...
여기 위 까지 공식문서 내용인데 용어들이 낯설고 어렵다..
그래서 여태 위에 설명된 공식문서대로 내 문제를 대입 시키면?
할 수 있는게 없다
아까 봤던 코드를 다시보면
val list = mutableListOf<Document>()
...
// button (onclick)
onSearch = {
val results = searchImage(query = query)
list.addAll(results)
Log.d("TAG", "onCreate: list $list")
}
searchImage()
함수의 반환타입을 비동기 함수로 선언할 수 있는 방법을 안 것도 아니고searchImage()
함수 내에서 call.enqueue{}
를 수행하는데 Asynchronously send 라서 코드 자체에 이해도가 부족한 내가 비동기 함수 선언하는 코드 몇자 배웠다고 할 수 있는게 아니였다.현재 .. 매우 절망 상태..
결론적으론 다시.. 어떻게든
retrofit2
(http 통신 라이브러리) + compose
조합으로
화면 변화를 일으킬 수 있는 코드를 작성해보려한다.