해당 글은 ShopY( Swift UI Mini Project ) 에 처음으로 도입한
Swift Concurrency 를 사용해 가며, 이해가 덜가는 부분들이 많아
작성하게 된 글입니다. 총 4편(예정) 으로 이루어질 장편 블로그 글입니다.
https://youtu.be/0TD96VTf0Xs 1:37:30 부터 쭉
WWDC 에서 나온 Swift Concurrency의 핵심은 아래와 같습니다.
"동시성
코드를Simple
하게 "
" 빠르고 현대적이며 안전하게 "
그리고 이어서 설명중 위 사진과 같은 코드가 나옵니다.
actor
async
await
그리고 이번 글에는async
와await
를 다루어 보려고 합니다.
APPLE 은 말합니다.
" 개발자님들 동시성 처리가 힘드셨죠? 이젠 Simple 하게 해드릴꼐요~! "
어느 부분의 동시성 처리가 힘들었다고 말하였던 것일까요?
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
을 통해 함수가 종료될수 있도록 하게 하여야 합니다.
그렇게 된다면 코드는 길어지고, 가독성 마저 떨어지게 되겠죠.
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에 관하여) 하여 줍니다.
Corutine
은 함수가 1.동작(실행)
을 2.일시정지(suspend)
할수 있고,
3.Resume(다시 시작)
할수있게 하는 방법을 말합니다.
비동기 코드가 마치 동기 코드인것 같이 작성할수 있게 해주죠.
비동기 함수는
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
}
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()
}
async 메서드를 호출할때 thread의 제어권을 포기할때 suspended 현상이 발생 하는데,
다시 제어권을 돌려 받을때 어디서부터 실행할지 할수 있게 합니다.
1편 부터 난이도가 상당한데 라는 생각이 들었습니다.
async / await를 학습해보니 조금이나마 어떠한 동작으로 코드가 동작하고 있는지
알게 하는 편이였습니다. 다음 편에선 `Async Method 과 asyncSeqce> 에 대해서 다뤄 보도록
하겠습니다. 긴글 읽어주셔서 감사합니다.