class TaskGroupBootcampViewModel: ObservableObject {
@Published var images: [UIImage] = []
}
struct TaskGroupBootcamp: View {
@StateObject private var viewModel = TaskGroupBootcampViewModel()
let columns = [GridItem(.flexible()), GridItem(.flexible())]
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(viewModel.images, id: \.self) { image in
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(height: 150)
}
}
}
.navigationTitle("Async Let 🥳")
}
}
}
요런 식으로 뷰랑 뷰모델이 있다고 해봅시다
(저번시간에 했던 거랑 똑같음!)
class TaskGroupBootcampDataManager {
func fetchImagesWithAsyncLet() async throws -> [UIImage] {
async let fetchImage1 = fetchImage(urlString: "https:/picsum.photos/300")
async let fetchImage2 = fetchImage(urlString: "https:/picsum.photos/300")
async let fetchImage3 = fetchImage(urlString: "https:/picsum.photos/300")
async let fetchImage4 = fetchImage(urlString: "https:/picsum.photos/300")
let (image1, image2, image3, image4) = await (try fetchImage1, try fetchImage2, try fetchImage3, try fetchImage4)
return [image1, image2, image3, image4]
}
private func fetchImage(urlString: String) async throws -> UIImage {
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let image = UIImage(data: data) {
return image
} else {
throw URLError(.badURL)
}
} catch {
throw error
}
}
}
fetchImage를 처리해주는 DataManager 클래스를 만들어줌!
그리고 async let으로 비동기 작업이 한번에 모두 같이 시작할 수 있게끔 해주고
class TaskGroupBootcampViewModel: ObservableObject {
@Published var images: [UIImage] = []
let manager = TaskGroupBootcampDataManager()
func getImages() async {
if let images = try? await manager.fetchImagesWithAsyncLet() {
self.images.append(contentsOf: images)
}
}
}
뷰모델에서 manager가 가지고 있는 메소드를 호출해주고!
(원래는 지금처럼 manager를 만들어주기보다는 dependencyInjection으로 넣어주겠죠)
뷰에서 .task로 이미지를 가져오게 해봅시다
뺌!
한번에 다 불러와지죠 이미지
지금은 요렇게 async let으로 하나하나 다 작성해줬는데
한번 TaskGroup을 써서 더 효율적으로 코드를 작성해봅시다
메소드를 하나 만들어주는데
taskGroup이라고 치면 throwing이 붙은게 있고 안붙은게 있다.
에러 처리할건지에 따라서 나뉨
UIImage타입으로 of 파라미터에 넣어주고
이미지를 return 하면서 그 결과 값을 바로 또 return 되게끔 해줌
이 메소드 안에서 addTask를 호출해주는데 priority 설정이 가능하다
기본적으로 task는 parent의 priority를 물려받아서 보통 작성안해줌
func fetchImagesWithTaskGroup() async throws -> [UIImage] {
return try await withThrowingTaskGroup(of: UIImage.self) { group in
var images: [UIImage] = []
group.addTask {
try await self.fetchImage(urlString: "https://picsum.photos/300")
}
group.addTask {
try await self.fetchImage(urlString: "https://picsum.photos/300")
}
group.addTask {
try await self.fetchImage(urlString: "https://picsum.photos/300")
}
group.addTask {
try await self.fetchImage(urlString: "https://picsum.photos/300")
}
for try await taskResult in group {
images.append(taskResult)
}
return images
}
}
group에다 task를 4개 추가해줬다
그리고 group안에 있는 각각의 task들을 for in 루프를 써서 images array에 append 되게 해줬는데 잘 보면 뭔가 평소에 쓰던 for in 루프랑 다르다는 게 보임
iterate 하면서 돌던 거랑 달리 각각을 동시에 시작하고 결과를 await한다!
그니까 이중에 하나라도 실패하면 계속 기다리게 된다는 거 알아둬야함
func fetchImagesWithTaskGroup() async throws -> [UIImage] {
let urlStrings = [
"https://picsum.photos/300",
"https://picsum.photos/300",
"https://picsum.photos/300",
"https://picsum.photos/300",
"https://picsum.photos/300",
]
return try await withThrowingTaskGroup(of: UIImage.self) { group in
var images: [UIImage] = []
for urlString in urlStrings {
group.addTask {
try await self.fetchImage(urlString: urlString)
}
}
for try await taskResult in group {
images.append(taskResult)
}
return images
}
}
urlStrings 배열을 만들어주고 for in으로 돌리면서 group에다가 task를 추가해줬다! 더 효율적이고 확장성이 좋아졌죠?
한가지 팁!
images array의 크기를 미리 예약을 해주면 조금 더 메모리를 아낄 수 있음
그리고 아까 for in 루프에서 task 처리될 때 하나라도 실패하면 error 튀어나와서 밖으로 빠져나오게 된다고 했잖음
이걸 방지하려면 UIImage 자체를 옵셔널하게 만들어주고
group.addTask를 try?로 바꿔주면됨
그리고 append하는 부분에서는 옵셔널 바인딩을 해주고!
func fetchImagesWithTaskGroup() async throws -> [UIImage] {
let urlStrings = [
"https://picsum.photos/300",
"https://picsum.photos/300",
"https://picsum.photos/300",
"https://picsum.photos/300",
"https://picsum.photos/300",
]
return try await withThrowingTaskGroup(of: UIImage?.self) { group in
var images: [UIImage] = []
images.reserveCapacity(urlStrings.count)
for urlString in urlStrings {
group.addTask {
try? await self.fetchImage(urlString: urlString)
}
}
for try await taskResult in group {
if let image = taskResult {
images.append(image)
}
}
return images
}
}
정리하면 이렇게 되겠습니다!
아 그리고 지금처럼 urlStrings는 메소드 내에 작성하기보다는
파라미터로 받는 쪽으로 사용하게 되겠죠!!