[코틀린 동시성 프로그래밍] 4장 일시 중단 함수와 코루틴 컨텍스트 -1

Sdoubleu·2023년 1월 17일
0

코틀린 동시성

목록 보기
6/10
post-thumbnail

지금까지는 일시 중단 코드 작성을 launch, async와 같은 코루틴 빌더를 이용하는 것으로 제한해 왔지만, 코틀린은 더 많은 방법을 제공

일시 중단 함수를 배워보고 지금까지 사용했던 비동기 함수와 비교
코루틴 컨텍스트와 그 사용법

4장에서 다루는 주제

  • 일시 중단 함수의 개요
  • 일시 중단 함수를 사용하는 방법
  • 일시 중단 함수 대신 비동기 함수를 사용하는 경우
  • 코루틴 컨텍스트
  • 디스패처, 예외 처리기 및 취소 불가와 같은 다양한 유형의 컨텍스트
  • 코루틴 동작을 정의하기 위한 컨텍스트의 결합 및 분리

RSS 리더 UI 개선

  • RSS 리더를 보다 더 사용자 친화적으로 만들 적당한 시점
    뉴스 기사 정보를 사용자가 스크롤 할 수 있는 목록으로 표시하도록 애플리케이션을 변경하고, 기사의 헤드라인 뿐 아니라 기사의 요약과 기사가 포함된 피드를 표시

각 피드에 이름 부여

  • 먼저 데이터 클래스를 구성할수 있도록 새로운 패키지인 model을 작성하고 다음과 같이 model.kt 코틀린을 그 안에 생성해보자.

URL뿐만 아니라 이름으로도 피드를 식별할 수 있기 원하기 때문에,
새 모델 파일 안에 Feed라는 이름의 데이터 클래스를 만듦
클래스는 각 피드에 대한 name과 url을 쌍으로 갖음

MainActivity에 있는 feeds의 내용을 Feed 유형이 되도록 업데이트 가능👌
목록의 마지막 요소는 잘못된 피드라는 점에 유의💣

private val feeds = listOf(
	Feed("npr","https://www.npr.org/rss/rss.php?id=1001"),
    Feed("fox","https://feeds.foxnews.com/foxnews/politics?format=xml"),
        //"https://rss.cnn.com/rss/cnn_topstories.rss",  //<- 사이트 변경으로 인하여 오류 발생때문에 주석처리
	Feed("inv","htt://myNewsFeed")
)
  • asyncFetchHeadlines() 함수를 변경해 Feed를 가져오고 해당 URL 속성을 사용해 피드를 가져오는 것이 좋음

피드의 기사에 대한 자세한 정보 가져오기

  • 각 기사에 대해서 feed, title, summary를 포함하는 자세한 정보를 표시하려고 함
    정보를 저장할 데이터 클래스를 만들어보자. Feed 바로 다음에 만들 수 있음

  • title만 반환하는 asyncFetchHeadlines() 함수 대신에, 해당 함수의 이름을
    asyncFetchAricle()로 지정하고 피드의 기사에 해당하는
    Deferred<List<Article>>을 반환

asyncLoadNews() 에서의 요청 목록을 적절하게 업데이트해야 함

스크롤이 가능한 기사 목록 추가

  • 현재 안드로이드 앱에서 스크롤 가능한 목록을 표시해주는 가장 좋은 방법은 RecyclerView 를 사용하는 것으로, 이를 액티비티에 추가
  • 모듈의 build.gradle 파일에 의존성을 추가하는 업데이트를 해야 함

기사별 레이아웃

  • 기사의 정보를 표시하기 위한 레이아웃이 필요하기 때문에 layout 폴더에 XML 파일을 생성

정보 매핑을 위한 어댑터

ViewHolder 추가

  • RecyclerView의 기본 개념은 비용이 많이 드는 뷰 생성 프로세스를 최대한 피한다는 것
    대신 작은 뷰 세트가 생성돼 재사용되며, RecyclerView라는 이름을 통해 사용자가 스크롤할 때 정보를 표시

  • 이 작업을 위해서 ViewHolder라는 것이 필요,
    ViewHolder 는 나중에 재사용될 수 있는 뷰의 레이아웃 요소를 갖는 객체
    각 항목에 대해 정의한 레이아웃을 기반으로 어댑터 안에 ViewHolder를 생성

↪ ViewHolder 클래스는 RecylerView.ViewHolder를 확장하고
레이아웃 자체를 super 생성자에 전달한다는 점을 주목⚡

데이터 매핑

  • 어댑터가 작동하려면 어떻게 ViewHolder를 생성하고 내용을 대체할 수 있는지 알아야하고, 현재 갖고 있는 요소의 양을 반환할 수 있어야 함
    첫 번째 단계로 ViewHolder 타입을 사용하는 RecyclerView.Adapter를 확장

  • 작업이 끝나면 세 가지 함수를 구현해야 함

  1. onCreateViewHolder()
    새로운 ViewHolder를 생성할 때마다 호출된다. 필요에 따라 모든 뷰를 인플레이트하고 ViewHolder를 사용할 준비가 된 상태로 반환한다.

  2. onBindViewHolder()
    ViewHolder의 내용을 지정된 위치에 요소의 내용을 로드/치환하기 위해
    호출된다. 뷰의 내용을 업데이트하기 위해 필요하다.

  3. getItemCount()
    어댑터가 갖고 있는 요소의 수량을 반환해야 한다.

inflate는 사전적으로 '부풀리다'라는 뜻인데,
View를 정의한 XML이나 자바 파일을 기반으로 View 객체 만드는 것을
의미한다.

OnCreateViewHolder

개별 기사에 대해 정의한 XML 레이아웃을 인플레이트하고 정보를 표시하는 데 사용될 뷰를 찾을 것이다.
새로운 ViewHolder가 필요할 때마다 함수가 호출

onBindViewHolder

여기서 전달 받은 위치(position)에 따라 기사를 검색하고, 그에 따라서 뷰의 텍스트를 로드해야 함.
함수는 첫 번째 그룹의 기사를 표시하기 위해 처음 호출되며 나중에 사용자가 기사의 내용을 변경하기 위해 스크롤하는 시점에 호출

getItemCount

article의 사이즈를 반환

어댑터에 기사를 점진적으로 추가

현재는 어댑터에 기사를 추가하는 외부 클라이언트를 허용하는 어떠한 함수도 노출하지 않음
우리가 구현한 어댑터는 외부 클라이언트가 기사를 추가할 수 있는 어떤 함수도 노출하지 않고 있음
기사 그룹을 추가할 수 있도록 간단한 함수를 추가

액티비티에 어댑터 연결

List<Article>를 뷰에 매핑할 수 있는 어댑터가 만들어졌음
-> 메인 액티비티에 추가한 RecyclerView와 함께 이 어댑터를 사용

  • OnCreate() 함수에서 이것들 모두를 인스턴스 화할 것

  • UI에서 기사를 실제로 표시할 수 있으려면 asyncLoadNews()를 업데이트해서 어뎁터에 검색된 요소를 추가
    -> 이전에 남겨뒀던 TODO를 변경

  • 프로그레스바를 숨기고 새 기사를 viewAdapter에 추가하는 코드를 추가

새 UI 테스트

UI 테스트를 하기 전에, 데이터를 표시하기 전 일정 시간 동안 프로그레스바를 실제로 볼 수 있도록 asyncFetchArticles()에 약간의 지연 시간 추가

  • 앱을 실행하면 현재의 스크롤 위치를 나타내는 사이드에 있는 스크롤바와 뉴스가 표시되는 것을 보게 됨
    -> 스크롤하면 일부 내용에 약간 문제 발생!

데이터 삭제

기사 중 일부는 summary 부분에 HTML 태그가 표시된다.
보통 이런 태그들은 summary가 HTML을 지원하는 뷰어에 표시되기 때문에 문제가 되지 않지만, 여기에서는 뷰어를 사용하지 않기 때문에 제거하는 것이 좋음

div 요소가 나오면 설명 부분을 잘라내기 위해 asyncFetchArticles()를 수정 필요!

위 코드에서는 <div 요소 전 까지의 내용을 summary로 가져온다.
summary의 내용이 <div로 시작한다면 가져오는 summary의 내용이 모두 없어진다.


일시 중단 함수

launch(), async(), runBlocking()과 같은 코루틴 빌더를 사용해서 일시 중단 알고리즘의 대부분을 작성
코루틴 빌더를 호출할 때 전달하는 코드는 일시 중단 람다임

  • 실행의 한 부분인 일시 중단 함수가 생겼으며 디스패처를 사용해 보다 편리하게 시점에 호출 가능
    단일 스레드 컨텍스트를 사용해 호출할 수도 있음

동작 중인 함수를 일시 중단

2장에서 동시성 코드를 구현할 때 코루틴 빌더 대신 비동기 함수를 사용하는 쪽이 더 편리한지에 대해 설명했음
이제 일시 중단 함수를 추가해 이 주제를 확장할 차례

  • 간단한 레파지토리를 구현할 때 비동기 함수를 사용한 구현과
    일시 중단 함수를 사용한 구현을 비교해보자!

우리는 비동기 함수를 구현한 Job을 (Deferred를 포함해서) 반환하는 함수라고 했다. 이러한 함수는 보통 launch() 또는 async() 빌더로 감싸인 함수이지만, 구현한 잡이 반환될 때만 비동기 함수로 본다.

비동기 함수로 레파지토리 작성

잡 구현을 반환하는 함수가 있으면 어떤 시나리오에서는 편리할 수 있지만,
코루틴이 실행되는 동안에 일시 중단을 위해서
join() 이나 await() 를 사용하는 코드가 필요하다는 단점💣이 생김

기본 동작으로 일시 중지를 하고 싶으면 어떻게 해야 할까?

비동기 함수를 사용해 레파지토리를 설계하고 구현하는 방법을 살펴보자.
다음과 같이 데이터 클래스에서부터 시작

  • 구현에서 관찰할 수 있는 몇 가지 사항
  1. 함수 이름이 이해하기 편리하게 돼 있음
    -> 함수가 비동기라는 점을 명시하는 것이 중요

  2. 이러한 클라이언트의 성질로 인해, 호출자는 항상 요청이 완료될 때까지 일시 정지해야 하므로 보통 함수 호출 직후에 await() 호출이 있게 됨
    f

  3. 구현은 Deferred와 엮이게 될 것
    다른 유형의 퓨처(future)로 ProfileServiceRepository 인터페이스를
    깔끔하게 구현하기 위한 방법은 없음

일시 중단 함수로 업그레이드

  • 비동기 구현에 비해 몇 가지 분명한 이점
  1. 유연함
    인터페이스의 상세 구현 내용은 노출되지 않기 때문에 퓨처를 지원하지 모든 라이브러리를 구현에서 사용할 수 있음
    현재 스레드를 차단하지 않고 예상된 Profile을 반환하는 구현이라면 어떤 퓨처 운영도 동작할 것

  2. 간단함
    순차적으로 수행하려는 작업에 비동기 함수를 사용하면 항상 await() 를 호출해야 하는 번거로움이 생기고, 명시적으로 async가 포함된 함수의 이름을 지정해야 함
    일시 중단 함수를 사용하면 레파지토리를 사용할 때마다 이름을 변경하지 않아도 되고 await() 를 호출할 필요가 없어짐

일시 중단 함수와 비동기 함수

  • 다음 목록은 Deferred<T>를 포함하는 구현과 관련된 Job을 사용

  • 일반적으로 구현에 Job이 엮이는 것을 피하기 위해서는 일시 중단 함수를 사용하는 것이 좋음

  • 인터페이스를 정의할 때는 항상 일시 중단 함수를 사용
    비동기 함수를 사용하면 Job을 반환하기 위한 구현을 해야 함

  • 마찬가지로 추상함수를 정의할 때는 항상 일시 중단 함수를 사용
    가시성이 높은 함수일수록 일시 중단 함수를 사용해야 함
    비동기 함수는 private / internal 함수로 제한돼야 함

  • 비동기 함수를 사용하는 코드를 적은 범위로 제한하는 것이
    Job을 더 이상 사용할 수 없을 때 리팩토리의 영향을 줄임

다음장에 계속..

profile
개발자희망자

0개의 댓글