Jinsujin / translate-documents

iOS 연관 문서 번역
0 stars 0 forks source link

async/await #6

Open Jinsujin opened 1 year ago

Jinsujin commented 1 year ago

참고

클로저로 비동기 작업을 처리할때 문제점

  1. 단일 함수를 실행할때는 괜찮지만, 여러개의 콜백을 받아오는 처리를 해야할때 depth 가 깊어져 가독성이 떨어진다는 문제점이 있다.
  2. 에러를 빠뜨릴 수 있다
  3. 중첩과 블럭{} 의 사용으로 가독성이 떨어지는 코드
Jinsujin commented 1 year ago

Concurrency

Note: Swift 의 thread 사용

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 을 사용해 위의 코드를 개선해 보자.

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)
실행순서 자세히보기 1. 코드의 실행은 첫번째 라인에서부터 시작해서 첫 `await` 까지 실행한다. `listPhotos(inGallery:)` 함수가 호출되고 해당 함수가 return 할때까지 실행이 연기된다. 2. 이 코드의 실행이 연기(suspend)되는 동안, 같은 프로그램에 있는 다른 concurrent 코드가 실행된다. 예를들어, long-running background task 이 사진 갤러리의 리스트를 계속 업데이트 할 수 있다. 이 코드는 await 으로 표시된 다음 연기 지점(suspension point)이나, 완료될때까지 실행된다. 3. listPhotos(inGallery:) 가 return 된 후에, 이 코드는 해당 지점부터 실행을 계속한다. 반환되어진 값이 photoNames 에 할당된다. 4. 라인에 정의된 sortedNames 과 name 은 일반적인 동기코드다. 해당 라인에 await 표시가 없기때문에, 가능한 연기 지점(suspension point)이 없다. 5. 다음 `await` 표시는 `downloadPhoto(named:)` 함수 호출이다. 이 코드는 함수가 return 할때까지 다시 실행을 일시정지해서, 다른 concurrent code 를 실행할 수 있는 기회를 준다. 6. `downloadPhoto(named:)` return 후에, 해당 반환 값은 photo 에 할당되어진 다음, `show(_:)` 이 호출되면 argument 로 전달되어 진다.

비동기 메서드 내부에서, 실행의 흐름은 당신이 다른 비동기 메서드를 호출할때만 연기(suspend)된다. —연기는 절대 암시적(implicit)이거나 선제적(preemptive)이지 않다—

이는 모든 가능한 연기할수있는 지점(suspension point)은 await 로 표시된다는 것을 의미한다.

Jinsujin commented 1 year ago

WWDC21 Meet async/await in Swift

스크린샷 2023-01-30 오후 5 58 44
  1. data(for: request) 함수를 실행한다
  2. await
    • 스레드의 제어권을 System 에 넘긴다.
    • 비동기 함수는 suspend 될 수 있다. 그럴때 스레드의 제어를 포기한다.
    • Suspending 은 시스템에게 “할일이 많다는 것을 알고있어요. 당신이 가장 중요한 것이 무엇인지 결정하세요.” 라고 말해주는 것이다.
    • 함수가 연기되면 시스템은 스레드를 사용해 다른 작업을 자유롭게 수행할 수 있다.
  3. 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))
    }
}
Jinsujin commented 1 year ago

일반적인 함수와 비동기함수의 차이

스크린샷 2023-01-30 오후 5 49 06

일반적인 함수

일반적인 동기함수를 호출하면, 스레드는 block 되고 그 함수가 끝나기를 기다린다. fetchThumbnail 함수가 preparingThumbnail--UIKit 이 제공하는 동기함수—을 호출한다면, 그 작업이 끝날때까지 해당 스레드는 다른작업을 할 수 없다.

비동기 함수

일반적인 함수와 달리 prepareThumbnail(of:completionHandler:) --해당 함수의 비동기 버전-- 을 호출한다면, 실행되는 동안 해당 스레드는 다른 작업을 할 수 있다.

작업이 끝나면, completion handler 를 호출함으로써 완료를 당신에게 알려줄 것이다. SDK 는 많은 비동기 함수를 제공한다.

완료했음을 알려주는 다양한 방법들이 있다: 일부는 이처럼 completion handler 를 사용하고 다른방법으로는 delegate callback 이 있다. 그리고 많은 것들이 async 로 표시되어 단지 값을 반환한다.