🔥 비동기 시퀀스

363자
5분

비동기 시퀀스는 Swift의 강력한 기능 중 하나로, 컬렉션의 요소를 하나씩 기다리면서 순차적으로 처리할 수 있게 해줍니다. 이전 섹션에서 살펴본 listPhotos(inGallery:) 함수는 배열의 모든 요소가 준비된 후에 한 번에 전체 배열을 비동기적으로 반환했죠. 하지만 비동기 시퀀스를 사용하면 컬렉션의 요소를 하나씩 기다리는 또 다른 접근 방식을 사용할 수 있습니다.

비동기 시퀀스를 순회하는 코드는 다음과 같습니다:

swift
import Foundation
 
let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
    print(line)
}
swift
import Foundation
 
let handle = FileHandle.standardInput
for try await line in handle.bytes.lines {
    print(line)
}

일반적인 for-in 루프 대신에, 위의 예제에서는 for 다음에 await을 작성하고 있어요. 비동기 함수나 메서드를 호출할 때와 마찬가지로, await을 작성하는 것은 실행이 일시 중단될 수 있는 지점을 나타냅니다. for-await-in 루프는 다음 요소를 사용할 수 있을 때까지 기다리는 동안 각 반복의 시작 부분에서 실행을 일시 중단할 수 있어요.

for-in 루프에서 자신만의 타입을 사용하기 위해 Sequence 프로토콜을 준수하도록 추가하는 것과 같은 방식으로, AsyncSequence 프로토콜을 준수하도록 추가하여 for-await-in 루프에서 자신만의 타입을 사용할 수 있습니다.

예를 들어, 파일에서 줄을 읽어오는 작업을 비동기 시퀀스로 구현해 볼까요?

swift
import Foundation
 
// 파일에서 줄을 읽어오는 비동기 시퀀스
struct LineSequence: AsyncSequence {
    typealias Element = String
    let url: URL
 
    // 비동기 이터레이터
    struct AsyncIterator: AsyncIteratorProtocol {
        let handle: FileHandle
 
        mutating func next() async throws -> String? {
            // 파일에서 데이터 읽기
            guard let data = try await handle.read(upToCount: 1024) else {
                return nil
            }
            // 데이터를 문자열로 변환하여 반환
            return String(decoding: data, as: UTF8.self)
        }
    }
 
    // 비동기 이터레이터 생성
    func makeAsyncIterator() -> AsyncIterator {
        let fileHandle = try! FileHandle(forReadingFrom: url)
        return AsyncIterator(handle: fileHandle)
    }
}
 
// 파일 URL
let fileURL = URL(fileURLWithPath: "path/to/file.txt")
 
// 비동기 시퀀스 생성
let lines = LineSequence(url: fileURL)
 
// 비동기 시퀀스 순회
for try await line in lines {
    print(line)
}
swift
import Foundation
 
// 파일에서 줄을 읽어오는 비동기 시퀀스
struct LineSequence: AsyncSequence {
    typealias Element = String
    let url: URL
 
    // 비동기 이터레이터
    struct AsyncIterator: AsyncIteratorProtocol {
        let handle: FileHandle
 
        mutating func next() async throws -> String? {
            // 파일에서 데이터 읽기
            guard let data = try await handle.read(upToCount: 1024) else {
                return nil
            }
            // 데이터를 문자열로 변환하여 반환
            return String(decoding: data, as: UTF8.self)
        }
    }
 
    // 비동기 이터레이터 생성
    func makeAsyncIterator() -> AsyncIterator {
        let fileHandle = try! FileHandle(forReadingFrom: url)
        return AsyncIterator(handle: fileHandle)
    }
}
 
// 파일 URL
let fileURL = URL(fileURLWithPath: "path/to/file.txt")
 
// 비동기 시퀀스 생성
let lines = LineSequence(url: fileURL)
 
// 비동기 시퀀스 순회
for try await line in lines {
    print(line)
}

위 코드에서는 LineSequence라는 비동기 시퀀스를 정의하고 있습니다. 이 시퀀스는 주어진 URL의 파일에서 줄을 읽어오는 역할을 합니다.

AsyncIterator 구조체는 AsyncIteratorProtocol을 채택하여 비동기 이터레이터를 구현하고 있어요. next() 메서드는 파일에서 데이터를 읽어와 문자열로 변환하여 반환합니다. 파일의 끝에 도달하면 nil을 반환하죠.

makeAsyncIterator() 메서드는 AsyncIterator 인스턴스를 생성하여 반환합니다. 이 메서드는 주어진 URL의 파일을 열고 FileHandle을 사용하여 파일을 읽을 준비를 합니다.

마지막으로, for-await-in 루프를 사용하여 LineSequence를 순회하면서 파일의 각 줄을 출력하고 있습니다.

이처럼 비동기 시퀀스를 활용하면 컬렉션의 요소를 하나씩 처리하면서 비동기 작업을 수행할 수 있습니다. 이는 대용량 데이터를 다룰 때 특히 유용하겠죠? 메모리 사용량을 줄이고 효율적으로 처리할 수 있습니다.

YouTube 영상

채널 보기
AI는 왜 수백 차원의 벡터를 사용할까? 고차원 공간과 행렬 | 선형대수학
내적의 기하학적 의미와 코사인 유사도 원리 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
BTree 노드의 구조는?
스칼라 곱셈과 내적의 기하학적 의미 | 선형대수학
트라이(Trie)에서 단어를 삭제하는 방법 | Trie 자료구조 이야기
우리가 매일 쓰는 맞춤법 검사기와 라우터 속에 숨겨진 알고리즘은? | Trie 자료구조 이야기