Struct 기반 Validator와 유효성 검사

Eden·2024년 12월 19일

iOS

목록 보기
13/18

회원가입 화면에서 입력 필드의 유효성을 검증해야 했다. 이를 위해 중복 코드 없이 재사용 가능한 방식으로 유효성 검사를 구현하고자 했다.

유효성 검증을 각각의 필드에서 개별적으로 수행하여 코드가 중복되고 관리가 어려울 수 있어서 고민을 많이 하고 구현했다.

1. 구현 과정

1) 유효성 검사 로직의 추출과 정리

  • 초기에는 뷰컨트롤러 내부에서 각 필드에 대해 직접 유효성 검사를 수행하려고 했다.
  • 코드 중복이 많아 유지보수가 어려워 보여, 유효성 검사를 독립적인 Validator 구조체로 분리하기로 결정했다.

2) Validator 구조체 설계

  • 재사용성을 높이기 위해 validateFields 메서드에서 모든 필드를 한꺼번에 검사할 수 있도록 설계했다.
  • 각 필드의 조건은 개별 메서드(isValidId, isValidPassword, isValidEmail, isValidPhone)로 캡슐화했다.

3) 정규식 활용

  • 정규식을 사용하여 각 필드의 유효성 조건을 명확히 정의했다.
  • NSPredicate를 활용해 정규식을 적용했다.

4) 뷰컨트롤러와의 연동

  • SignupViewController에서 Validator를 호출하여 사용자가 입력한 값의 유효성을 검사했다.
  • 검사 결과에 따라 경고 메시지를 사용자에게 보여주도록 구현했다.

2. 최종 구현 코드

Validator 구조체

struct Validator {
    func validateFields(id: String?, password: String?, email: String?, phone: String?) -> (Bool, String) {
        if let id = id, isValidId(id) == false {
            return (false, "아이디는 영어와 숫자 조합이어야 합니다.")
        }
        if let password = password, isValidPassword(password) == false {
            return (false, "비밀번호는 영어, 숫자 포함 8자 이상이어야 합니다.")
        }
        if let email = email, isValidEmail(email) == false {
            return (false, "유효하지 않은 이메일 형식입니다.")
        }
        if let phone = phone, isValidPhone(phone) == false {
            return (false, "휴대폰 번호는 010으로 시작해야 합니다.")
        }
        return (true, "유효성 검사를 통과했습니다.")
    }

    private func isValidId(_ id: String) -> Bool {
        let regex = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{1,}$"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: id)
    }

    private func isValidPassword(_ password: String) -> Bool {
        let regex = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*?&#]{8,}$"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: password)
    }

    private func isValidEmail(_ email: String) -> Bool {
        let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: email)
    }

    private func isValidPhone(_ phone: String) -> Bool {
        let regex = "^010\\d{8}$"
        return NSPredicate(format: "SELF MATCHES %@", regex).evaluate(with: phone)
    }
}

SignupViewController

@objc
private func signup() {
    guard let id = signupView.idField.text,
          let password = signupView.passwordField.text,
          let email = signupView.emailField.text,
          let phone = signupView.phoneField.text else {
        showAlert(message: "모든 필드를 입력해 주세요.")
        return
    }

    let (isValid, message) = validator.validateFields(id: id, password: password, email: email, phone: phone)
    if isValid {
        showAlert(message: "회원가입이 완료되었습니다.")
    } else {
        showAlert(message: message)
    }
}

3. 배운 점

구조체 활용: Validator 구조체를 통해 로직 분리와 재사용성을 강화할 수 있었다.
정규식의 강력함: 각 필드의 유효성 조건을 간결하고 명확하게 정의할 수 있었다.
확장 가능성: 새로운 필드가 추가되어도 Validator 구조체만 수정하면 전체 유효성 검사가 가능하도록 설계할 수 있었다.

4. 구현 후 느낀 점

구현 과정에서 정규식을 정확히 작성하는 것이 가장 어려웠다. 초기에는 복잡한 패턴을 작성하고 이를 디버깅하는 데 많은 시간을 소요했지만, 여러 번의 테스트를 통해 정규식을 체계적으로 관리하는 방법을 터득할 수 있었다. 한 번 구조를 잡아놓으니 유지보수와 확장이 훨씬 쉬워졌다.

Validator 구조체를 독립적으로 설계한 덕분에 각 필드별로 단위 테스트를 작성하는 것이 매우 수월했다. 이를 통해 유효성 검사 로직의 안정성을 높이고, 전체적인 코드 품질을 강화할 수 있었다. 또한, 잘못된 입력값에 대한 경고 메시지를 명확히 전달하여 사용자에게 친화적인 회원가입 프로세스를 제공할 수 있었다.

무엇보다 회원가입 화면 외의 다른 화면에서도 동일한 Validator를 재사용할 수 있는 설계 덕분에, 초기 설계 단계에서의 고민과 노력이 충분히 보상받는다는 것을 느꼈다.

profile
🌐 Frontend &&  iOS && 대학생

0개의 댓글