감자주먹밥

[Swift] Swift Concurrency - async/await 본문

IOS/Swift

[Swift] Swift Concurrency - async/await

JustHm 2023. 3. 2. 17:12
728x90

WWDC 2021에서 Swift Concurrency에 대한 소개가 있다. GCD, Operation을 사용해서 비동기 작업을 수행하거나 URLSession에 complationHandler로 작업 종료 후 처리를 했는데 Swift Concurrency를 사용하면 어떤 장점이 있고, 어떻게 더 쉽고 간편하게 처리 할 수 있는지 알아보고 직접 사용해봤다.

Swift Concurrency의 장점

  • 가독성
    • completionHandler 콜백을 사용하지 않아 가독성이 좋아진다.
  • 에러 핸들링 안정성
    • 종료되는 시점에 completionHandler를 빼먹었을 때 컴파일시 문제는 없지만 에러처리가 불가능. Swift Concurrency를 사용하면 이런 에러처리를 빼먹는 실수를 방지할 수 있다.
  • 동기화 처리
    • 비동기 처리는 항상 data race를 주의해야한다. Swift Concurrency는 이런 문제를 컴파일 에러 처리를 해준다.
  • 성능
    • Swift Concurrency를 사용하면 GCD 보다 스레드 생성량이 감소하고 컨텍스트 스위칭이 감소한다.

적용하면서 알아보기

async 키워드는 함수가 Asynchronous 하다는 것을 나타낸다
await 키워드는 가능한 일시중단 시점(possible suspension point)

Asynchronous Function - async/await

func rankRequest(
    brand: BrandType,
    date: RankDateType,
    completionHandler: @escaping ([Song]) -> Void
) {
    guard let url = searchURL.rankingURL(brand: brand, date: date) else { return }
    
    AF.request(url, method: .get)
        .responseDecodable(of: [Song].self) { response in
            switch response.result {
            case .success(let data):
                completionHandler(data)
            case .failure(let error):
                print("error:" + error.localizedDescription)
                completionHandler([])
            }
        }
        .resume()
}

Swift Concurrency를 사용하지 않고 비동기 작업에 대한 처리를 했을땐 다음과 같이 completionHandler를 사용해 비동기 작업이 끝난 후 처리를 해줬다.

이 코드는 Result 타입을 사용하지 않아 Error 처리가 되어 있지도 않고 잘 될꺼라고 생각하고 만든 코드인데, 만약 에러를 다 처리해주는 코드를 작성하면 Result타입도 넣고 종료될 수 있는 시점에 모두 completionHandler를 호출해주면 많이 길어질 것이다. 

Swift Concurrency를 사용하면 이런 문제점을 해결할 수 있다.

func rankRequest(brand: BrandType, date: RankDateType) async throws -> [Song] {
    let dataTask = AF.request(url, method: .get).serializingDecodable([Song].self)
    switch await dataTask.result {
    case .success(let data):
        return data
    case .failure(let error):
        throw error
    }
}
func fetchNow(page: Int) async throws -> DummyModel {
    let (data, response) = try await URLSession.shared.data(from: url)
    guard let response = response as? HTTPURLResponse,
          response.statusCode == 200 else {
        throw DogError.invalidServerResponse
    }
    guard let data = try? JSONDecoder().decode(DummyModel.self, from: data) else {
        throw DogError.unSupportedText
    }
    return data
}

Alamofire, URLSession 둘 다 적용한 코드를 가지고 왔다. Alamofire, URLSession 둘 다 Swift Concurrency를 지원하는 함수가 있다.

공부하던 프로젝트에 Alamofire를 써서 먼저 적용해보고 URLSession도 아무 요청 URL을 가져와 시도 했는데, 코드가 간결하고 또 읽기도 편해졌다. 비동기 작업을 하는 코드인데도 에러처리와 데이터 전달 방식이 순차적이고 간결하다.

적용하는 법은 간단하다. 비동기로 동작할 함수의 반환 화살표 전에 async를 붙여준다. 에러 반환도 해 줄 일이 있다면 async 뒤에 throws를 붙여주면 된다.

func fetchAsync() async throws -> Model

async 함수는 중단 할 수 있는 특별한 함수라고 한다. 이를 사용하려면 await 키워드를 사용한다.

async로 만들어진 함수를 실행할때 사용하는 키워드다. await를 가능한 일시중단 시점(possible suspension point)으로 만들어 비동기 함수가 실행하고 완료할 때 까지 대기하는 포인트를 만드는 것.

func rankRequest(brand: BrandType) {
    Task { [weak self] in
        do {
            let songs = try await searchManager.rankRequest(brand: currentBrand, date: currentDate)
            self?.songs = songs
        }
        catch {
            print("Home-ERROR: \(error.localizedDescription)")
        }
    }
}

async 함수는 concurrent context 에서만 사용이 가능하다. 때문에 async 함수가 아닌 곳에서 실행 할 땐 Task 블럭 내에서 처리해야한다.

throws 가 있는 경우 try-catch문을 이용해 await와 함께 사용하면 된다.

do {
    let a = try await anyfunction()
    let b = await (try anyfunction())
} catch {}

위와 같이 묶어서 사용도 가능하다.

Task블럭은 concurrent context기 때문에 UI작업을 하려면 mainThread에서 따로 해줘야 한다. 이전에는 DispatchQueue.maind을 사용했지만, Swift Concurrency는 MainActor가 그 역할을 해 준다.
MainActor는 concurrency의 Actors API 중 하나로, 메인 스레드에서 돌아가는 것을 보장하는 singleton Actor다.

 Asynchronous Function in parallel - async let

private func searchRecentSongs() {
    Task { [weak self] in
        do {
            async let tj = searchManager.searchReqeust(brand: .tj,
                                                       query: currentDate,
                                                       searchType: .release)
            async let ky = searchManager.searchReqeust(brand: .kumyoung,
                                                       query: currentDate,
                                                       searchType: .release)
            let songs = try await [tj, ky]
            self?.tjRecentSongs = songs[0]
            self?.kyRecentSongs = songs[1]
            
            await MainActor.run { [weak self] in
                self?.viewController?.reloadTableView()
            }
        }
        catch {
            print("RecentSong-ERROR: \(error.localizedDescription)")
        }
    }
}

async를 하나씩 처리하는 방법 말고도 병렬로 처리 하는 방법도 있다.

async let은 concurrency binding을 하여 child task를 생성해준다. 이렇게 만들어진 task는 변수에 바인딩 되어 있다가 await를 만나고 나서 동작하게 된다. 

structured concurrency 를 생성하는 방법 중 하나다.

Asynchronous Sequence - for-await-loop

import Foundation

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

for await 를 사용해서 Asynchronous Sequence를 이용해 한번에 하나의 element를 기다려 처리할 수 있다.

이 구문은 적용할 곳이 마땅치 않아서 예제를 그냥 들고 왔다.

적용하며 나왔던 에러

Reference to captured var 'self' in concurrently-executing code

다른 async 적용 코드에선 괜찮았는데, 병렬 처리를 위해 async let을 사용해 바인딩 할 때 오류가 나왔다. 

self는 sendable 하지 않기 때문인 듯? 그냥 self 빼주면 해결 됐다.

Sendable 프로토콜은 비동기 환경에서 특정 데이터를 보내도 안전한지 점검하는 프로토콜



https://engineering.linecorp.com/ko/blog/about-swift-concurrency

 

Swift Concurrency에 대해서

안녕하세요. iOS Platform Dev 팀의 김윤재입니다. 1편에서 설명드렸던 것과 같이, 온보딩 스터디에서 공부한 내용 중 저에게는 동시성 프로그래밍(concurrency)에 관한 내용이 가장 도움이 되었습니다.

engineering.linecorp.com

https://developer.apple.com/news/?id=2o3euotz 

 

Meet Swift Concurrency - Discover - Apple Developer

Async awaits: Discover asynchronous and concurrent programming in Swift.

developer.apple.com

https://eunjin3786.tistory.com/459

 

[Swift] Concurrency

Swift Docs > Concurrency 의 내용을 요약한 글로, 더 자세한 내용은 Meet Swift Concurrency 를 참고하시길 추천드립니다. [ 요약 ] # 개념 ✔️ Asynchronous Function - 일시중단될 수 있으며 그동안 다른 비동기 함

eunjin3786.tistory.com

https://zeddios.tistory.com/1230

 

[Swift Concurrency] Async/await

안녕하세요 :) Zedd입니다. 오늘은 매우 핫한 async, await를 한번 보려고 합니다. 2021.03.25일 기준 async-await proposal은 Swift 5.5에서 구현된 상태입니다. + ) 2021.06.15 글 수정. @asyncHandler 내용 삭제. # 문제 1

zeddios.tistory.com

 

728x90

'IOS > Swift' 카테고리의 다른 글

[Swift] Swift Concurrency - Actor  (0) 2023.03.14
[Swift] Swift Concurrency - Task  (0) 2023.03.06
[Swift] 동시성 프로그래밍 GCD, Operation  (0) 2023.02.25
Xcode 단축키 모음  (2) 2022.01.13
Swift PS 할때 유용한 것  (0) 2021.09.01
Comments