홈 화면에 진입했을 때, 5번의 요청을 진행하다보니 로딩이 오래 걸리는 문제가 있었습니다.
기존에는 GCD
를 이용하여 작업을 global Queue
에 전달하고 응답 데이터를 escaping closure
로 전달해서 UI를 업데이트하고 있었습니다.
동시성 처리를 보다 더 효율적으로 처리하기 위해 고민하다가 Swift Concurrency
에 대해서 알게 됐습니다. Swift Concurrency
를 사용하면, 코어 개수에 따른 최소 스레드만 사용하여 작업을 처리하기 때문에 효율적으로 다수의 요청을 처리할 수 있습니다. 또한, 작업 처리에 대한 중단 지점을 설정하여 코드 가독성을 높일 수 있습니다.
Swift Concurrency
를 적용하여 중단 지점을 기점으로 서버로부터의 요청이 모두 끝난 후에 MainActor
을 이용하여 메인 스레드에서 UI를 업데이트할 수 있도록 마이그레이션했습니다.
과정은 다음과 같습니다.
먼저, NetworkManager
에서 async throws
키워드 추가합니다.
샘플 코드는 다음과 같습니다.
// NetworkManager
final class NetworkManager {
**func request<T: Decodable>(targetType: TargetType, completion: @escaping (Result<[T], NetworkError>) -> ()) async throws {**
let (data, response) = try await URLSession.shared.data(for: request as URLRequest)
****
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
completion(.failure(.invalidResponse))
return
}
guard !data.isEmpty else {
completion(.failure(.noData))
return
}
do {
let data = try JSONDecoder().decode([T].self, from: data)
completion(.success(data))
} catch {
completion(.failure(.decodingError))
}
}
}
PostTableViewModel
에서 NetworkManager
에서 서버 요청 함수를 do
블럭으로 감싸고 await
으로 중단 지점을 생성합니다.
샘플 코드는 다음과 같습니다.
// PostTableViewModel
final class PostTableViewModel {
var posts: [Post] = []
var filteredPosts: [Post] = []
func request(targetType: TargetType) async {
switch targetType
case .getPosts:
do {
**try await NetworkManager.shared.request(targetType: .getPosts) { (result: Result<[Post], NetworkError>) in**
switch result {
case .success(let posts):
self.posts = posts
if !posts.isEmpty && posts.count > 10 {
self.filteredPosts = Array(posts[0..<10]).reversed()
}
case .failure(let error):
print(error.description)
}
}
} catch {
print(NetworkError.requestFailed)
}
}
}
}
HomeViewController
의 fetchPosts
함수에서 서버 요청 함수는 Task
를 이용하여 작업을 background Thread
에서 실행하고, 요청이 끝난 후, 응답받은 데이터를 데이터 바인딩하는 작업은 MainActor
로 수행했습니다.
샘플 코드는 다음과 같습니다.