async/await, Task

Groot·2022년 12월 16일
0

TIL

목록 보기
112/148
post-thumbnail

TIL

🌱 난 오늘 무엇을 공부했을까?

📌 Swift Concurrency - 1

📍 Swift concurrency란?

  • 기존 CompletionHandler 방식이나, Combine 방식과 별개로 Swift에서 동시성 프로그램을 지원하는 새로운 기술
  • 특징
    • CompletionHandler가 사용하는 탈출 클로저를 사용한 방식이 아님.
    • Combine 처럼 Observable한 타입을 만들어서 사용하는 방식이 아님.
    • 완료될 때까지 실행되거나 오류가 발생하거나 반환되지 않는 일반적인 동기식 함수 및 메서드가 아닌 실행 도중 일시 중지할 수 있는 특수한 종류의 함수 또는 메서드를 사용하는 방식.
  • 단순하게 생각하면 비동기 처리의 완료 시점까지 기다려준다.

📍 사용방법 - async/await

  • 비동기 함수를 표시하려면 함수 선언의 매개변수 뒤에 async 키워드를 작성

    func listPhotos(inGallery name: String) async -> [String] {
        let result = // ... some asynchronous networking code ...
        return result
    }
  • 비동기 메서드를 호출하면 해당 메서드가 반환될 때까지 실행이 일시 중단.

  • 일시 중단 지점을 표시하기 위해 호출 앞에 await를 붙인다.

    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:) 함수를 호출하고 해당 함수가 반환될 때까지 기다리는 동안 실행을 일시 중단

    2. 이 코드의 실행이 일시 중단되는 동안 동일한 프로그램의 다른 동시 코드가 실행, 해당 코드는 await로 표시된 다음 일시 중단 지점까지 또는 완료될 때까지 실행

    3. listPhotos(inGallery:)가 반환된 후 이 코드는 해당 지점부터 실행을 계속한다. listPhotos(inGallery:)가 리턴해준 값을 photoNames에 할당.

    4. 다음 await는 downloadPhoto(named:) 함수에 대한 호출이기 때문에 이 코드는 해당 함수가 반환될 때까지 실행을 다시 일시 중지하여 다른 동시 코드가 실행할 기회를 제공한다.

    5. downloadPhoto(named:)가 반환된 후 반환 값은 photo에 할당된 다음 show(_:) 호출 시 인수로 전달.

  • 여기서 iOS에서 UI 업데이트와 별개로 이뤄지는 네트워킹 같은 비동기 처리를 하기 위해선 어떻게 해야할까?

    • 방법은 Tasks and Task Groups을 사용

📍 Tasks and Task Groups

  • 프로그램의 일부를 비동기적으로 실행할 수 있는 작업 단위
  • 작업 그룹을 만들고 해당 그룹에 하위 작업을 추가할수도 있다.
  • 작업은 계층 구조로 정렬.
  • 작업 그룹의 각 작업에는 동일한 상위 작업이 있으며 각 작업에는 하위 작업이 있을 수 있다.
  • 이 접근 방식을 구조화된 동시성이라고 함.
  • Task Groups 참고
await withTaskGroup(of: Data.self) { taskGroup in
    let photoNames = await listPhotos(inGallery: "Summer Vacation")
    for name in photoNames {
        taskGroup.addTask { await downloadPhoto(named: name) }
    }
}

🔗 단일 Task - unstructured concurrency

  • 계층 없이 하나의 Task
  • Task.init(), Task.detached()로 호출
  • Task를 생성한 후 인스턴스를 사용하여 Task와 상호 작용
    • 작업이 완료될 때까지 기다리거나 작업을 취소한다.
      let newPhoto = // ... some photo data ...
      let handle = Task {
          return await add(newPhoto, toGalleryNamed: "Spring Adventures")
      }
      let result = await handle.value
  • Task에서 await 함수를 실행하게 되면 Task에 감싸진 코드들은 비동기로 실행하고 await 함수가 호출되고 나서 Task 내부에 있는 다음 코드를 호출한다. Task 외부에 있는 코드는 Task의 종료를 기다리지 않는다.
    • CompletionHandler와 비슷한 느낌.

🔗 Task Cancellation

  • Task은 실행 중 적절한 시점에 취소되었는지 여부를 확인하고 적절한 방식으로 취소에 응답한다.

  • 수행 중인 Task에 따라 일반적으로 다음 중 하나를 의미한다.

    • CancellationError와 같은 오류 발생
    • Returning nil or an empty collection
    • Returning the partially completed work
  • 사용방법

    • Task의 취소를 수동으로 전파하려면 Task.cancel()을 호출
    • Task가 조기에 중지되는 마지막 지점을 이미 지나 실행된 경우 이 메서드를 호출해도 취소는 불가능.
  1. 취소 후 Task.checkCancellation()를 호출하면 CancellationError가 발생한다.
  2. Task.isCancelled 값을 확인하고 자체 코드에서 취소를 처리하는 방법도 있다.

📍 CompletionHandler 코드를 Concurrency 방식으로 리팩토링

  • 네트워킹 비동기 처리를 CompletionHandler로 사용한 프로젝트를 간단하게 Concurrency로 변경해보기.

  • CompletionHandler

// completionHandler 방식에서 Urlsession 코드

func excute(completionHandler: @escaping (Result<[[String : Any]], Error>) -> Void) {
        guard let url = self.url else { return completionHandler(.failure(APIError.noneUrlValue)) }
        
        let task = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
            guard error == nil else { return completionHandler(.failure(APIError.request)) }
            
            guard let response = response as? HTTPURLResponse, 200 <= response.statusCode, response.statusCode < 300
            else { return completionHandler(.failure(APIError.response)) }

            guard let data = data else { return completionHandler(.failure(APIError.invalidData)) }
            
            guard let jsonData = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String : Any]]
            else { return completionHandler(.failure(APIError.decode)) }
            
            completionHandler(.success(jsonData))
        }
        
        task.resume()
    }

// completionHandler 방식에서 실제 네트워킹 사용

 wiseSayingRequest.excute { result in
            switch result {
            case .success(let data):
                DispatchQueue.main.async { [weak self] in
                    guard let wiseSaying = data[1]["respond"] as? String else { return }
                    
                    self?.wiseSayingLabel.text = wiseSaying
                        .replacingOccurrences(of: "-", with: "–")
                        .split(separator: "–")
                        .map { String($0) }
                        .joined(separator: "\n\n")
                }
            case .failure( _):
                break
            }
        }
  • Concurrency로 리팩토링
```swift
// Concurrency 방식에서 Urlsession 코드

func excute() async -> (Result<[[String : Any]], Error>) {
        guard let url = self.url else { return .failure(APIError.noneUrlValue) }
        
        do {
            let (data, response) = try await URLSession(configuration: .default).data(from: url)
            
            guard let response = response as? HTTPURLResponse, 200 <= response.statusCode, response.statusCode < 300
            else { return .failure(APIError.response) }
            
            guard let jsonData = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String : Any]]
            else { return .failure(APIError.decode) }
            
            return .success(jsonData)
        } catch {
            return .failure(APIError.request)
        }
    }

// Concurrency 방식에서 실제 네트워킹 사용
Task.init {
            let data = await wiseSayingRequest.excute()
            switch data {
            case .success(let data):
                DispatchQueue.main.async { [weak self] in
                    guard let wiseSaying = data[1]["respond"] as? String else { return }
                    
                    self?.wiseSayingLabel.text = wiseSaying
                        .replacingOccurrences(of: "-", with: "–")
                        .split(separator: "–")
                        .map { String($0) }
                        .joined(separator: "\n\n")
                }
            case .failure(_):
                break
            }
        }

https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID643
https://developer.apple.com/documentation/swift/updating_an_app_to_use_swift_concurrency

profile
I Am Groot

0개의 댓글