WWDC

WWDC| Get started with Swift concurrency

ziziDev 2024. 7. 17. 14:36
반응형

Meet async/await in Swift

 

 

Swift Async/await 기능으로 비동기 코드를 쉽게 작성할 수 있습니다

 

 

completionHandler / delegate pattern 등 여러가지 방법으로 비동기 코드를 작성할 수 있습니다

 

 

첫 번째 스레드는 sync(동기)

 

두 번째 스레드는 async(비동기)

함수를 호출하면 작업을 시작한 스레드의 차단을 빠르게 해제하게됩니다.

그래서 스레드는 오래 실행되는 작업이 완료되는 동안 다른 작업을 수행 하게됩니다

그러다 수행하던 작업이 완료되면 completionHandler를 호출해서 다음 작업을 진행하게 됩니다

 

 

각 행에 서버에 저장된 이미지의 썸네일이 표시되는 항목 목록입니다

이러한 과정에 대해서 설명하고자 합니다

 

 

순서

1.  thumbnailURLRequest 메서드가 이미지를 요청하기 위해서 URLRequest를 생성합니다. : 시간 소요 ⬇️

2.  URLSession의 dataTask 메서드가 해당 요청에 대한 데이터를 가져옵니다. : 시간 소요 🆙

3.  UIImage init(data:)가 해당 데이터에서 이미지를 생성합니다. : 시간 소요 ⬇️

4. UIImage의 prepareThumbnail 메서드가 원본 이미지에서 썸네일을 렌더링합니다. : 시간 소요 🆙

 

 

1,3 작업은 빠르게 처리가 됩니다

API를 요청할 수 있는지와 이미지를 만드는 단계는 시간이 얼마 걸리지 않습니다

 

 

다만 2,4의 경우는 시간이 걸릴 수 있습니다

2번의 경우에 데이터 용량이 크다면 오래걸릴 수 있고 여러개의 이미지 데이터라고 했을 때 시간이 더 필요할 수 있습니다

이미지를 다운로드하는 동안 다른 작업을 수행하고 싶다면 비동기 코드를 반드시 작성해야만 합니다

 

 

 

3, 4에서도 예외처리를 해야합니다

 

func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -> Void) {
	//1
    let request = thumbnailURLRequest(for: id)
    //2
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            completion(nil, error)
        } else if (response as? HTTPURLResponse)?.statusCode != 200 {
            completion(nil, FetchError.badID)
        } else {
        //3
            guard let image = UIImage(data: data!) else {
                completion(nil, FetchError.badImage)
                return
            }
            //4
            image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
                guard let thumbnail = thumbnail else {
                    completion(nil, FetchError.badImage)
                    return
                }
                completion(thumbnail, nil)
            }
        }
    }
    task.resume()
}

 

이미지를 가지고 오기위해서 예외맞는 completion을 호출하는걸 알 수 있습니다

단계별로 오류 발생 유무를 확인하며 guard let 구문으로 처리해주고 prepareThumbnail 메서드에서도 오류 유무를

확인하고 있습니다

 

위의 작업을 좀 더 안전하게 만들 수 있는 방법이 있습니다.

예를 들어 표준 라이브러리의 결과 유형을 사용할 수도 있습니다.

이 방법은 조금 더 안전하지만, 관례가 추가되어 코드가 약간 길어집니다.

사람들은 다른 방법으로 비동기 코드를 개선하려고 했지만 이러한 접근 방식 중 어느 것도 간단하고 쉽고 안전한 코드를 제공하지 못합니다.

하지만 비동기/대기를 사용하면 더 나은 결과를 얻을 수 있습니다

 

 

아규머트 옆 메서드의선언부인  async 키워드가 있습니다

그리고 에러처리를 하기위해서 throws 키워드를 볼 수 있습니다

async/await 키워드를 사용하면 간단하게 작성할 수 있습니다

 

fetchThumbnailURLRequest메서드는 sync로 다른 작업을 하지 못합니다

다음 줄 부턴 URLSession에서 data(for: request)를 호출하여 데이터 다운로드를 시작하는데 await 키워드는 이 작업을 진행하는 동안 스레드 점유를 해제하게되고 다른 작업들을 진행할 수 있게 됩니다

async함수에서 특정 작업이 수행되는것을 기다리고 싶다면 await 키워드를 사용하면 됩니다

 

그리고 메서드에 throws가 있고 

반드시 비동기로 사용할 때는 await 앞에 반드시 try 키워드를 사용해야합니다

 

20줄에서 6줄로 간략하게 줄어든것을 볼 수 있습니다

쉽게 처리할 수 있고 에러 방지도 가능하게 됩니다

 

함수 호출이 아니더라도 await를 사용할 수 있습니다

 

 

UIImage 읽기 속성인 thumbnail에 getter async를 볼 수 있습니다 그래서 await를 사용할 수 있고 또한 Swift 5.5부터 getter에도

throw를 사용해서 에러 처리를 할 수 있습니다 

그리고 ⭐️read only 프로퍼티에만 async를 사용⭐️할 수 있습니다

 

반면에 잘못된 사용에 대해서 알아보고자 합니다

스레드 사용을 일시 중단할 위치를 나타내기 위해서 await를 사용하면 안됩니다

 


for 루프 구문에서 async sequence를 사용할 수 있습니다(✏️only for loop) 

async sequence는 비동기적으로  id(item) 제공한다는 점을 제외하고 일반적인 sequence와 비슷합니다

 

여러 값들에 대한 비동기 작업을 진행할 수 있습니다  

 

 

async함수에 await 키워드를 만났을 때의 흐름에 대해서 알아보고자 합니다 먼저 일반 함수인 경우 스레드도 해당 함수에 넘겨주게 됩니다

스레드는 작업이 끝날 때 까지 가지고 있고 함수가 종료되기 위해 정상적 반환 or 에러를 발생하게 되겠고 종료되면 스레드를 다시 호출해서

호출자에게 넘겨줍니다

함수가 스레드를 반환할 수 있는 경우는 함수가 종료되는 경우(return / error)

 

 

비동기 함수는 스레드의 정지를 통해서 스레드를 반환하고 비동기 함수도 호출 될 때 스레드에 대한 제어권을 받게됩니다

그러다가 await키워드를 만나면 스레드(일꾼)을 반환합니다

호출자는 스레드를 반환하는게 아니라 시스템에 반환하게 되기 때문에 시스템은 해당 스레드를 활용해서 다른 작업들을 수행하고 어느 시점이 되었을 때 시스템은 비동기 함수에 스레드 제어권을 주게됩니다

비동기 함수의 실행이 끝나고 에러나 결과를 반환하게 되고 스레드 권한을 호출자에게 다시 넘기게 됩니다

 

반면에 전혀 일시 중단할 필요가 없을 수도 있습니다. 비동기 함수는 일시 중단될 수 있지만, 비동기라고 표시되어 있다고 해서 반드시 일시 중단되는 것은 아닙니다. 마찬가지로 '대기 중'이라고 표시되었다고 해서 함수가 반드시 거기서 일시 중단된다는 의미는 아닙니다. 하지만 결국 일시 중단되지 않든, 마지막으로 재개된 후든 함수는 완료되어 값 또는 오류와 함께 스레드 제어권을 함수에 다시 넘겨줍니다.

 

 

일시 중단될 때 어떤 일이 발생할 수 있는지 fetchThumbnail을 다시 한 번 살펴보겠습니다.

 

 

URLSession.Shared.data(for:) 메서드를 호출하면 스레드를 일시정지할 수 있습니다

스레드를 시스템에 반환을 하게되고 시스템은 URLSession.shared.data 메서드에 대한 작업을 예약고

빨리 끝나면 바로 시작될 수 있고 아니라면 다른 작업을 할 수도 있습니다

해당 작업이 예약되어있으면 likeCurrentPost()메서드를 호출하고

 

 

업로드하는 작업을 수행하는데 시스템에 반환된 스레드로 해당 작업을 수행할 수 있습니다

 

 

데이터가 다운로드를 끝나게 되면 호출자로 돌아가서 다음작업을 수행하게 됩니다

 

 

이렇게 일시정지된 상태에서 작업들이 수행될 수 있기 때문에 앱 상태가 일시정지 중에 변할 수 있는걸 유의해야하는데 async/await 블록은 하나의 연결로인해서 실행되지 않는걸 알 수 있지만 반환한 스레드가 아니라 다른 스레드에서 실행되고 있는겁니다 이는 공유자원이 보호될 수 없는걸 의미하고 actor를 통해서 해결할 수 있다고 합니다

actor는 기존 사용하는 비동기에 캡슐화를 하는것이라고 생각하면 됩니다

 

 

async / await facts

1. 메서드를 sync로 작성하게 된다면 일시정지를 할 수 있고 자신이 호출한 호출자에게도 일시정지를 할 수  있기 때문에 호출자도 async로 작성해야합니다

2. 비동기 함수에서 일시정지될 수 있는걸 알리기 위해서 await 키워드를 사용합니다

3. 비동기 함수가 일시정지 되는 동안 스레드는 시스템에 반환되어서 다른 작업을 할 수 있지만 앱 상태가 원하지 않게 변할 수 있습니다

4. 비동기 함수가 다시 시작되면 호출한 비동기 함수가 반환 결과가 호출자로 전달되고 중단된 곳 await 키워드 다음 부분부터 다시 실행

 

 

비동기 fetchThumbnail 함수 호출의 결과를 기다리는 것만큼 쉽게 테스트하기

Testing async code

 

 

swift에서는 테스트 코드를 위해서 XCTest 프레임워크를 지원하고 있습니다

비동기 코드를 테스트하는 것이 동기 코드를 테스트하는 것만큼이나 쉬워지기를 원했기 때문에 XCTest는 기본적으로 비동기를 지원합니다. 기대값을 설정하고, 테스트 중인 API를 호출하고, 기대값을 이행한 다음 임의의 시간 동안 대기해야 하는 지루한 프로세스를 테스트 함수에 비동기 키워드를 추가하고, XCTest 기대값과 그 이행, 명시적 대기를 제거하고, 대신 앞서 보여드린 새로운 비동기 fetchThumbnail 함수 호출의 결과를 기다리는 것만큼 쉽게 테스트할 수 있게 되었습니다.

 

이제 테스트가 완료되었으므로 애플리케이션 코드 자체를 확대해 보겠습니다

 

 

async/await를 적용해서 테스트를 작성하게되면 훨씬 짧고 직관적으로 코드를 수정할 수 있고

작업이 끝나면 Assertion을 처리하는 방식으로 변경했습니다

 

 

 

Swift UI

 

SwiftUI 코드에서 비동기적으로 id를 가지고 있고 thumbnail을 가지고 오는 코드 입니다

async/await로 수정을 해볼까요?

 

 

 

completeHandler를 제거하고 try?.await를 사용해서 오류처리와 비동기처리를 하고 있습니다

 

 

Swift Compiler는 async context가 아닌 곳에서 비동기 함수를 호출할 수 없기 때문에

Task를 통해서 해당 부분을 감싸줘야합니다

 

 

async가 아닌곳에서도 비동기 함수를 호출 할 수 있습니다

 

 

스위프트 병렬성 채택은 complete hander로 통해서 비동기 작업을 했지만 async/await로 한층 더 리팩토링하여

간결하게 만들 수 있습니다

 

 

기존의 completion Handler인 비동기 작업을 제공하는것 대신 

async/await를 사용해서 아래와 같이 변경할 수 있습니다

 

 

직관적/간단함/깔끔

 

 

 

 

위 설명과 동일하게 completion Handler대신 try? await를 사용해서 간결하게 나타내고 있습니다

 

// 기존 함수
func getPersistentPosts(completion: @escaping ([Post], Error?) -> Void) {
    // 로컬 데이터베이스에서 게시물 데이터를 비동기적으로 가져와 completion 핸들러를 통해 결과를 전달합니다.
    
    do {
        // 게시물 데이터를 요청하는 NSFetchRequest 객체를 생성합니다.
        let req = Post.fetchRequest()
        
        // 데이터를 날짜 오름차순으로 정렬하도록 설정합니다.
        req.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
        
        // 비동기 작업을 수행하는 NSAsynchronousFetchRequest 객체를 생성합니다.
        let asyncRequest = NSAsynchronousFetchRequest<Post>(fetchRequest: req) { result in
            // 비동기 작업이 완료되면 completion 핸들러를 호출합니다.
            
            if let error = result.error {
                // 에러가 발생하면 에러를 completion 핸들러에 전달합니다.
                completion([], error)
            } else {
                // 에러가 없으면 결과 데이터를 completion 핸들러에 전달합니다.
                completion(result.finalResult ?? [], nil)
            }
        }
        
        // 비동기 작업을 실행합니다.
        try self.managedObjectContext.execute(asyncRequest)
    } catch {
        // 작업 중 에러가 발생하면 에러를 completion 핸들러에 전달합니다.
        completion([], error)
    }
}

 

 

persistentPosts()메서드를 이용해서 게시물 데이터 [post]를 반환하고 있습니다

작업중에 에러가 발생하게되면 throws를 통해서 에러를 반환하게 됩니다

 

 

 

 

self.getPersistentPosts {} 는 에러 유무를 나눠서 작성할 수 있습니다

 

 

await를 통해서 coredata에 전달하고 coredata에서 전달받아서 사용도 가능합니다

 

withCheckedThrowingcontinuation블록을 사용해서 비동기 작업을 수행하는데  continution 객체를 통해서 반환이 됩니다

try await 키워드로 비동기 작업을 기다리고 작업중에 오류가 발생하면 throws를 합니다

 

 

error가 존재하는 경우에 에러를 던져주고 에러가없는경우에 데이터를 반환하게 됩니다

 

 

오류 처리를 하고 싶지 않다면? withCheckContinuation를 사용하면 됩니다

일시정지된 async 함수를 다시시작하는데 사용할 수 있는 continuation value에 접근합니다

확실하게 딱 한번만 작성해야합니다

여러번 전달할 수 없습니다

만약 호출하게된다면 fatal error가 발생하게 됩니다

 

 

class ViewController: UIViewController {

  // 현재 진행 중인 작업을 나타내는 컨티뉴에이션 객체 (CheckedContinuation)
  private var activeContinuation: CheckedContinuation<[Post], Error>?

  // 피어 관리자로부터 공유 게시물 데이터를 비동기적으로 가져옵니다.
  func sharedPostsFromPeer() async throws -> [Post] {
    try await withCheckedThrowingContinuation { continuation in
      // 현재 진행 중인 작업을 나타내는 컨티뉴에이션 객체를 저장합니다.
      self.activeContinuation = continuation

      // 피어 관리자에게 공유 게시물 데이터 동기화를 요청합니다.
      self.peerManager.syncSharedPosts()
    }
  }
}

extension ViewController: PeerSyncDelegate {
  // 피어 관리자로부터 공유 게시물 데이터를 받았을 때 호출됩니다.
  func peerManager(_ manager: PeerManager, received posts: [Post]) {
    // 컨티뉴에이션 객체가 유효한 경우 결과 데이터를 전달합니다.
    self.activeContinuation?.resume(returning: posts)

    // 컨티뉴에이션 객체를 nil로 설정하여 중복 호출을 방지합니다.
    self.activeContinuation = nil
  }

  // 피어 관리자와 동기화中に 에러가 발생했을 때 호출됩니다.
  func peerManager(_ manager: PeerManager, hadError error: Error) {
    // 컨티뉴에이션 객체가 유효한 경우 에러를 throws 합니다.
    self.activeContinuation?.resume(throwing: error)

    // 컨티뉴에이션 객체를 nil로 설정하여 중복 호출을 방지합니다.
    self.activeContinuation = nil
  }
}

 

 

 

 

더보기

 

♪ ♪ 안녕하세요, 저는 Apple의 Swift 팀 엔지니어인 Nate입니다. 오늘은 제 동료 Robert와 함께 Swift의 비동기/대기에 대해 이야기해 보겠습니다. 비동기 프로그래밍은 많은 분들에게 익숙한 작업입니다. 따라서 장황하고 복잡하며 심지어 부정확한 비동기 코드를 작성하는 것이 너무 어렵지는 않다는 것을 알고 계실 것입니다. Swift의 비동기/await 기능이 도움이 될 수 있습니다. 이 기능을 사용하면 일반 코드를 작성하는 것처럼 쉽게 비동기 코드를 작성할 수 있습니다. 그렇게 하면 코드에 아이디어를 더 잘 반영할 수 있습니다. 더 안전해집니다. 또한 SDK에는 수백 가지의 대기 메서드를 사용할 수 있습니다. 예를 들어 UIKit은 UIImage에서 썸네일을 생성하는 기능을 제공합니다. 실제로 이 작업을 완료하기 위해 동기 및 비동기 함수를 모두 제공합니다.

 


간단히 말해, 동기식 함수, 즉 일반적인 오래된 함수 호출 시 스레드는 해당 함수가 완료될 때까지 기다리면서 중지됩니다. 따라서 fetchThumbnail 함수가 UIKit에서 제공하는 동기식 함수인 prepareThumbnail을 호출하면 완료될 때까지 스레드는 다른 작업을 수행할 수 없습니다. 반대로, 해당 함수가 실행되는 동안 비동기 버전인 준비 썸네일(of:completionHandler:)을 호출하면 스레드는 다른 작업을 자유롭게 수행할 수 있습니다. 작업이 완료되면 완료 핸들러를 호출하여 알려줍니다. SDK는 많은 비동기 함수를 제공합니다. 이러한 함수는 몇 가지 다른 방식으로 완료되었음을 알려줍니다. 일부는 이와 같은 완료 핸들러를 사용합니다. 다른 것들은 델리게이트 콜백에 의존합니다. 그리고 많은 함수는 비동기로 표시되어 있으며 값만 반환합니다.

이러한 비동기 함수의 공통점은 함수를 호출하면 작업을 시작한 스레드의 차단을 빠르게 해제한다는 점입니다. 따라서 스레드는 오래 실행되는 작업이 완료되는 동안 다른 작업을 수행할 수 있습니다. 그 차이를 알아보기 위해 많은 분들에게 친숙한 예시를 살펴보겠습니다.

 

로버트와 제가 함께 만들고 있는 앱에는 각 행에 서버에 저장된 이미지의 썸네일이 표시되는 항목 목록이 있습니다.
해당 목록에 표시할 썸네일을 준비할 때가 되면 뷰 모델에서 fetchThumbnail 메서드가 호출됩니다.이 메서드는 일련의 단계를 통해 문자열을 UIImage로 변환합니다. 
먼저 뷰 모델의 thumbnailURLRequest 메서드가 문자열에서 URLRequest를 생성합니다. 다음으로 URLSession의 dataTask 메서드가 해당 요청에 대한 데이터를 가져옵니다. 그런 다음 UIImage initWithData가 해당 데이터에서 이미지를 생성하고 마지막으로 UIImage의 prepareThumbnail 메서드가 원본 이미지에서 썸네일을 렌더링합니다. 이러한 각 작업은 이전 작업의 결과에 따라 달라집니다. 즉, 순서대로 수행해야 합니다.

 

 

이러한 연산 중 일부는 값을 빠르게 반환하므로(문자열에서 URLRequest를 구성하는 것과 데이터에서 UIImage를 구성하는 것 모두 이와 유사합니다) 함수가 어떤 스레드에서 실행되든 동기 호출 방식으로 실행해도 괜찮습니다.
그러나 일부 함수는 시간이 소요됩니다.

로버트와 제가 비동기/대기 함수를 사용하기 전에는 완료 핸들러를 사용하여 함수를 작성했습니다.

 

이 함수는 인자로 문자열, 첫 번째 연산에 대한 입력 및 호출자에게 출력을 반환하는 데 사용되는 완료 핸들러를 받습니다.
fetchThumbnail이 호출되면 먼저 thumbnailURLRequest를 호출합니다.이 메서드는 동기식이기 때문에 완료 핸들러가 필요하지 않습니다.다음으로 공유 URLSession 인스턴스에서 데이터 태스크를 호출하여 해당 URLRequest와 완료 핸들러를 전달합니다.비동기 작업을 시작하기 위해 다시 시작해야 하는 URLSessionDataTask를 동기적으로 생성합니다.그런 다음 FetchThumbnail이 반환되고 스레드는 다른 작업을 자유롭게 수행할 수 있습니다. 
이미지 다운로드에는 시간이 걸리고 데이터 스트리밍을 기다리는 스레드를 차단하고 싶지 않기 때문에 이는 매우 중요합니다. 결국 이미지 다운로드가 완료되거나 문제가 발생합니다. 어느 쪽이든 요청이 완료되면 데이터, 응답, 오류 등 몇 가지 선택적 값과 함께 데이터태스크에 전달된 완료 핸들러가 호출됩니다.

문제가 발생하면 완료 핸들러를 호출하고 오류를 전달해야 합니다.
모든 것이 잘 되었다면 UIImage의 initWithData를 사용하여 데이터에서 이미지를 생성합니다.동기식이기 때문에 결과를 처리하기 위해 일반적인 직선 코드를 작성할 수 있습니다.이미지가 생성되지 않으면 완료됩니다.이미지가 생성된 경우 마지막으로 UIKit의 prepareThumbnail 메서드를 호출하고 완료 핸들러를 전달합니다.작업이 완료되면 스레드는 차단이 해제되고 다른 작업을 수행할 수 있게 됩니다.

썸네일이 준비된 후 썸네일 준비가 성공하면 이미지와 함께 해당 완료 핸들러가 호출되고, 그렇지 않으면 무효화됩니다.성공하면 완료 핸들러를 호출하고 이미지를 전달합니다.

하지만 Robert가 지적한 것처럼 문제가 있습니다. FetchThumbnail의 호출자는 실패하더라도 작업이 완료되면 알림을 받기를 기대합니다. 그런데 지금은 호출자를 혼란에 빠뜨리고 있습니다. "guard else return"을 작성하는 데 너무 익숙해져서 완료 핸들러를 두 번 호출하는 것을 잊어버렸습니다. 따라서 데이터에서 UIImage를 만들거나 썸네일을 준비하는 데 실패하면 fetchThumbnail의 호출자에게 알림이 표시되지 않고 행이 업데이트되지 않습니다. 그냥 영원히 회전 이미지만 표시될 뿐입니다.

 

그렇기 때문에 fetchThumbnail의 작성자인 저희는 어떤 일이 발생하더라도 호출자에게 알리는 것이 매우 중요합니다.

 

따라서 함수를 통과하는 모든 경로에서 이를 알려야 합니다. 그러기 위해서는 오류가 발생하면 완료 핸들러를 호출하고 오류를 전달해야 합니다. 일반적인 함수는 에러를 발생시키면 호출자에게 에러를 반환합니다. 그리고 Swift는 함수를 통해 실행이 어떻게 진행되든 값이 반환되지 않으면 에러가 발생하도록 보장합니다. 하지만 여기서는 Swift의 일반적인 에러 처리 메커니즘을 사용할 수 없습니다. 문제가 발생하면 이러한 완료 핸들러 내에서 오류를 던질 수 없습니다. 이는 불행한 일입니다. Swift가 우리의 작업을 확인할 수 없다는 뜻이기 때문입니다. Swift에게 fetchThumbnails와 같은 완료 핸들러는 단지 클로저일 뿐입니다. 우리는 항상 호출되는지 확인하고 싶지만, Swift에서는 이를 강제할 방법이 없습니다. 그래서 방금 두 가드에서 돌아왔을 때 컴파일 오류가 발생하지 않았습니다. Robert가 문제가 있다고 지적해줘야 문제를 해결할 수 있었죠. 따라서 완성 핸들러가 결국 호출되도록 하는 것은 여러분의 몫입니다. 우리 둘이 이 함수를 작성하기 위해 앉았을 때, 우리는 몇 가지 연산을 차례로 수행하려고 했습니다. 두 개는 동기식, 두 개는 비동기식으로 완료 핸들러를 사용했습니다. 성공했지만, 결국 미묘한 버그가 발생할 수 있는 5개의 기회가 포함된 약 20줄의 코드를 작성하게 되었습니다. 우리가 원했던 것은 이 네 가지 연산을 순서대로 수행하는 것이었지만, 우리가 얻은 코드는 따라하기 어렵고 올바르게 수행하기 어려우며 의도를 모호하게 만들었습니다.

 

이 작업을 좀 더 안전하게 만들 수 있는 방법이 있습니다. 예를 들어 표준 라이브러리의 결과 유형을 사용할 수도 있습니다. 이 방법은 조금 더 안전하지만, 관례가 추가되어 코드가 더 추악해지고 약간 길어집니다. 사람들은 다른 방법으로 비동기 코드를 개선하기 위해 퓨처와 같은 기술을 사용하기도 했습니다. 하지만 이러한 접근 방식 중 어느 것도 간단하고 쉽고 안전한 코드를 제공하지 못합니다. 비동기/대기를 사용하면 더 나은 결과를 얻을 수 있습니다. Robert와 저는 이 네 단계를 수행하는 함수를 다시 작성했습니다. 이번에는 async/await을 사용했습니다.

이 함수는 여전히 문자열을 인수로 받습니다. 하지만 지난번에는 완료 핸들러도 전달했지만 이번에는 그 대신 비동기 함수를 사용했습니다. 함수를 비동기로 표시할 때 키워드는 함수 시그니처의 “throw” 바로 앞에, 또는 함수가 던지지 않는 경우 화살표 앞에, 이렇게 표시해야 합니다. 함수를 비동기로 표시하면 함수와 그 서명이 더 간단해집니다. 이미지 썸네일이 성공적으로 생성되면 해당 썸네일만 반환됩니다. 그리고 오류가 발생하면 그냥 던져집니다. fetchThumbnail이 호출되면 이전과 마찬가지로 thumbnailURLRequest를 호출하는 것으로 시작합니다. 이 함수는 동기식이기 때문에 스레드가 차단되면서 작업을 수행합니다.


다음으로, 공유 URLSession에서 data(for: request)를 호출하여 데이터 다운로드를 시작합니다. 이 메서드 역시 데이터태스크와 마찬가지로 Foundation에서 제공하며 비동기식입니다. 하지만 dataTask와 달리 데이터 메서드는 대기 중입니다. 따라서 호출된 후에는 빠르게 일시 중단되어 스레드의 차단을 해제합니다. 그러면 스레드는 다른 작업을 자유롭게 수행할 수 있습니다.

데이터 메서드가 “throws”로 표시되어 있기 때문에 “Try”가 표시됩니다. 이전 버전에서는 오류를 확인한 다음 완료 핸들러를 명시적으로 호출해야 했던 것을 기억하시나요? 여기 대기 중 버전에서는 그 모든 코드가 try 키워드로 단순화되어 있습니다. “throws"로 표시된 함수를 호출할 때 ‘try’가 필요한 것처럼, ‘async’로 표시된 함수를 호출할 때 ‘await’이 필요합니다. 표현식에 여러 개의 비동기 함수 호출이 있는 경우, 여러 개의 throw 함수 호출이 있는 표현식에 “try” 하나만 필요한 것과 마찬가지로 “await”을 한 번만 작성하면 됩니다. 즉, 함수 호출은 “try await”으로 표시됩니다. throw하는 비동기 표현식을 다룰 때는 이와 같이 await 앞에 try를 붙여야 합니다.
결국 데이터 다운로드가 완료되면 데이터 메서드는 다시 시작되어 fetchThumbnail로 돌아갑니다. 이때 데이터 메서드가 반환하는 값 또는 에러가 유입됩니다. 에러가 발생하면 fetchThumbnail은 자체적으로 해당 에러를 던집니다. 그렇지 않으면 데이터 및 응답 변수가 정의됩니다. 이는 이전 버전에서 URLSession의 dataTask 메서드로 전달된 완료 핸들러가 호출될 때 발생한 것과 유사합니다.

두 버전 모두 URLSession의 비동기 메서드에서 생성된 값과 오류가 입력되었습니다. 하지만 대기 버전은 훨씬 더 간단합니다. 정확히 무슨 뜻인지 알 수 있습니다. 이 요청을 하고 반환된 값을 변수에 할당하여 사용할 수 있도록 하면 됩니다. 그리고 문제가 발생하면 오류를 발생시키면 됩니다.

다음으로 fetchThumbnail은 다운로드한 데이터에서 UIImage를 생성하려고 시도합니다. 성공하면 해당 이미지의 썸네일 속성에 액세스하여 해당 이미지의 썸네일을 렌더링합니다. 썸네일이 생성되는 동안 스레드는 썸네일 프로퍼티가 재개되어 fetchThumbnail로 돌아갈 때까지 다른 작업을 자유롭게 수행할 수 있습니다.

썸네일이 렌더링되면 fetchThumbnail은 썸네일을 반환합니다. 그렇지 않으면 오류가 발생합니다.

 

완성을 위한 핸들러 버전과 달리, 썸네일이 렌더링되지 않으면 Swift는 여기에 오류를 던지거나 값을 반환합니다. 하지만 그냥 조용히 실패할 수는 없습니다. 그게 다입니다. 이것이 우리에게 필요한 모든 코드입니다. 이 함수는 이전에 완성 핸들러 버전이 했던 것과 똑같은 기능을 수행합니다. 하지만 20줄의 코드 대신 6줄만 있습니다. 그리고 모두 직관적인 코드입니다.

순서대로 수행해야 하는 네 가지 연산이 차례로 나열되어 있습니다. 그리고 Swift는 함수가 완료되면 항상 호출자에게 반환하거나 문제가 발생하면 throw를 통해 알려줍니다. 이것은 비동기/대기 함수를 사용하면 비동기 Swift 코드를 어떻게 변형하여 더 안전하고 짧게 만들고 의도를 더 잘 반영할 수 있는지에 대한 한 가지 예시일 뿐입니다.

 

fetchThumbnail이 어떻게 구현되는지 자세히 살펴봅시다. 두 번째 줄에서 마지막 줄을 보면 함수 호출이 없음에도 불구하고 썸네일 렌더링을 시작하는 표현식에 “await”이 표시되어 있습니다. 이는 썸네일 프로퍼티가 비동기식이기 때문입니다. 함수만 비동기일 수 있는 것은 아닙니다. 프로퍼티도 이니셜라이저와 마찬가지로 비동기화될 수 있습니다. 이제 썸네일 프로퍼티는 SDK의 일부가 아닙니다. 사실 Robert가 추가한 것입니다. 한번 살펴봅시다.

그는 이 프로퍼티를 UIImage의 확장에 정의했으며, 구현은 간단합니다. CGSize를 형성하고 이를 byPreparingThumbnail(ofSize)에 전달한 결과를 기다립니다. 참고로 이 메서드는 앞서 사용한 메서드의 대기를 기다려야 하는 버전입니다.

몇 가지 주목해야 할 점이 있습니다. 첫째, 명시적인 getter가 있습니다. 이는 프로퍼티를 비동기로 표시하는 데 필요합니다. Swift 5.5부터는 프로퍼티 getter가 throw도 가능합니다. 그리고 비동기 함수 시그니처와 마찬가지로, 프로퍼티가 비동기인 동시에 throw인 경우 비동기 키워드는 throw 바로 앞에 오게 됩니다.

 

 

 
 
 

둘째, 프로퍼티에는 설정자가 없습니다. 읽기 전용 프로퍼티만 비동기화할 수 있습니다.

함수, 속성 및 이니셜라이저에서 표현식에 await을 사용하여 함수가 스레드 차단을 해제할 수 있는 위치를 나타낼 수 있습니다. 비동기 시퀀스를 반복하는 루프에도 await을 사용할 수 있습니다. 비동기 시퀀스는 요소를 비동기적으로 전달한다는 점을 제외하면 일반 시퀀스와 비슷합니다. 따라서 다음 항목을 가져올 때는 비동기식임을 나타내는 await 키워드로 표시해야 합니다.

함수는 비동기 시퀀스를 계속 반복하면서 다음 요소를 기다리는 동안 스레드를 차단 해제하고 다음 요소를 루프 본문으로 가져오거나 남은 요소가 없는 경우 루프 이후에 다시 시작할 수 있습니다.

비동기 시퀀스에 대해 자세히 알아보려면 “비동기 시퀀스 살펴보기” 세션을 시청하세요. 그리고 많은 비동기 작업을 병렬로 실행하는 데 관심이 있다면 “Swift의 구조화된 동시성” 세션을 확인하세요.

이처럼 await을 사용할 수 있는 곳은 많습니다. 이 키워드는 비동기 함수가 거기서 일시 중단될 수 있음을 나타냅니다. 비동기 함수가 일시 중단된다는 것은 무엇을 의미할까요? 이에 대한 답을 찾기 위해 함수를 호출할 때 어떤 일이 일어나는지 생각해 봅시다.

 

함수를 호출하면 함수가 실행 중인 스레드의 제어권을 해당 함수에 넘깁니다. 여기서 thumbnailURLRequest와 같이 호출하는 함수가 일반 함수인 경우 스레드는 해당 함수가 완료될 때까지 해당 함수를 대신하여 작업을 수행하며 완전히 사용하게 됩니다.

그 작업은 함수 자체의 본문에 있거나 함수가 호출하는 다른 함수에 있을 수 있습니다. 결국 해당 함수는 값을 반환하거나 오류를 발생시켜 완료됩니다. 함수가 완료되면 제어권을 다시 내 함수에 넘깁니다. 이것이 일반 함수가 스레드에 대한 제어권을 포기할 수 있는 유일한 방법입니다. 그리고 여러분의 함수만이 제어권을 넘겨줄 수 있습니다. 비동기 함수를 호출하는 경우에는 상황이 달라집니다. 일반 함수와 마찬가지로 완료되면 완료되고 제어권을 함수에 반환합니다. 하지만 일반 함수와 달리 일시 중단이라는 완전히 다른 방식으로 스레드에 대한 제어권을 넘겨줄 수 있습니다.

일반 함수와 마찬가지로 비동기 함수를 호출하면 스레드에 대한 제어권을 비동기 함수에 넘깁니다.

 

비동기 함수는 실행 중일 때 일시 중단될 수 있습니다. 이 경우 비동기 함수는 스레드 제어권을 포기합니다. 하지만 함수에 제어권을 돌려주는 대신 스레드에 대한 제어권을 시스템에 넘깁니다. 이런 일이 발생하면 함수도 일시 중단됩니다. 일시 중단은 함수가 시스템에 “할 일이 많다는 것을 알고 있습니다. 무엇이 가장 중요한지 결정하세요."라고 말하는 것입니다. 얼마나 협조적일까요? 따라서 함수가 일시 중단되면 시스템은 스레드를 사용하여 다른 작업을 자유롭게 수행할 수 있습니다. 어느 시점에서 시스템은 가장 중요한 작업이 앞서 일시 중단했던 비동기 함수를 계속 실행하는 것이라고 결정할 것입니다. 이 시점에서 시스템은 비동기 함수를 다시 시작합니다. 그러면 해당 비동기 함수가 다시 스레드를 제어하고 작업을 계속 진행할 수 있습니다.

그리고 원한다면 다시 일시 중단할 수도 있습니다. 실제로 비동기 함수는 필요한 횟수만큼 스스로를 일시 중단할 수 있습니다.

반면에 전혀 일시 중단할 필요가 없을 수도 있습니다. 비동기 함수는 일시 중단될 수 있지만, 비동기라고 표시되어 있다고 해서 반드시 일시 중단되는 것은 아닙니다. 마찬가지로 '대기 중'이라고 표시되었다고 해서 함수가 반드시 거기서 일시 중단된다는 의미는 아닙니다. 하지만 결국 일시 중단되지 않든, 마지막으로 재개된 후든 함수는 완료되어 값 또는 오류와 함께 스레드 제어권을 함수에 다시 넘겨줍니다.

 

일시 중단될 때 어떤 일이 발생할 수 있는지 fetchThumbnail을 다시 한 번 살펴보겠습니다.

fetchThumbnail이 URLSession의 비동기 데이터 메서드를 호출하면 데이터 메서드는 비동기 함수만이 할 수 있는 특별한 방식인 일시 중단을 통해 스레드에서 실행을 중지합니다. 이는 스레드에 대한 제어권을 시스템에 넘기고 시스템에 URLSession의 데이터 메서드에 대한 작업 예약을 요청합니다.

하지만 이 시점에서는 시스템이 제어권을 가지고 있으므로 해당 작업이 즉시 시작되지 않을 수 있습니다. 대신 스레드를 다른 용도로 사용할 수 있습니다. 어떻게 이런 일이 발생할 수 있는지 살펴보겠습니다. fetchThumbnail이 호출된 후 사용자가 일부 데이터를 업로드하는 버튼을 탭한다고 가정해 봅시다. 예를 들어 사용자가 게시물에 반응한다고 가정해 봅시다. 그러면 시스템은 이전에 대기 중인 작업 전에 사용자의 반응을 게시하는 작업을 자유롭게 실행할 수 있습니다.

이 지연 작업이 완료되면 URLSession의 데이터 메서드가 다시 시작될 수 있습니다. 또는 시스템이 다른 작업을 대신 실행할 수도 있습니다. 마지막으로, 데이터 메서드가 완료되면 fetchThumbnail로 돌아갑니다. 함수가 일시 중단된 동안 다른 작업을 수행할 수 있다는 사실 때문에 Swift는 비동기 호출에 await 키워드를 표시하도록 강조합니다. 함수가 일시 중단되면 앱의 상태가 크게 바뀔 수 있다는 점에 유의해야 합니다.

 

셋째, 비동기 함수가 일시 중단되는 동안에는 스레드가 차단되지 않습니다. 따라서 시스템에서 다른 작업을 자유롭게 예약할 수 있습니다. 나중에 시작되는 작업도 먼저 실행할 수 있습니다. 즉, 함수가 일시 중단된 동안 앱의 상태가 크게 변경될 수 있습니다. 마지막으로 비동기 함수가 재개되면 호출한 비동기 함수에서 반환된 결과가 원래 함수로 다시 유입되고 중단된 지점부터 실행이 계속됩니다. 지금까지 Swift에서 비동기/대기 함수가 어떻게 작동하는지 살펴보았습니다. 이제 Robert가 여러분의 프로젝트에서 이 기능을 사용하는 방법을 보여드리겠습니다. 고마워요, 네이트. 앞서 네이트는 우리가 함께 만들고 있는 앱을 보여드렸습니다. 그가 변환한 썸네일 함수는 몇 군데에서 호출되었으므로 동시성을 채택하기 위해 마이그레이션해야 합니다. 현대 소프트웨어 개발의 핵심인 테스트부터 시작하겠습니다. 우리는 비동기 코드를 테스트하는 것이 동기 코드를 테스트하는 것만큼이나 쉬워지기를 원했기 때문에 XCTest는 기본적으로 비동기를 지원합니다. 기대값을 설정하고, 테스트 중인 API를 호출하고, 기대값을 이행한 다음 임의의 시간 동안 대기해야 하는 지루한 프로세스를 테스트 함수에 비동기 키워드를 추가하고, XCTest 기대값과 그 이행, 명시적 대기를 제거하고, 대신 앞서 보여드린 새로운 비동기 fetchThumbnail 함수 호출의 결과를 기다리는 것만큼 쉽게 테스트할 수 있게 되었습니다.

 

이제 테스트가 완료되었으므로 애플리케이션 코드 자체를 확대해 보겠습니다. 특히 이 목록의 각 행에 있는 썸네일 보기 뒤에 있는 SwiftUI 코드를 살펴보겠습니다.

이미지 셀은 게시물로 생성되며, 각 게시물에는 뷰모델에 전달하여 썸네일을 비동기적으로 검색할 수 있도록 하는 ID가 있습니다. 테스트 코드에서 이 호출을 변환하는 방법을 이미 보셨으니 한 번 해보겠습니다. 먼저 완료 핸들러를 제거한 다음 오류를 처리하기 위해 “try”를 추가하고 비동기 함수 호출을 완료하기 위해 “await”을 추가합니다. 하지만 이 코드를 빌드하려고 하면 문제가 발생합니다. Swift 컴파일러는 자체적으로 비동기적이지 않은 컨텍스트에서는 비동기 함수를 호출할 수 없다고 말합니다. 여기서 onAppear 수정자는 일반 비동기 클로저를 사용하므로 동기 세계와 비동기 세계 사이의 간격을 메울 수 있는 방법이 필요합니다.

해결책은 비동기 작업 함수를 사용하는 것입니다. 비동기 작업은 클로저의 작업을 패키징하여 글로벌 디스패치 큐의 비동기 함수처럼 사용 가능한 다음 스레드에서 즉시 실행되도록 시스템으로 보냅니다.

여기서 비동기 코드의 주요 이점은 동기화 컨텍스트 내부에서 비동기 코드를 호출할 수 있다는 것입니다.

한 번 더 리빌드하면 컴파일러가 완료됩니다. 비동기 작업은 친숙하고 자연스럽게 구조화된 형식으로 강력한 동시 Swift 코드를 빌드할 수 있는 API 모음의 일부입니다.

 

자세한 내용은 “Swift에서 구조화된 동시성 살펴보기”를 참조하세요. 그리고 SwiftUI 앱에서 비동기 코드를 최대한 활용하는 방법을 알아보려면 “SwiftUI에서 동시성 살펴보기”를 참조하세요. fetchThumbnail 함수를 호출하던 모든 곳의 마이그레이션을 마쳤습니다. 하지만 우리 앱에는 비동기/대기를 채택할 기회가 훨씬 더 많습니다. 빠르게 시작하고 실행하려면 기존 API를 대체할 수 있는 비동기 방식으로 작게 시작하는 것이 좋습니다. SDK는 비동기 방식으로 사용자를 대신하여 작동하기 때문에 완료 핸들러를 사용하는 수백 개의 API를 제공합니다. 이러한 API를 나란히 나열하면 패턴이 나타나기 시작합니다.

 

이름도 다르고 목적도 다르지만, 이 모든 함수는 동일한 필수 API 계약을 가지고 있습니다. 사용자가 함수를 호출하면 함수는 제공된 완성 핸들러로 다시 호출하여 얻은 결과를 전달합니다. 앞서 네이트는 비동기 함수의 결과를 기다리면 보다 자연스러운 코드를 작성할 수 있다는 것을 보여드렸습니다. 이러한 콜백 블록을 이러한 비동기 함수로 바꿀 수 있다면 정말 멋지지 않을까요? Swift 5.5부터 바로 이런 일이 가능합니다. Swift 컴파일러는 Objective-C에서 가져온 완성 핸들러 코드를 자동으로 살펴보고 비동기 대안을 제공합니다. 하지만 여기서 멈추지 않았습니다. 많은 델리게이트 API에는 완료 핸들러를 전달하는 메서드도 포함되어 있습니다. 핸들러를 호출하면 비동기 작업이 완료되면 프레임워크에 협조적으로 알려줍니다. 특정 글의 타임라인 항목을 표시하기 위해 fetchThumbnail을 호출하는 ClockKit 컴플리케이션 데이터 소스를 예로 들어보겠습니다.

이전과 마찬가지로 모든 경로에서 완료 핸들러를 호출해야 하며, 여기에는 클로저로 인해 많은 추가 오류가 발생합니다.

 

비동기 대기를 사용하면 더 이상 그럴 필요가 없습니다. 이 델리게이트 메서드에는 대신 사용할 수 있는 비동기 대체 메서드가 있습니다. 먼저, 비동기 대체 이름에서 앞의 “get”을 삭제하는 비동기 대체 이름을 사용합니다. 비동기 함수는 호출 결과가 직접 반환되지 않을 때 통신하는 “get”과 같은 선행 단어를 생략하는 것이 좋습니다. 결국 이 함수는 비동기 대체 함수이므로 타임라인 항목을 직접 반환합니다. 이제 비동기 컨텍스트가 설정되었으므로 비동기 버전의 fetchThumbnail을 호출합니다. 마지막으로, 이제 삭제된 완료 블록을 호출하는 대신 이 메서드에서 타임라인 항목을 반환합니다.

여기서 강조한 비동기 API는 극히 일부에 불과합니다.

더 자세히 알아보려면 비동기/대기 API 자체에 대해 훨씬 더 자세히 설명하는 이 세션과 비동기/대기 방식을 채택할 때 이를 사용하는 방법을 참조하세요.

이 모든 것은 Swift가 사용자를 대신하여 비동기 대안을 생성하는 상황의 예시입니다. 그러나 코드에는 필연적으로 비동기 대안을 직접 만들어야 하는 부분이 있을 것입니다.

이것이 실제로 어떻게 보이는지 살펴보겠습니다.

 

우리 앱에서는 이 getPersistentPosts 함수를 사용하여 코어 데이터 스토어에 지속된 모든 게시물을 검색합니다. 이 함수는 앱에서 비동기 썸네일 함수보다 훨씬 더 많은 곳에서 호출되므로 모든 곳에서 비동기를 사용하는 것은 정말 큰 변화일 것입니다. 그리고 NSAsynchronousFetchRequest를 사용하고 있기 때문에 이 함수는 비동기 함수를 대체할 수 있는 완벽한 후보인 것 같습니다. 먼저 비동기 함수를 만들고 반환값을 변환합니다. 이 함수는 에러를 발생시킬 수 있으므로 이 함수도 'throw'로 표시합니다.

다음으로 getPersistentPosts의 완료 핸들러 버전을 호출하는데, 이제 문제가 생겼습니다.

콜백의 결과를 비동기 퍼시스턴트 포스트 함수에 대한 호출을 기다리는 곳으로 다시 반환해야 합니다. 뿐만 아니라 해당 호출자는 일시 중단된 상태입니다. 호출자가 나머지 작업을 계속할 수 있도록 적절한 시점에 적절한 데이터로 호출을 재개해야 합니다.

앞서 Nate가 Swift와 시스템이 협력하여 비동기 코드 재개를 처리하는 방법을 보여드렸습니다. 이 일시 중단/재개 프로세스가 어떻게 작동하는지 좀 더 자세히 살펴보고 우리 문제에 대한 유사한 해결책을 찾을 수 있는지 알아보겠습니다.

 

퍼시스턴트포스트의 비동기 버전이 호출되면 코어 데이터로 호출됩니다. 나중에 코어 데이터는 완료 핸들러를 호출하고 가져오기 요청의 결과를 전달합니다. 이 상황은 앞서 Nate가 보여드린 fetchThumbnail 함수가 일시 중단된 비동기 함수 호출을 재개하도록 코어 데이터가 아닌 시스템에 요청했을 때와 거의 동일하게 보입니다.

빠진 것은 완료 핸들러를 기다렸다가 가져오기 요청의 결과를 가지고 재개하는 브리지뿐입니다.

이 패턴은 항상 등장하며, 이 패턴에는 연속성이라는 이름이 있습니다. 이 세션에서 네이트와 저는 이미 연속의 많은 예제, 즉 완료 블록을 사용하는 메서드를 보여드렸습니다.

메서드 호출자는 함수 호출의 결과를 기다렸다가 다음에 수행할 작업을 지정하는 클로저를 제공합니다. 함수 호출이 완료되면 완료 핸들러를 호출하여 호출자가 그 결과로 수행하고자 했던 작업을 재개합니다. 이러한 종류의 협력 실행은 Swift에서 비동기 함수가 작동하는 방식과 정확히 일치합니다.

이를 명확히 하기 위해 Swift는 높은 수준의 안전한 방식으로 연속을 생성, 관리 및 재개할 수 있는 기능을 제공합니다.

예제로 돌아가서 연속 함수가 비동기 대체 코드 작성을 완료하는 데 어떻게 도움이 되는지 살펴봅시다.

 

withCheckedThrowingContinuation 함수는 오류가 있는 완료 블록을 비동기 Swift 함수를 던질 때까지 해제합니다. 함수가 에러를 던지지 않을 것이라는 것을 알고 있는 경우를 위해 withCheckedContinuations라는 대응 함수가 있습니다. 이 함수는 일시 중단된 비동기 함수를 재개하는 데 사용할 수 있는 연속 값에 액세스하는 방법입니다. 또한 getPersistentPosts에 대한 호출을 기다릴 수 있게 함으로써 브리지의 첫 번째 부분을 구축합니다.

이제 브리지 구축을 마무리하겠습니다. 지속 값은 완료 핸들러의 결과를 배치하는 재개 함수를 제공합니다. 뿐만 아니라 resume는 persistentPosts 함수의 결과를 기다리는 모든 호출의 일시 중단을 해제하는 데 필요한 누락된 링크를 제공합니다. 하나의 깔끔한 패키지로 완성된 완성 핸들러에서 비동기 함수로의 연결 고리가 완성되었습니다.

 

연속은 비동기 함수의 실행을 수동으로 제어할 수 있는 강력한 방법을 제공하지만 몇 가지 유의해야 할 사항이 있습니다. 연속에는 간단하지만 중요한 계약이 있습니다. Resume는 모든 경로에서 정확히 한 번만 호출해야 합니다. 하지만 걱정하지 마세요. 스위프트가 도와드리겠습니다.

resume가 호출되지 않고 연속이 중단되면 비동기 호출이 일시 중단되지 않으므로 Swift 런타임이 경고를 기록합니다.

그러나 동일한 함수에서 연속이 여러 번 재개되면 프로그램 데이터가 손상될 수 있으므로 더 심각한 오류입니다. 이를 방지하기 위해 Swift 런타임은 재시작을 여러 번 호출하려는 시도를 감지하고 두 번째 재개 지점에서 치명적인 오류가 발생하도록 합니다.

이를 염두에 두고 검사된 연속을 사용할 수 있는 중요한 위치를 한 가지 더 강조해 보도록 하겠습니다.

많은 API가 이벤트 기반입니다. 이들은 특정 중요 지점에서 애플리케이션에 알리고 애플리케이션이 적절하게 응답할 수 있도록 델리게이트 콜백을 제공합니다. 비동기/대기를 제대로 적용하려면 연속을 저장했다가 나중에 다시 시작해야 합니다. 이전과 마찬가지로 체크된 연속을 생성합니다.

그런 다음 이를 저장하고 작업을 시작합니다.

 

확인된 연속의 API 계약을 준수하기 위해 활성 연속을 다시 시작하고 마지막으로 무효화하여 두 번 이상 호출하지 못하도록 보호합니다.

항상 기억하세요: 여기서 체크된 연속 값은 이 API에 대한 비동기 호출을 수동으로 재개할 수 있는 기능을 나타내므로 모든 경로에서 호출되어야 합니다.

특정 상황에서 델리게이트 API가 여러 번 호출되거나 전혀 호출되지 않는 경우 활성 연속을 정확히 한 번만 재개하는 것이 중요합니다.

연속을 포함한 Swift 동시성의 하위 수준 세부 사항에 대해 자세히 알아보려면 “Swift 동시성: 비하인드 스토리” 세션을 참조하세요.

지금까지 Swift의 비동기/대기에 대해 살펴봤습니다. 런타임에 비동기 및 대기 키워드가 어떻게 작동하는지, 그리고 애플리케이션과 프레임워크에 어떻게 적용할 수 있는지 살펴보았습니다. 시작하기 위해 SDK에서 사용할 수 있는 비동기 API의 샘플을 제공하고 기존 코드를 동기식 세계에서 비동기식 세계로 연결하는 방법을 보여드렸습니다.

비동기/대기 기능은 Swift의 모든 동시성 기능의 기초입니다. 여러분이 이 기능으로 무엇을 만들어낼지 기대가 됩니다. 시청해 주셔서 감사합니다.

 

 

 

 

 

 

 

async/await를 통해서 comletion Handler 보다 간결하고 가시성있게 안전하게 사용할 수 있는걸 알 수 있습니다

그리고 5.5에 추가된 읽기 속성으로 통해서 async를 작성할 수 있고 for 구문으로도 사용할 수 있는걸 알게되었습니다

 

 

 

 

Meet async/await in Swift - WWDC21 - Videos - Apple Developer

Swift now supports asynchronous functions — a pattern commonly known as async/await. Discover how the new syntax can make your code...

developer.apple.com

 

 

Get started with Swift concurrency - Discover - Apple Developer

Take charge of concurrent tasks with this comprehensive toolkit.

developer.apple.com

 

반응형

'WWDC' 카테고리의 다른 글

WWDC20 | App essentials in SwiftUI  (1) 2024.07.22