홈 화면에 진입했을 때, 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)
            }
        }
    }
}

HomeViewControllerfetchPosts함수에서 서버 요청 함수는 Task를 이용하여 작업을 background Thread에서 실행하고, 요청이 끝난 후, 응답받은 데이터를 데이터 바인딩하는 작업은 MainActor로 수행했습니다.

샘플 코드는 다음과 같습니다.