Programing/iOS

[WWDC 2021] async / await

5주년 2021. 6. 30. 18:19

안녕하세요, 오랜만에... 글을 작성해봅니다

 

빠르게~ 본론으로 들어가봅시다. 이번 WWDC전부터 많은 사람들의 관심을 끌었던... async/await를 다뤄보려고 합니다.

 

세션

 

이번에 async/await가 나온 이유중에 가장 큰 이유는 무엇일까요??

 

제가 가장 좋아하는 방법은 예시를 하나하나 보면서 이해하는건데요! 세션에서 제공되는 예시를 통해 보죠!

 

먼저 사용할 흐름도를 보고 코드를 보러갑시다!

 

기존에 다른 비동기 처리 로직 라이브러리를 사용하지 않고 사진을 로딩하는 코드 보자면...?

func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -> Void) {
    let request = thumbnailURLRequest(for: id)
    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 {
            guard let image = UIImage(data: data!) else {
                completion(nil, FetchError.badImage)
                return
            }
            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()
}

우선 code를 한번 흝어서 봐볼까요??

 

fetchThumbnail은 String 타입의 id를 받아 다른 메소드로 구현된 Request를 서버에 보냅니다.

 

그리고 data, response, error를 받아오는데요

 

error를 먼저 옵셔널 체크한이후에 error가 있다면 escaping handler로 error를 completion을 통해 보내줍니다.

 

다음으로는 서버에서 보내주는 response에서 statusCode가 200이 아닌경우에,

기존에 정의해둔 error(*FatchError.badID*)를 completion을 통해 보내줍니다.

 

여기까지 무사히 (서버 error 없이, statusCode가 200이라면!) 오게 된다면,

data를 UIImage화 시켜줍니다. 하지만? 여기서? 또 에러가 발생한다면... completion를 통해 FetchError.badImage를 보내줍니다...

 

그리고,

 

UIImage에 정의된 prepareThumbnail 메소드를 통해 썸네일 화를 하게 되는데 여기서도 thumbnail을 만들다 에러가 발생하게 되면

completion을 통해 badImage를 보냅니다...

 

여기까지 무사히 넘어가게 된다면 정상적인 이미지를 받게 되는거죠...

 

이렇게 한줄한줄 보다보면 흐름이 이해하기는 쉽지만 저렇게 들여쓰기가 어어엄청 되어있다면... 보기가 힘들죠,,,

게다가 저렇게 completion handler로 흐름을 제어하다보면 어디에서 어떤게 나가는지 파악을 하나하나 해야하죠... 

 

 

자 이 시점에서 async/await가 적용된 예시코드를 봐볼까요??

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
}

 

한눈에 봐도 깔끔해진... 코드를 볼 수 가 있습니다! 호호~

 

우선 completion이 throws와 -> UIImage 즉 에러를 던지는 throws와 정상적인 return값 UIImage를 사용하게 됩니다.

 

이런 구조로 메소드를 부르게 되는데요, 2번째 line은 이전과 동일합니다.

 

3번째 라인부터 달라졌는데요, await로 URLSession.shared.data 호출을 합니다.

 

이렇게 받아온 데이터를 4번째 라인에서 guard문으로 예외처리를 해줍니다 throw로 해당 예외를 던져주죠?

 

다음에 data를 UIImage로 바꿔주고(옵셔널로 처리가 되나보네요),  여기서 guard let 구문으로 예외처리를 해주죠

 

 

 

 

async / await를 사용하게 되면 completion handler를 이용한 클로저 호출을 해 그 값을 핸들링 할 필요없이,

swift에서 알아서 처리를 하나보네요...!

 

세션에서 소개되는 Async를 이용해 구현한 SDK의 메소드를 보여주는데요! 오...이것도... 굉장히 깔끔하게 어떤 데이터를 보내주는지 한눈에 보이네요...

 

뭔가 문서로서 내부 구조가 어떻게 이뤄져있는지 보고 싶은데 apple developer Document에 딱히 적혀있는것들이 찾을수가 없네요...

 

그래서 모호한 UIImage를 불러오는 코드가 아닌 정확한 예시 하나를 들어보려고 합니다. ( 동일하게 URLSession을 이용해 데이터를 받아오는 코드에요)

 

예전에 제가 만들었던 서비스에서 GET메소드를 이용해 불러오는 것 하나만 해보겠습니다.

 

{
    "message": "요청이 성공했습니다.",
    "data": [
        {
            "name": "IT",
            "endColor": "#FFFFFF",
            "startColor": "#6DD90C",
            "id": 1
        },
        {
            "name": "영어",
            "endColor": "#FFFFFF",
            "startColor": "#879822",
            "id": 2
        },
        {
            "name": "일본어",
            "endColor": "#FFFFFF",
            "startColor": "#989DC2",
            "id": 3
        },
        {
            "name": "중국어",
            "endColor": "#FFFFFF",
            "startColor": "#FD82CC",
            "id": 4
        },
        {
            "name": "운동",
            "endColor": "#FFFFFF",
            "startColor": "#89CC23",
            "id": 5
        },
        {
            "endColor": "#FFFFFF",
            "name": "생활습관",
            "startColor": "#98CCDD",
            "id": 6
        }
    ],
    "status": 200
}

우선 response는 이렇게 날아옵니다 

 

이 데이터를 이용해 구조체를 만들어주면

struct ResponseData: Codable {
    let message: String
    let data: [ColorCode]
    let status: Int
}

struct ColorCode: Codable {
    let name, endColor, startColor: String
    let id: Int
}

이런 decode를 하는 데이터를 만들수가 있습니다.

 

그리고 이걸 일반적인 ViewController에서 보여주기 위해 작성을 해보자면...

 

    override func viewDidLoad() {
        super.viewDidLoad()

        async {
            let response = try await color()
            print(response)
        }
    }
    
    func color() async throws -> String {
        let request = URLRequest(url: URL(string: "해당하는 URL")!)
        let (data, _) = try await URLSession.shared.data(for: request)
        do {
            let responseData = try JSONDecoder().decode(ResponseData.self, from: data)
            return responseData.data[0].startColor
        } catch {
            throw error
        }
    }

 

메소드를 선언해줄때는 async를 함께 작성해 비동기 함수라는 것을 알려줍니다

 

그리고 throws와 return 값은 자유롭게 지정이 가능합니다

 

아래에서 보이는 await는 해당 시점에서 중단되는 지점을 보여주는것인데요 해당 시점에서 response가 올때까지 기다리는 지점을 말합니다.

 

그리고 JSONDecoder를 통해 data를 decode해주는데요, 여기서 에러가 발생하게 되면 catch문에서 error가 던져지게 되죠.

 

마지막으로 async 메소드를 호출할때에는 해당 부분이 비동기적으로 작동하는것을 알려줘야하기 때문에 async로 묶어주게 됩니다.

 

여기서 결국 우리가 만든 color()메소드는 async 메소드이기 때문에 try await를 이용해 기다리는것을 알려줘야합니다.

 

결과값을 받아보게 되면!!

 

와 같이 정상적인 컬러 hex 코드를 받을수가 있습니다!!

 

이렇게... 간단한듯... 아닌듯한 async/await를 다루어보았는데요 (실제 사용하는것 처럼)

 

혹시... 채워야할 부분이나, 궁금하신점이 있다면 댓글로 달아주시면 감사하겠습니다!!

 

빠른시일내에 다시 포스팅하기를 바라면서... 이만 마무리를...해보겠습니다!