순진이의 하루/study정리
13. async/await_2022.09.19
순진이
2022. 9. 19. 19:39
async/await
async / await 사용이유
- 콜백 지옥
- 에러 처리에서 실수 가능성 있음
기본코드 ⬇️
더보기
model
import Foundation
// Codable = Encodable + Decodable
struct Quote: Decodable {
let content: String
}
error
import Foundation
enum NetworkError: Error {
case badResponse
case communicationError
case decodeFailed
case noData
}
controller
import UIKit
class ViewController: UIViewController {
let networkService = NetworkService.shared
let centerLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
setUI()
setAttributes()
}
}
// MARK: -URLSession
extension ViewController {
func setUI() {
view.addSubview(centerLabel)
centerLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
centerLabel.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
centerLabel.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
centerLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
centerLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])
}
func setAttributes() {
centerLabel.textColor = .black
centerLabel.textAlignment = .center
centerLabel.numberOfLines = 0
}
}
기존의 @escaping 방식
import Foundation
class NetworkService {
static let shared = NetworkService()
private init() {}
let url = URL(string: "https://api.quotable.io/random")!
func getQuote(completion: @escaping (Result<String, NetworkError>) -> Void) -> String {
var result = ""
URLSession.shared.dataTask(with: url) { data, response, error in
// 1. error check
if let error = error {
print(error)
completion(.failure(.communicationError))
return
}
// 2. response check
if let response = response as? HTTPURLResponse, !(200..<300).contains(response.statusCode) {
completion(.failure(.badResponse))
return
}
// 3. data check
guard let data = data else {
completion(.failure(.noData))
return
}
do {
let quote = try JSONDecoder().decode(Quote.self, from: data)
result = quote.content
completion(.success(result))
} catch {
completion(.failure(.decodeFailed))
print(error)
}
}.resume()
return result
}
}
import UIKit
class ViewController: UIViewController {
let networkService = NetworkService.shared
let centerLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
setUI()
setAttributes()
getQuote()
}
// 🍎
func getQuote() {
networkService.getQuote { [weak self] result in
switch result {
case .success(let quote):
print(quote)
DispatchQueue.main.async {
self?.centerLabel.text = quote
}
case .failure(let error):
print(error)
}
}
}
}
// MARK: -URLSession
extension ViewController {
func setUI() {
view.addSubview(centerLabel)
centerLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
centerLabel.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
centerLabel.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor),
centerLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
centerLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor)
])
}
func setAttributes() {
centerLabel.textColor = .black
centerLabel.textAlignment = .center
centerLabel.numberOfLines = 0
}
}
async / await 방식
정의
- 함수 정의 시 async 키워드 붙여주기
- data함수가 throws이므로 사용 시 try await 키워드 필요
- 결과 자체가 data, response로 옴
import Foundation
class NetworkService {
static let shared = NetworkService()
private init() {}
let url = URL(string: "https://api.quotable.io/random")!
func getQuoteWithAsync() async -> String {
do {
// try 뒤에 await 써야 함
// 애초에 data와 response를 반환함
let (data, response) = try await URLSession.shared.data(from: url)
if let response = response as? HTTPURLResponse, !(200..<300).contains(response.statusCode) {
return "응답 에러"
}
let quote = try JSONDecoder().decode(Quote.self, from: data)
return quote.content
} catch {
return "Decode Error"
}
}
}
사용
- 반드시 Task 블록 안에서 사용해야 함! + await 키워드와 함께
- DispatchQueue에 넣지 않아도 됨
func getQuote() {
Task {
let quote = await networkService.getQuoteWithAsync()
self.centerLabel.text = quote
}
}
배열로 부르기
func getQuotesArray1() async -> [String] {
var quotes = [String]()
for _ in 1...3 {
quotes.append(await getQuoteWithAsync())
}
return quotes
}
func getQuotesArray() {
var quoteArray = ""
Task {
let quotes = await networkService.getQuotesArray()
quotes.forEach { quoteArray += "-\($0)\n" }
self.centerLabel.text = quoteArray
}
}
배열로 부를 때 차이
1. async 키워드 붙일 때
- 기다리지 않음 -> 순서가 보장되지 않으나 빠름
- 리턴될 배열이 꽉차기를 기다릴 뿐
func getQuotesArray2() async -> [String] {
async let firstQuote = "1️⃣" + getQuoteWithAsync()
async let secondQuote = "2️⃣" + getQuoteWithAsync()
async let thirdQuote = "3️⃣" + getQuoteWithAsync()
return await [firstQuote, secondQuote, thirdQuote]
}
func getQuotesArray() {
var quoteArray = ""
Task {
let quotes = await networkService.getQuotesArray2()
quotes.forEach { quoteArray += "-\($0)\n" }
self.centerLabel.text = quoteArray
}
}
2. async 키워드 안 붙일 때
- 무조건 순서대로 진행됨 (순서 보장되지만 시간 소요가 큼)
- 각 작업이 10초가 걸린다면 이 함수는 무조건 30초가 걸림
func getQuotesArray3() async -> [String] {
let firstQuote = await "1️⃣" + getQuoteWithAsync()
let secondQuote = await "2️⃣" + getQuoteWithAsync()
let thirdQuote = await "3️⃣" + getQuoteWithAsync()
return [firstQuote, secondQuote, thirdQuote]
}
func getQuotesArray() {
var quoteArray = ""
Task {
let quotes = await networkService.getQuotesArray3()
quotes.forEach { quoteArray += "-\($0)\n" }
self.centerLabel.text = quoteArray
}
}
continuation
- 내용을 알지 못하지만 async하게 쓰고 싶다!
- 회사에 갔는데 기존 함수가 패키지에 쌓여 있거나, 너무 복잡해서 건드릴 수 없을 때 등
- unsafe보다는 withCheckedContinuation를 쓸 것 (unsafe가 더 빠르다지만, 안전한게 중요!)
class NetworkService {
static let shared = NetworkService()
private init() {}
let url = URL(string: "https://api.quotable.io/random")!
// 🍎 기존 escaping 함수
func getQuote(completion: @escaping (Result<String, NetworkError>) -> Void) -> String {
var result = ""
URLSession.shared.dataTask(with: url) { data, response, error in
// 1. error check
if let error = error {
print(error)
completion(.failure(.communicationError))
return
}
// 2. response check
if let response = response as? HTTPURLResponse, !(200..<300).contains(response.statusCode) {
completion(.failure(.badResponse))
return
}
// 3. data check
guard let data = data else {
completion(.failure(.noData))
return
}
do {
let quote = try JSONDecoder().decode(Quote.self, from: data)
result = quote.content
completion(.success(result))
} catch {
completion(.failure(.decodeFailed))
print(error)
}
}.resume()
return result
}
// 🍎 continuation
func continuationGetQuote() async -> String {
return await withCheckedContinuation { continuation in
getQuote { result in
switch result {
case .success(let quote):
// resume은 반드시 한 번만 호출해야 함
continuation.resume(returning: quote)
case .failure(let error):
print(error)
}
}
}
}
func getQuote() {
Task {
let quote = await networkService.continuationGetQuote()
self.centerLabel.text = quote
}
}