본문 바로가기

Swift

async과 await 개요

Swift에서 async와 await는 비동기 코드를 작성하기 위한 주요 키워드로, 비동기 작업을 보다 직관적이고 안전하게 작성할 수 있도록 설계되었습니다.

1. async

  • 역할: 비동기 작업을 포함하는 함수나 메서드를 정의합니다.
  • 특징:
    • async 함수는 호출 시 즉시 결과를 반환하지 않고, 작업이 완료되기를 기다립니다.
    • async 함수 내부에서만 await 키워드를 사용할 수 있습니다.

2. await

  • 역할: 비동기 작업의 결과가 준비될 때까지 실행을 일시 중단합니다.
  • 특징:
    • 호출된 작업이 완료될 때까지 기다립니다.
    • await는 항상 async 컨텍스트 내에서 사용해야 합니다.

 

3. 사용 방법

기본 예제: async와 await

// 비동기 함수 정의
func fetchData() async -> String {
    // 네트워크 요청이나 시간 소요 작업을 모방하기 위해 sleep 사용
    try? await Task.sleep(nanoseconds: 1_000_000_000) // 1초 대기
    return "데이터 가져오기 완료"
}

// 비동기 함수 호출
Task {
    let result = await fetchData()
    print(result) // 출력: "데이터 가져오기 완료"
}

 


🔥 Task의 역할🔥

1. fetchData 메서드 외부의 Task

  • 역할: 비동기 작업을 실행할 수 있는 컨텍스트를 제공합니다.
  • Task 생성자를 사용하면 비동기 코드를 실행할 수 있는 독립적인 작업을 만듭니다.
  • 작업은 즉시 실행되며, 내부에서 정의된 비동기 함수(fetchData)를 호출할 수 있습니다.
Task {
    let result = await fetchData()
    print(result)
}

 

주요 특징:

  • Task 블록은 비동기 실행의 진입점입니다.
  • Task는 별도의 비동기 작업으로 실행되며, 내부의 작업이 완료되기를 기다립니다.
  • 이 Task는 작업이 독립적으로 실행되므로 호출자가 해당 결과를 기다리거나 관리하지 않아도 됩니다.
  • 예를 들어, 이 코드는 메인 스레드에서 실행되면서도 다른 작업을 차단하지 않습니다.

 

2. fetchData 메서드 내부의 Task

  • fetchData 내부에서 사용된 Task.sleep는 작업을 일정 시간 동안 일시 중단합니다. 이 Task는 Task 타입의 클래스 메서드로 제공되는 유틸리티 메서드입니다.
  • 역할:
    • 현재 실행 중인 태스크를 일정 시간 동안 일시 중단합니다.
    • 시스템 리소스를 차단하지 않고, 다른 태스크가 실행될 수 있도록 스케줄링됩니다.
try? await Task.sleep(nanoseconds: 1_000_000_000)
  • 주요 특징:
    • Task.sleep는 현재 태스크의 실행을 일시 중단하는 메서드입니다.
    • 이 호출은 비동기 작업이므로 await를 사용해 대기해야 합니다.

 

3.결론

  • 외부의 Task: 비동기 작업 실행의 진입점을 제공하며, 독립적으로 실행됩니다.
  • 내부의 Task.sleep: 현재 태스크를 일시 중단하는 유틸리티로 사용됩니다.
  • 두 Task는 동일한 이름을 사용하지만, 각각 태스크 생성과 태스크 제어라는 다른 역할을 수행합니다.

 

async와 await의 조합으로 병렬 작업 처리

// 두 개의 비동기 함수 정의
func fetchUser() async -> String {
    try? await Task.sleep(nanoseconds: 1_000_000_000) // 1초 대기
    return "유저 정보"
}

func fetchPosts() async -> String {
    try? await Task.sleep(nanoseconds: 2_000_000_000) // 2초 대기
    return "게시글 목록"
}

// 병렬 작업 실행
Task {
    async let user = fetchUser()
    async let posts = fetchPosts()

    let userResult = await user
    let postsResult = await posts

    print("결과: \(userResult), \(postsResult)")
    // 출력: "결과: 유저 정보, 게시글 목록"
}

 

🔥 두 개의 비동기 함수에 순서를 적용하려면? 🔥

await를 사용하여 첫 번째 비동기 작업이 완료된 후 두 번째 비동기 작업을 실행하도록 순차적으로 작성할 수 있습니다.

 

순서를 적용한 코드 예제

아래 코드에서는 fetchUser()가 먼저 실행되고, 해당 유저 정보를 확인한 후 fetchPosts()를 실행하여 유저의 게시글을 가져옵니다.

Task {
    // 1. 먼저 fetchUser() 호출
    let userResult = await fetchUser()
    print("유저 정보: \(userResult)")
    
    // 2. fetchUser() 결과를 확인한 후 fetchPosts() 호출
    let postsResult = await fetchPosts()
    print("게시글 목록: \(postsResult)")
    
    // 최종 결과 출력
    print("결과: \(userResult), \(postsResult)")
}

 

코드 실행 순서

  1. fetchUser()를 호출하고 await로 결과를 기다립니다.
  2. fetchUser()의 결과를 사용하거나 확인한 뒤, fetchPosts()를 호출합니다.
  3. 각 비동기 작업은 순서대로 실행됩니다.

 

async let과의 차이점

  • async let은 비동기 작업을 병렬로 실행할 때 유용하지만, 순서를 보장하지 않습니다.
  • 반면 await는 특정 작업이 완료될 때까지 기다리므로 순차 실행이 가능합니다.

 

네트워크 요청하는 예제

import Foundation

func fetchJSON(from url: String) async throws -> [String: Any] {
    guard let url = URL(string: url) else {
        throw URLError(.badURL)
    }

    let (data, _) = try await URLSession.shared.data(from: url)
    let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
    return json ?? [:]
}

Task {
    do {
        let result = try await fetchJSON(from: "https://jsonplaceholder.typicode.com/todos/1")
        print(result)
    } catch {
        print("네트워크 오류: \(error)")
    }
}

 

1. async throws의 의미

fetchJSON 함수는 비동기적으로 실행되며 동시에 오류를 던질 수 있는 함수로 정의되었습니다.

  • async:
    • 함수가 비동기적으로 실행됨을 나타냅니다.
    • 네트워크 요청과 같은 시간이 오래 걸리는 작업을 수행할 때 사용됩니다.
    • 비동기 함수는 await 키워드로 호출됩니다.
  • throws:
    • 함수 내에서 오류를 던질 수 있음을 나타냅니다.
    • 오류 발생 시 호출한 코드에서 do-catch 블록을 사용하여 오류를 처리해야 합니다.

이 함수에서 throws를 사용하는 이유네트워크 요청 실패JSON 파싱 실패와 같은 상황에서 적절히 오류를 전달하기 위해서입니다.

 

2. 반환 값

async throws -> [String: Any]

 

  • 이 함수는 Dictionary 타입의 값을 반환합니다.
    • : String
    • : Any (JSON의 값이 다양한 타입일 수 있기 때문에 Any로 선언)
  • 예를 들어, 반환값은 다음과 같은 형태일 수 있습니다:
[
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
]

 

 

3. let (data, _)에서 _의 의미

let (data, _) = try await URLSession.shared.data(from: url)

 

이 코드에서는 URLSession의 data(from:) 메서드로부터 튜플 (Data, URLResponse)를 반환받습니다.

  • data: 서버로부터 받은 실제 데이터 (Data 타입).
  • _: 사용하지 않을 값을 무시할 때 사용합니다.
    • 여기서는 URLResponse 객체를 사용하지 않으므로 _로 처리합니다.
    • _는 "여기 값이 있지만 필요하지 않다"는 의미입니다.

 

4. JSONSerialization을 이용한 JSON 파싱

let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]

 

  • JSONSerialization: JSON 데이터를 Swift 객체로 변환하는 클래스입니다.
    • data: 네트워크 요청으로 받은 Data 타입의 JSON 데이터.
    • options: 변환 시 옵션 (기본적으로 빈 배열 사용).
  • 변환 결과를 [String: Any] 타입으로 캐스팅합니다.
    • 성공하면 딕셔너리를 반환합니다.
    • 실패하면 nil을 반환합니다.

 

5. Task의 역할

Task {
    do {
        let result = try await fetchJSON(from: "https://jsonplaceholder.typicode.com/todos/1")
        print(result)
    } catch {
        print("네트워크 오류: \(error)")
    }
}

 

 

  • Task:
    • 비동기 작업의 실행을 시작하는 컨텍스트를 제공합니다.
    • 여기서는 fetchJSON을 호출하여 네트워크 요청을 실행합니다.
  • do-catch:
    • fetchJSON에서 발생한 오류를 처리합니다.
    • 예를 들어:
      • 잘못된 URL(URLError.badURL).
      • 네트워크 요청 실패.
      • JSON 파싱 실패.