Async Await Refactoring
기존의 비동기 처리 방식을
(escaping completion 을 RxSwift single로 리팩토링 이후 해당 Single 함수를 Fetch 함수 내부에서 사용)
async await 비동기 처리 방식으로 refactoring 하는 과정입니다.
- 기존 코드 (escaping completion + RxSwift single)
Data - Repository - Search - Search User List API 의 일부
// Data - Repository - Search - search user list 함수 예시
private func lookUpRequest(userId: String,
keyword: String,
page: Int32 = 1,
capacity: Int32 = 30,
completion: @escaping (Result<Search_V1_LookUpResponse, Error>) -> Void){
var request = Search_V1_LookUpRequest()
request.userID = userId
request.word = keyword
request.page = page
request.limit = capacity
request.notCompleted = false
let method = lookUp
gRPCRequest.fetch(method: method, reqeust: request)
}
//위의 @escaping completion 비동기 처리를 RxSwift Single로 처리해준 모습
func lookUpRequestRxSingle(userId: String,
keyword: String) -> Single<Search_V1_LookUpResponse> {
return Single<Search_V1_LookUpResponse>.create { single in
self.lookUpRequest(userId: userId, keyword: keyword) { result in
switch result {
case let .success(data):
single(.success(data))
case let .failure(error):
single(.failure(error))
}
}
return Disposables.create()
}
.subscribe(on: ConcurrentDispatchQueueScheduler(qos: .default))
.observe(on: MainScheduler.instance)
}
변경 된 코드 (async await로 refactoring)
// Data - Repository - Search - search user list 함수 예시
func lookUpRequest(userId: String,
keyword: String,
page: Int32 = 1,
capacity: Int32 = 30) async throws -> Search_V1_LookUpResponse{
var request = Search_V1_LookUpRequest()
request.userID = userId
request.word = keyword
request.page = page
request.limit = capacity
request.notCompleted = false
let method = lookUp
let res = try await gRPCRequest.fetch(method: method, reqeust: request)
return res
}
+ 추가적인 gRPCRequest.fetch 함수 정의 입니다.
class grpcRequestUtil {
static let shared = grpcRequestUtil()
private init() { }
static func fetch<T,R,C>(method: (T,C?) -> UnaryCall<T,R>, reqeust: T, completion: @escaping (Result<R, Error>) -> Void) {
let call = method(reqeust, nil)
do{
// let startTime = CFAbsoluteTimeGetCurrent()
// print("grpc req success: \\(type(of: T.self)) : \\(reqeust)")
let res = try call.response.wait()
// print("grpc res success: \\(type(of: R.self)) : \\(res)")
// let durationTime = CFAbsoluteTimeGetCurrent() - startTime
// print("grpc duration time \\(type(of: R.self)) : \\(durationTime)")
completion(.success(res))
}catch {
// print("grpc res failure: \\(type(of: R.self))")
completion(.failure(error))
}
}
모든 gRPC 통신 API 함수가
let res = method(request,nil).response.wait() 가 이루어지는데 (method, request - 변하는 값)
모든 함수의 비동기 처리 시간 측정을 제어하고자 추론 타입 함수(Generic func type)으로 구현
위에서 정의한
Data - Repository의 Search API를
ViewModel 에서 함수를 사용할때 코드 입니다
기존 코드 (ViewModel)
// search user list
func fetchSearchUserList(userId: String, keyword: String) {
let searchProto = searchProto(channel: self.channel)
searchProto.lookUpRequestRxSingle(userId: userId, keyword: keyword)
.subscribe { [unowned self] res in
self.input.searchUserList.onNext(res.format.users)
} onFailure: { err in
debugPrint("🔵fetchSearchUserList err : \\(err)")
self.input.searchUserList.onNext([])
} onDisposed: {
// print("disposed: searchProto Channel Closed")
}
.disposed(by: disposeBag)
}
변경된 코드 (ViewModel)
// search user list
func fetchSearchUserList(userId: String, keyword: String) async {
let searchProto = SearchProto(channel: self.channel)
do {
let res = try await searchProto.lookUpRequest(userId: userId, keyword: keyword)
input.searchUserList.onNext(res.format.users)
} catch {
debugPrint("🔵fetchSearchUserList err : \\(error)")
}
}
특이사항 - ARC관련해서 self capture 불필요 (self.input.searchUserList → input.searchUserList)
escaping completion 내부 변수 접근 메모리 할당 관련해서
비동기 호출 리턴이 완료되면 클로져 자동 해제 → 따로 weak self 처럼 순환 참조 처리x
async await 의 weak self 관련 참조
https://stackoverflow.com/questions/71728943/async-await-task-and-weak-self
+ 추가적으로
Task 문법을 사용하여 비동기 함수들을 동기로 처리하기가 쉽다.
Task {
let a = await 비동기함수1()
let b = await 비동기함수2(a)
let c = await 비동기함수3(b)
return c
}
간단하게 작성하자면 이런식으로 비동기 함수를 동기형태로 처리 가능하다. (기존에는 콜백의 콜백 형태로 구현)