Swift Concurrency 1편 async await 이 뭔데.

김재형·2024년 5월 29일
1
post-custom-banner

들어가기에 앞서...

해당 글은 ShopY( Swift UI Mini Project ) 에 처음으로 도입한
Swift Concurrency 를 사용해 가며, 이해가 덜가는 부분들이 많아
작성하게 된 글입니다. 총 4편(예정) 으로 이루어질 장편 블로그 글입니다.

WWDC 2021 - Swift Concurrency?


https://youtu.be/0TD96VTf0Xs 1:37:30 부터 쭉

WWDC 에서 나온 Swift Concurrency의 핵심은 아래와 같습니다.
" 동시성 코드를 Simple 하게 "
" 빠르고 현대적이며 안전하게 "

그리고 이어서 설명중 위 사진과 같은 코드가 나옵니다.
actor async await
그리고 이번 글에는 asyncawait 를 다루어 보려고 합니다.

async / await

APPLE 은 말합니다.
" 개발자님들 동시성 처리가 힘드셨죠? 이젠 Simple 하게 해드릴꼐요~! "
어느 부분의 동시성 처리가 힘들었다고 말하였던 것일까요?

  • 기존 URLSession 방식 ( @escaping complition Handler )
func someRequest<Dto: DTOType>(router: Router, completion: @escaping (Result<Dto, Error>) -> Void) {

    let urlRequest = router.makeURLRequest()
    
    let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
        if let error = error {
            completion(.failure(error)) // 헨들러
            
        } else if (response as? HTTPURLResponse)?.statusCode != 200 {
            completion(.failure(NetworkError.failForStatusCodeChange)) // 헨들러
            
        } else {
            guard let data = data else {
                completion(.failure(NetworkError.dataIsNil)) // 헨들러
                return
            }
            do {
                let decodedData = try JSONDecoder().decode(Dto.self, from: data)
                completion(.success(decodedData)) // 헨들러
                
            } catch {
                completion(.failure(NetworkError.decodingError)) // 헨들러
                
            }
        }
    }
    task.resume()
}

기존의 URLSession 방식을 통해 네트워크 코드를 구현해 보았습니다.
오류가 나올 모든 곳에 completion: @escaping 을 사용하여 해당 메서드가 정상적으로
종료될수 있게 해주었습니다.

이 때 문제가 존재 하는데, 더 복잡한 구조였다면 개발자는 모든 경우의 수마다
completion: @escaping 을 통해 함수가 종료될수 있도록 하게 하여야 합니다.
그렇게 된다면 코드는 길어지고, 가독성 마저 떨어지게 되겠죠.

async / await 두 두 등 장

Swift 5.5 에서 등장한 새로운 키워드로, 비동기 프로그래밍을 Simple & Easy 하게 만들어좁니다.
방금전 코드를 async / await 를 통해 구현해보죠

func someRequest<Dto: DTOType>(router: Router) async throws -> Dto {
    let urlRequest = router.makeURLRequest() // Thread Block
    
    let (data, response) = try await URLSession.shared.data(for: urlRequest)
    
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
        throw NetworkError.failForStatusCodeChange
    }
    
    do {
        let decodedData = try JSONDecoder().decode(Dto.self, from: data) // Thread Block
        return decodedData
    } catch {
        throw NetworkError.decodingError
    }
}

async / await 를 통해 같은 기능을 하지만 가독성 명이 확실히 차이가 나는 코드가 완성 되었습니다.

  • router.makeURLRequest() - 동기 메서드 즉 쓰레드 블락이 걸립니다.

  • URLSession.shared.data(for: urlRequest) - 비동기 메서드 입니다...만
    await (기다려) 를 통해 기다리게 합니다.

  • try await 이때 try 는 각 에러를 핸들링했던 completion: @escaping
    의 역활을 수행(Error에 관하여) 하여 줍니다.

Async Await 원리가... 뭐죠?

코루틴 원리(모델) 도입

Corutine 은 함수가 1. 동작(실행) 을 2. 일시정지(suspend) 할수 있고,
3. Resume(다시 시작) 할수있게 하는 방법을 말합니다.
비동기 코드가 마치 동기 코드인것 같이 작성할수 있게 해주죠.

async

비동기 함수는 async 키워드를 사용하여 정의합니다. 즉 다시말해
해당하는 함수는 비동기 함수임을 나타내는 거죠
해당 함수는 일지정지(suspend)를 허용하며, 일시중단 될시 호출자도 일시정지 됩니다.

func fetchData(from url: URL) async throws -> Data {
    let (data, response) = try await URLSession.shared.data(from: url)
    
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }
    
    return data
}

await

async 키워드가 표시된 메서드의 반환값을 기다리게 됩니다.
Suspend Point 라고도 하며, 호출된 메서드가 결과값 혹은 thorow 하면, Resume(재게) 합니다.
비동기 함수가 정지된 상태에도 쓰레드는 멈추지 않으며 시스멤은 해당 쓰레드에 다른 작업을 예약이 가능합니다.

// 비동기 함수 호출
func performFetch() async {
    let url = URL(string: "test.com")!
    
    do {
        let data = try await fetchData(from: url)
        print("Data received: \(data)")
    } catch {
        print("Error: \(error)")
    }
}

....... 생략 ........

// 비동기 함수 호출
Task {
    await performFetch()
}

Continuation

async 메서드를 호출할때 thread의 제어권을 포기할때 suspended 현상이 발생 하는데,
다시 제어권을 돌려 받을때 어디서부터 실행할지 할수 있게 합니다.

  • Suspension (일시 중지): async 함수는 비동기 작업을 수행하는 동안 실행을 일시 중지합니다.
    이때 현재 스레드는 다른 작업을 수행할 수 있습니다.
  • Resumption (재개): 비동기 작업이 완료되면, continuation을 사용하여 중단된 지점에서 실행을 재개합니다.

1편을 마무리 지으며

1편 부터 난이도가 상당한데 라는 생각이 들었습니다.
async / await를 학습해보니 조금이나마 어떠한 동작으로 코드가 동작하고 있는지
알게 하는 편이였습니다. 다음 편에선 `Async Method 과 asyncSeqce> 에 대해서 다뤄 보도록
하겠습니다. 긴글 읽어주셔서 감사합니다.

profile
IOS 개발자 새싹이
post-custom-banner

0개의 댓글