티스토리 뷰

iOS & swift

Async Await Refactoring

ggasoon2 2023. 4. 12. 10:51

 

 

기존의 비동기 처리 방식을

(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
}

 

간단하게 작성하자면 이런식으로 비동기 함수를 동기형태로 처리 가능하다. (기존에는 콜백의 콜백 형태로 구현)

 

댓글