- Swift concurrency의 특징
- linear
- concise
- Native error handling
- 네트워킹은 그 자체가 비동기기 때문에, 이 특성을 잘 살릴 수 있다.
- 기존 코드를 보면서 문제점을 확인해보자.
- 복잡한 제어 흐름(아래로 갔다가 다시 위로 가야 됨)
- 스레드 스위칭을 수동으로 신경써야 한다. → 데이터 경합 등의 스레드 이슈가 나올 수 있다.
func fetchPhoto(url: URL, completionHandler: @escaping (UIImage?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completionHandler(nil, error) // 아래에서는 DispatchQueue.main에 넣고 있는데, 여기서는 안 넣고 있다.
// return을 안하고 있다.
}
if let data = data, let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode = 200 {
DispatchQueue.main.async {
completionHandler(UIImage(data: data), nil) // UIImage가 nil일 수 있다.
}
} else {
completionHandler(nil, DogsError.invalidServerResponse
}
}
task.resume()
}
func fetchPhoto(url: URL) async throws -> UIImage {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw DogsError.invalidServerResponse
}
guard let image = UIImage(data: data) else {
throw DogsError.unsupportedImage
}
return image
}
func data(from url: URL) async throws -> (Data, URLResponse)
func data(from request: URLRequest) async throws -> (Data, URLResponse)
func upload(for request: URLRequest, from data: Data) async throws -> (Data, URLResponse)
func upload(for request: URLRequest, fromFile url: URL) async throws -> (Data, URLResponse)
// 다운로드 받은 파일을 자동으로 지우지 않는다. 수동으로 지워야 한다.
func download(from url: URL) async throws -> (URL, URLResponse)
func download(for request: URLRequest) async throws -> (URL, URLResponse)
func download(resumeFrom resumeData: Data) async throws -> (URL, URLResponse)
- 명시적인 핸들이 제공되지 않지만, async 호출을 통해서 handle을 얻어서 cancel할 수 있다.
let handle = async {
let (data1, response1) = try await URLSession.shared.data(from: url1)
// ...
let (data2, response2) = try await URLSession.shared.data(from: url2)
// ...
}
func bytes(from url: URL) async throws -> (URLSession.AsyncBytes, URLResponse)
func bytes(for request: URLRequest) async throws -> (URLSession.AsyncBytes, URLResponse)
struct AsyncBytes: AsyncSequence {
typealias Element = UInt8
}
// 예제
func onAppearHandler() async throws {
let (bytes, response) = try await URLSession.shared.bytes(from: Self.eventStreamURL)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw DogsError.invalidServerResponse
}
for try await line in bytes.lines {
let photoMetadata = try JSONDecoder().decode(PhotoMetadata.self, from: Data(line.utf8))
await updateFavoriteCount(with: photoMetadata)
}
}
- task delegate는 어떻게 설정하지?
- 각 메소드에서 추가 인자로 task delegate를 받음
- session delegate와 task delegate가 둘 다 있으면 task가 우선적으로 실행