[SwiftUI] Do, Try, Catch, Throws

Junyoung Park·2022년 8월 26일
0

SwiftUI

목록 보기
54/136
post-thumbnail

How to use Do, Try, Catch, and Throws in Swift | Swift Concurrency #1

Do, Try, Catch, Throws

구현 목표

  • 실패 가능한 함수의 리턴값을 핸들링하는 방법
  • Result 커스텀하기
  • throws를 통해 에러 throw하기
  • thorws 함수의 리턴값을 do catch를 통해 받기

구현 태스크

  1. 커스텀 Result를 리턴하는 함수 구현
  2. 커스텀 에러를 throw하는 throws 함수 구현
  3. throw를 받는 try, try?, try! 코드 구현

핵심 코드

    func getTitle3() -> Result<String, Error> {
        return isActive ? .success(returnedText) : .failure(DoCatchTryThrowError.getTitleError)
    }
    func getTitle4() throws -> String {
        if isActive {
            return returnedText
        } else {
            throw DoCatchTryThrowError.getTitleError
        }
    }
  • Result<Success, Failure> 타입을 통해 switch case로 핸들링 가능 → Combine 프레임워크 사용 시 sinkcompletion 사용 방법과 같음
  • throws: String을 리턴하면서 특정 상황에 따라 에러를 발생시키는 함수
    func fetchTitle3() {
        let completion = manager.getTitle3()
        switch completion {
        case .success(let returnedValue):
            self.text = returnedValue
        case .failure(let error):
            self.text = error.localizedDescription
        }
    }
    func fetchTitle4() {
        do {
            self.text = try manager.getTitle4()
        } catch {
            self.text = error.localizedDescription
        }
    }
    func fetchTitle5() {
        guard let text = try? manager.getTitle4() else {
            self.text = DoCatchTryThrowError.getTitleError.localizedDescription
            return }
        self.text = text
    }
  • getTitle3Result를 리턴하고 있기 때문에 switch case를 통해 받을 수 있음
  • getTitle4가 커스텀 에러 타입을 리턴하고 있기 때문에 do catch, try? 문을 통해 받을 수 있음
  • try?do catch 블럭이 아니더라도 구현 가능하지만 실패한다면 nil을 리턴하기 때문에 주의 → 어떤 에러가 리턴되었는지도 확인할 수 없기 때문에 일반적으로 do catch를 통해 구현하기
  • 강제 언래핑 방법은 지양

소스 코드

struct DoCatchTryThrowBootCamp: View {
    @StateObject private var viewModel: DoCatchTryThrowBootCampViewModel
    
    init(dataManager: DoCatchTryThrowBootCampProtocol) {
        _viewModel = StateObject(wrappedValue: DoCatchTryThrowBootCampViewModel(manager: dataManager))
        // Dependency Ingection
    }
    
    var body: some View {
        NavigationView {
            VStack(spacing: 30) {
                textBox
                activeToggleButton
            }
            .padding(.horizontal, 10)
            .padding(.vertical,100)
            .navigationTitle("DoCatchTryThrow")
        }
    }
}

extension DoCatchTryThrowBootCamp {
    private var textBox: some View {
        Text(viewModel.text)
            .font(.title)
            .fontWeight(.bold)
            .withDoCatchTryThorwBootCampViewModifier()
            .onTapGesture {
                viewModel.fetchTitle4()
            }
    }
    private var activeToggleButton: some View {
        Button {
            viewModel.manager.isActive.toggle()
            viewModel.text = "Default Text"
        } label: {
            Text("Active Toggle : \(viewModel.manager.isActive.description)")
                .foregroundColor(.black)
                .font(.title)
                .fontWeight(.bold)
                .withDoCatchTryThorwBootCampViewModifier(backgroundColor: .red.opacity(0.3))
                .frame(height: 70)
        }
    }
}

struct DoCatchTryThrowBootCampViewModifier: ViewModifier {
    let backgroundColor: Color
    func body(content: Content) -> some View {
        content
            .frame(maxWidth: .infinity)
            .frame(maxHeight: .infinity)
            .background(backgroundColor)
            .cornerRadius(10)
    }
}

extension View {
    func withDoCatchTryThorwBootCampViewModifier(backgroundColor: Color = Color.blue.opacity(0.7)) -> some View {
        modifier(DoCatchTryThrowBootCampViewModifier(backgroundColor: backgroundColor))
    }
}
  • 데이터 서비스 클래스의 isAcitve 변수를 토글링하는 하단 버튼
  • 데이터 서비스 클래스가 리턴하는 값을 보여주는 텍스트 뷰
  • 데이터 서비스 클래스의 싱글턴 패턴 구현을 지양하고 의존성 주입을 통해 구현(프로토콜 활용)
class DoCatchTryThrowBootCampViewModel: ObservableObject {
    @Published var text: String = "Default Text"
    var manager: DoCatchTryThrowBootCampProtocol
    
    init(manager: DoCatchTryThrowBootCampProtocol) {
        self.manager = manager
    }
    func fetchTitle() {
        guard let text = manager.getTitle() else { return }
        self.text = text
    }
    func fetchTitle2() {
        let returnedValue = manager.getTitle2()
        if let text = returnedValue.title {
            self.text = text
        } else if let error = returnedValue.error {
            self.text = error.localizedDescription
        }
    }
    func fetchTitle3() {
        let completion = manager.getTitle3()
        switch completion {
        case .success(let returnedValue):
            self.text = returnedValue
        case .failure(let error):
            self.text = error.localizedDescription
        }
    }
    func fetchTitle4() {
        do {
            self.text = try manager.getTitle4()
        } catch {
            self.text = error.localizedDescription
        }
    }
    func fetchTitle5() {
        guard let text = try? manager.getTitle4() else {
            self.text = DoCatchTryThrowError.getTitleError.localizedDescription
            return }
        self.text = text
    }
    func fetchTitle6() {
        do {
            self.text = try manager.getTitle4()
            self.text = try manager.getTitle5()
            // getTitle4 -> try success, getTitle5 -> try fail
            // then catch block
        } catch {
            self.text = error.localizedDescription
        }
    }
    func fetchTitle7() {
        do {
            if let text = try? manager.getTitle5() {
                self.text = text
            }
            self.text = try manager.getTitle4()
            // getTitle4 -> try success, getTitle5 -> try fail but returned nil -> return getTitle4's value
        } catch {
            self.text = error.localizedDescription
        }
    }
}
  • UI 뷰와 데이터 서비스 클래스 간의 데이터 바인딩을 담당하는 뷰 모델
  • 데이터 서비스 클래스의 담당 프로토콜을 파라미터로 받고 있기 때문에 DoCatchTryThrowBootCampDataManager가 아니라 DoCatchTryThrowBootCampDataProtocol이 가지고 있는 함수만 사용하다는 데 주의
import SwiftUI

protocol DoCatchTryThrowBootCampProtocol {
    var isActive: Bool { get set}
    func getTitle() -> String?
    func getTitle2() -> (title: String?, error: Error?)
    func getTitle3() -> Result<String, Error>
    func getTitle4() throws -> String
    func getTitle5() throws -> String
}

enum DoCatchTryThrowError: LocalizedError {
    case getTitleError
}

class DoCatchTryThrowBootCampDataManager: DoCatchTryThrowBootCampProtocol {
    var isActive: Bool = true
    let returnedText: String = "Returned Text"
    func getTitle() -> String? {
        return isActive ? returnedText : nil
    }
    func getTitle2() -> (title: String?, error: Error?) {
        return isActive ? (returnedText, nil) : (nil, DoCatchTryThrowError.getTitleError)
    }
    func getTitle3() -> Result<String, Error> {
        return isActive ? .success(returnedText) : .failure(DoCatchTryThrowError.getTitleError)
    }
    func getTitle4() throws -> String {
        if isActive {
            return returnedText
        } else {
            throw DoCatchTryThrowError.getTitleError
        }
    }
    func getTitle5() throws -> String {
        throw DoCatchTryThrowError.getTitleError
    }
}
  • 의존성 주입을 위한 데이터 서비스 담당 함수 및 변수의 프로토콜화(isActive 토글링을 위한 get set 설정)
  • 커스텀 에러 이넘

구현 화면

에러를 throws하는 함수의 리턴값을 핸들링하는 것은 특히 비동기 코드를 작성할 때 확인해야 할 점이다!

profile
JUST DO IT

0개의 댓글