Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- tableView
- clean architecture
- 리펙토링
- UITextView
- 클린 코드
- MVVM
- 애니메이션
- Human interface guide
- rxswift
- uitableview
- combine
- HIG
- UICollectionView
- collectionview
- ios
- Xcode
- swift documentation
- Clean Code
- RxCocoa
- Refactoring
- ribs
- uiscrollview
- map
- Protocol
- 스위프트
- 리펙터링
- SWIFT
- swiftUI
- Observable
- 리팩토링
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] API 호출 후 에러가 발생했을 때 Retry 구현 방법 (재시도 기능) 본문
* Alamofire를 사용했을 때 정석적인 방법은 retry 방법 참고
* 커스텀 팝업 구현 방법은 해당글 참고
retry 기능 아이디어
- 클로저 이용: `코드에서 전달하고 사용할 수 있는 2가지의 기능을 가진 자체 블록` - 호출하기 전까지 실행 대기 상태인 특성 이용
- 커스텀 팝업을 부르는 곳에 retry 클로저를 넘김으로써 retry가 필요할 경우, 버튼을 누른 completion에 적용
- 팝업은 ViewController 계층 구조상 가장 위쪽에 위치한 인스턴스에 present하는 방식
- 현재 보여지는 ViewController를 찾기 위해 UIWindow의 extension으로 연산 프로퍼티 정의
- visibleViewController 이름으로 외부에서 사용할 수 있도록 구현
extension UIWindow {
public var visibleViewController: UIViewController? {
return visibleViewControllerFrom(viewController: rootViewController)
}
static private var firstRootViewController: UIViewController? {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.map { $0 as? UIWindowScene }
.compactMap { $0 }
.first?.windows
.first(where: { $0.isKeyWindow })?.rootViewController
}
private func visibleViewControllerFrom(viewController: UIViewController? = firstRootViewController) -> UIViewController? {
if let navigationController = viewController as? UINavigationController {
return self.visibleViewControllerFrom(viewController: navigationController.visibleViewController)
} else if let tabBarController = viewController as? UITabBarController {
return self.visibleViewControllerFrom(viewController: tabBarController.selectedViewController)
} else {
if let presentedViewController = viewController?.presentedViewController {
return visibleViewControllerFrom(viewController: presentedViewController)
} else {
return viewController
}
}
}
}
- extension UIWindow로 정의한 프로퍼티를 `UIApplication.shared.windows.first?.visibleViewController`로 접근
- 버튼의 이름이 "재시도"이고, 사용하는쪽에서 retry를 위해 completion을 넘긴 경우 right 버튼을 눌렀을 때 해당 클로저가 실행되도록 설계
import UIKit
struct ErrorHandler {
static func showAlert(_ error: Error?,
leftActionCallback: (() -> Void)? = nil,
rightActionCallback: (() -> Void)? = nil,
completionDidTapRetryButton: (() -> Void)? = nil) {
guard let error = error as? ErrorType else { return }
let errorData: ErrorData
let leftActionTitle: String?
let rightActionTitle: String
switch error {
case .notConnectedToInternet(let data):
errorData = data
leftActionTitle = nil
rightActionTitle = "재시도"
case .unknown(let data):
errorData = data
leftActionTitle = nil
rightActionTitle = "확인"
}
showAlert(errorData,
leftActionTitle: leftActionTitle,
rightActionTitle: rightActionTitle,
leftActionCallback: leftActionCallback,
rightActionCallback: rightActionCallback,
completionDidTapRetryButton: completionDidTapRetryButton)
}
static func showAlert(_ errorData: ErrorData,
leftActionTitle: String? = nil,
rightActionTitle: String,
leftActionCallback: (() -> Void)? = nil,
rightActionCallback: (() -> Void)? = nil,
completionDidTapRetryButton: (() -> Void)? = nil) {
var rightActionCallback = rightActionCallback
if completionDidTapRetryButton != nil, rightActionTitle == "재시도" {
rightActionCallback = completionDidTapRetryButton
}
let rootViewController = UIApplication.shared.windows.first?.visibleViewController
DispatchQueue.main.async {
rootViewController?.showPopUp(title: errorData.title,
message: errorData.message,
leftActionTitle: leftActionTitle,
rightActionTitle: rightActionTitle,
leftActionCompletion: leftActionCallback,
rightActionCompletion: rightActionCallback)
}
}
}
테스트 용도의 API 정의
- 보통은 Error타입을 내려주기 때문에 아래처럼 ErrorData로 wrapping하는 작업도 필요하지만 테스트를 위해서 아래처럼 구현
enum ErrorType: Error {
case notConnectedToInternet(ErrorData)
case unknown(ErrorData)
}
struct ErrorData {
let title: String?
let message: String?
}
struct API {
static func someAPI(completion: @escaping (Result<Void, ErrorType>) -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion(.failure(ErrorType.notConnectedToInternet(ErrorData(title: "네트워크 연결 에러입니다.\n네트워크 설정을 확인해주세요", message: ""))))
}
}
}
사용하는 쪽
- completionDidTapRetryButton에 재실행할 코드를 넘기는 형태
func processBusinessLogic() {
print("start BusinessLogic !!")
API.someAPI { result in
switch result {
case .success: print("success!!")
case .failure(let error):
ErrorHandler.showAlert(error, completionDidTapRetryButton: { [weak self] in self?.processBusinessLogic() } )
}
}
}
* 전체 소스코드: https://github.com/JK0369/PopUpViewControllerExample
'iOS 응용 (swift)' 카테고리의 다른 글
Comments