Search

[WWDC] Meet Swift Async Algorithms

들어가며

Swift는 계속 성장 중인 오픈 소스 패키지 카탈로그입니다.
 : 이 패키지는 Swift Collections & Swift Algorithms와 같은 다른 패키지들과 유사합니다. AsyncSequence 프로토콜을 사용해 시간에 따라 값을 처리하는 일련의 알고리즘인데요.
본론으로 들어가기 전에 AsyncSequence 프로토콜을 한 번 상기해 봅시다.

AsyncSequence Recap

AsyncSequence 프로토콜은 비동기 생성값을 기술할 수 있게 해줌
 : Sequence와 유사하나 두 가지 차이점이 있습니다.
반복자 다음 함수(next function form its iterator)는 Swift 동시성을 사용해 값을 전달하기 때문에 비동기 함수입니다.
Swift의 throw를 통해 잠재적인 장애를 해결합니다.
Sequence에서처럼 for-await-in 구문으로 이걸 반복할 수 있습니다.
for-await-in syntax
 즉, Sequence를 사용할 줄 알면 AsyncSequence 사용은 어렵지 않습니다.
AsyncSeqence를 도입하면서 Sequence에서 찾아볼 수 있는 모든 툴을 비동기 버전으로 추가했습니다.
 map, filter, reduce
고급 알고리즘을 통합하고 강력한 걸 제공하고자 Clock와 상호 운용되죠. Swift 동시성을 늘리는 AsyncSequence 알고리즘의 오픈 소스 패키지입니다.
작년 Swift Algorithm 패키지를 소개했고 그 사용 사례를 증명하고자 메시징 앱을 만들었습니다.
 : 우리는 Swift 동시성을 통해 앱 마이그레이션 기회가 많이 생겼다고 판단하였습니다. 비동기 알고리즘을 몇 가지 보여드리기 위해 우리가 사용했던 것들과 어떻게 실행되는지 보여드리도록 하겠습니다.

Multi-input algorithms

먼저, 우리에게는 다중 입력 AsyncSequence로 작동하는 특정 알고리즘들이 있습니다. 이 알고리즘들은 AsyncSequence를 다양한 방식으로 결합하는데요.
 : 하지만 하나의 공통점은 AsyncSequence가 다중으로 입력되지만 단일 출력된다는 점입니다.
이 알고리즘은 각 베이스에서 결과 튜플을 생성하기 위해 다중 입력을 취하고 그것을 반복합니다.
입력물 각각은 Zip이 구조를 이루고 있는 베이스입니다.
비동기 Zip 알고리즘은 표준 라이브러리에서 Zip과 유사하게 작동하나, 동시에 베이스 각각을 반복하고 반복 시 오류가 발생하면 다시 throw 합니다.
반복과 오류 rethrow를 동시에 하는 것은 더 복잡해질 수 있습니다.
하지만 Swift Async Algorithm은 메시징 앱에서 그걸 모두 다룰 수 있었죠.
[Zip 영상]
효율적인 저장과 전송을 위해 영상을 다양한 크기로 트랜스코딩하면서 비동기 조정하는 코드가 이전에도 있었는데요.
  Zip을 통해 트랜스코딩 영상을 다른 서버로 전송할 때 미리보기를 생성할 수 있게 되었습니다.
  Zip은 동기성이라 트랜스코딩이나 미리 보기가 둘 다 지연되지 않습니다.
 : 하지만 여기서 끝이 아닙니다.
Zip 자체가 어떤 쪽에서 먼저 값을 생성하는지 더 선호하는 것이 없기 때문에 영상이나 미리보기 둘 중 아무거나 먼저 생성되고 어느 쪽이든 간에 다른 쪽이 튜플을 완료할 때까지 기다릴 겁니다.
Zip은 튜플을 구축하기 위해 서로를 기다릴 수 있기 때문에 함께 업로드될 수 있도록 짝을 기다릴 수 있습니다.
우리는 입력 메시지를 AsyncSequence로 모델링하는 것이 꽤 타당할 것이라는 결론을 내렸습니다.
AsyncSequence는 명령을 보존하여 콜백을 메시지 비동기 시퀀스로 바꿀 수 있기 때문에 메시지 처리에 비동기 스트림을 사용하기로 결정했습니다.
 : 우리가 처리해야 할 부분 중 하나는 다양한 계정을 지원해야 한다는 점이었습니다.
각 계정에서 입력 메시지의 AsyncSequence를 만들었으나 막상 구현하려고 하니 하나의 AsyncSequence로 처리해야 했거든요.
 즉, AsyncSequence 병합 알고리즘이 필요했습니다.
Merge 알고리즘이 갖춰져 있는데요.
이 알고리즘은 다중 AsyncSequence를 동시에 반복한다는 점에서 Zip과 비슷하게 작동합니다.
하지만 다른 점이 있다면,
짝을 이루는 튜플 대신 베이스가 동일 요소 타입을 공유해야 합니다.
베이스 AsyncSequence를 해당 요소의 특이 AsyncSequence 하나에 병합합니다.
[Merge 영상]
병합 알고리즘은 반복 시 한 쪽 계정에서 먼저 생성된 요소를 취합니다. 모든 베이스 AsyncSequence가 반복자에서 아무것도 도출하지 않을 때까지 반복은 계속 됩니다. 베이스에서 오류가 발생한다면 다른 반복은 취소됩니다. 이를 통해 메시지를 AsyncSequence를 취하고 병합할 수 있죠. 이 Merge 알고리즘은 값 생성 시 동시에 작동하지만 간혹 시간과 상호 작용하는 게 유용할 때가 있습니다.

Clock, instant, duration

이번 패키지는 특정 알고리즘을 도입해 Swift 내 새로운 Clock API를 이용함으로써 시간과 작동합니다. 시간 그 자체는 굉장히 복잡한 주제로 Swift 5.7의 clock, instant, duration 타입은 시간을 안전하고 일관되게 하는 일련의 API입니다.
Clock 프로토콜은 2개의 프리미티브를 정의합니다.
 : 주어진 순간 이후 일어나는 방법과 지금이라는 개념을 생성하는 방법을요.
Clock에는 여러 가지 구조가 들어가 있는데 흔히 쓰이는 구조 중 두 가지는 바로 continuous clockSuspending Clock입니다.
continuous clock
스톱워치처럼 시간 측정 시 사용 가능하며 사물 상태 측정 여부와는 상관없이 시간은 흘러 갑니다.
suspending clock
머신이 정지하면 멈추죠.
우리는 데드라인 이후 경고를 무시할 수 있도록 앱에서 새로운 시계 API를 사용하였는데요.
지연하고자 하는 초를 구체적으로 가리키는 duration 값을 추가해 데드라인을 만들 수 있었습니다.
작동 실행 경과 시간을 측정하는 방법도 있구요.
 : 아래에서는 잠재적 작업 경과 시간의 측정 결과를 보여줍니다.
[측정 결과 영상]
이 두 시계의 주요 차이점은 머신이 정지했을 때의 반응에서 나타나는데요.
 실행을 재개하면 머신이 정지되어 있는 동안 continuous clock은 계속 작동하지만 suspending clock은 그렇지 않습니다.
보통 이 차이는 주요 특징이 실행 시간을 중단함으로써 애니메이션과 같은 것들을 예상한대로 작동하게 하는 주요 세부 사항이 될 수 있습니다.
 만약 애니메이션처럼 머신이 시간과 상호 작용해야 한다면 Suspending clock를 사용하세요.
 사람과 관련된 테스크를 측정하는 것이라면 continuous clock이 적합합니다.
 즉, 절대적 지속 구간에 따라 지연시켜야 하는 인간과 관련된 일에서는 후자를 사용하세요.

Algorithms using time

신형 패키지에서는 시간과 관련하여 이벤트가 전개되는 개념들을 처리하기 위해 이 타입들을 사용합니다.
 : 우리는 메시징 앱을 통해 이벤트를 정확히 통제하기 위해 이 타입들이 굉장히 도움된다는 사실을 발견했습니다. 상호 작용 제한 시간을 측정해 메시지를 효과적으로 버퍼링했죠.
우리가 가장 시간을 많이 활용하는 분야는 메시지 검색 입니다.
우리는 결과 채널을 관리하는 컨트롤러를 만들었죠. 이 채널은 검색 테스크에서 UI로 검색 결과를 모읍니다. 검색 테스크 자체에는 시간과 관련된 구체적인 특성이 필요했죠.
 : 서버에서 발송된 메시지 검색 제한 시간을 측정하려 했습니다.
Debounce 알고리즘은 반복 시 다음 값을 내기 전 정지 기간을 대기합니다.
 : 이벤트가 빨리 올 수도 있지만 값을 처리하기 전에 정지 기간 동안 대기하고자 한다는 거죠.
검색 필드의 유저 입력이 빠르게 바뀔 때 우리는 검색 컨트롤러가 각 변화의 검색 요청을 급하게 하길 원치 않습니다. 대신에 특정 타이핑이 완료되려고 할 때까지의 정지 기간 동안 대기하길 원하는 거죠.
Debounce 알고리즘은 기본적으로 Continuous Clock을 사용할 겁니다.
 이 경우 아무 일이 일어나지 않는 특정 기간동안 대기하게 할 수 있고 입력을 debouncing할 수 있습니다.
 : 시간과 지속 기간은 debounce뿐만 아니라 다른 알고리즘에서도 사용할 수 있습니다.
 : 우리가 정말 유용하다고 발견한 분야는 바로 대량의 메시지를 서버로 전송하는 것이었습니다.
Swift 알고리즘 패키지에는 값을 나누는 일련의 알고리즘이 있는데요. Swift Async Algorithm 패키지에서도 이들을 제공합니다. 다양한 Clock 및 duration과 상호 운용하는 버전들도 추가되었음은 물론이고요.
특정 Chunk 알고리즘들을 통해 카운트, 시간, 콘텐츠에 따라 통제가 가능합니다.
*만약 여기서 오류가 발생하면 다시 throw하기 때문에 오류와 관해서는 안전한 코드입니다.
[Chunk 영상]
우리는 메시지 청크가 특정 경과 시간마다 직렬화되어 전송될 수 있도록 chunked(by:) API를 사용했습니다.
이 방식으로 클라이언트가 보낸 효율적인 패키지를 받았죠. 대량 메시지를 50밀리초마다 입력할 때도 이 API를 사용할 수 있었습니다.
 : 누군가 신나서 너무나 빠르게 입력할 때도 서버에 전송된 요청이 나뉘어졌죠!!

Collections

컬렉션이나 시퀀스로 작업할 때는 요소를 게으르게(lazy) 처리하는 게 유용하며 잘 작동합니다.
AsyncSequence는 Swift 표준 라이브러리에서 이런 작동 방식과 유사하게 작동하죠. 하지만 lazy 알고리즘처럼 종종 컬렉션 세계로 돌아가야 할 때가 있습니다.
신규 패키지는 AsyncSequence로 컬렉션을 구축하기 위한 일련의 이니셜라이저를 제공합니다. 이들을 통해 유한하다고 알려진 AsyncSequence를 입력하여 Dictionary, Set, Array를 구축할 수 있습니다.
 : 컬렉션 이니셜라이저는 바로 메시지 초기화로 변환되고 데이터 타입을 배열로 유지할 수 있게 해줍니다.
Swift 동기성 사용을 위해 최신 정보를 사용할 수 있는 특색이 다양했기 때문에 굉장히 도움이 되었습니다.
 기존 데이터 구조를 유지함으로써 앱의 일부를 점차 타당한 곳으로 마이그레이션할 수 있었습니다.
지금까지 Swift Async Algorithm 패키지의 하이라이트 중 극소수만 보여드렸는데요.
오늘 본 것보다 훨씬 더 많은 알고리즘이 있습니다.
 : 다중 AsyncSequence를 결합하는 것부터
 : 시간에 따른 제한을 측정하고
 : 값을 나누는 알고리즘
 : 버퍼링하고
 : reduce 알고리즘, join 알고리즘부터 injection 알고리즘까지 더욱 다양합니다.
Swift Async Algorithms 패키지는 시간에 따라 처리할 수 있도록 알고리즘 집합을 사용하고 이를 앱에 도움이 될 수 있는 광범위한 고급 기능으로 확대합니다.
 : 한 번 써보세요!! 이 패키지로 여러분들이 무엇을 하려 하실지 기대됩니다! 그리고 그런 열정은 공유가 되는거죠. 이 패키지는 여러분과 함께 개발하는 것입니다.