Open Jinsujin opened 1 year ago
concurrency model 은 thread 위에 구축되지만, 당신은 스레드와 직접 상호작용하지 않는다.
Swift 에서의 비동기 함수는 실행중인 스레드를 포기할 수 있고, 첫번째 함수가 block 된 동안 다른 비동기 함수가 해당 스레드에서 실행할 수 있다.
비동기 함수가 다시 시작될때(resume), Swift 는 해당 함수가 실행될 스레드에 대해 보증하지 않는다.
아래의 코드는 photo name 리스트를 다운로드 하고, list 의 첫번째 사진을 다운로드 한 다음 사용자에게 해당 사진을 표시한다. completion handler 의 중첩은 복잡한 코드를 야기하여, 깊은 중첩이 있는 경우 다루기 어렵게 된다.
listPhotos(inGallery: "Summer Vacation") { photoNames in
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
downloadPhoto(named: name) { photo in
show(photo)
}
}
이제 async/await 을 사용해 위의 코드를 개선해 보자.
await
: 실행을 미룰 수 있는(suspend) 지점을 표시하는 키워드. return 될때까지 해당 함수의 실행을 연기(try를 사용하는 방식과 유사)async
: 함수나 메서드가 비동기라는 것을 표시하는 키워드func listPhotos(inGallery name: String) async -> [String] {
let result = // ... some asynchronous networking code ...
return result
}
아래의 코드는 갤러리안에 모든 사진의 이름을 가져온 다음 첫번째 이미지를 보여준다:
let photoNames = await listPhotos(inGallery: "Summer Vacation")
let sortedNames = photoNames.sorted()
let name = sortedNames[0]
let photo = await downloadPhoto(named: name)
show(photo)
비동기 메서드 내부에서, 실행의 흐름은 당신이 다른 비동기 메서드를 호출할때만 연기(suspend)된다. —연기는 절대 암시적(implicit)이거나 선제적(preemptive)이지 않다—
이는 모든 가능한 연기할수있는 지점(suspension point)은 await
로 표시된다는 것을 의미한다.
WWDC21
Meet async/await in Swiftdata(for: request)
함수를 실행한다await
System → resume
func fetchThumbnail(for id: String) **async** throws -> UIImage {
let request = thumbnailURLRequest(for: id)
let (data, response) = try **await** URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badID }
let maybeImage = UIImage(data: data)
guard let thumbnail = **await** maybeImage?.thumbnail else { throw FetchError.badImage }
return thumbnail
}
Async sequences
for await id in staticImageIDsURL.lines {
let thumbnail = await fetchThumbnail(for: id)
collage.add(thumbnail)
}
let result = await collage.draw()
Async properties
extension UIImage {
var thumbnail: UIImage? {
get async {
let size = CGSize(width: 40, height: 40)
return await self.byPreparingThumbnail(ofSize: size)
}
}
}
Testing using XCTestExpectation
class MockViewModelSpec: XCTestCase {
func testFetchThumbnails() throws {
let expectation = XCTestExpectation(description: "mock thumbnails completion")
self.mockViewModel.fetchThumbnail(for: mockID) { result, error in
XCTAssertNil(error)
expectation.fulfill()
}
wait(for: [expectation], timeout: 5.0)
}
}
Testing using async/await
class MockViewModelSpec: XCTestCase {
func testFetchThumbnails() async throws {
XCTAssertNoThrow(try await self.mockViewModel.fetchThumbnail(for: mockID))
}
}
일반적인 동기함수를 호출하면, 스레드는 block 되고 그 함수가 끝나기를 기다린다.
fetchThumbnail
함수가 preparingThumbnail
--UIKit 이 제공하는 동기함수—을 호출한다면, 그 작업이 끝날때까지 해당 스레드는 다른작업을 할 수 없다.
일반적인 함수와 달리 prepareThumbnail(of:completionHandler:)
--해당 함수의 비동기 버전-- 을 호출한다면, 실행되는 동안 해당 스레드는 다른 작업을 할 수 있다.
작업이 끝나면, completion handler 를 호출함으로써 완료를 당신에게 알려줄 것이다. SDK 는 많은 비동기 함수를 제공한다.
완료했음을 알려주는 다양한 방법들이 있다: 일부는 이처럼 completion handler 를 사용하고 다른방법으로는 delegate callback 이 있다. 그리고 많은 것들이 async 로 표시되어 단지 값을 반환한다.
참고
WWDC21
Meet async/await in Swift클로저로 비동기 작업을 처리할때 문제점
{}
의 사용으로 가독성이 떨어지는 코드