iyOmSd/Title: RxSwift

[RxSwift 기초] 로그인창을 입력에 따른 반응으로 구현하기

냄수 2019. 8. 4. 17:39
반응형

안녕하세요😄😄

 

요즘들어 Rx가 핫하더라구요

그래서 저도 궁금하기도하고 배워놓으면 정말 좋을 것 같기 때문에 유투브를 보면서 처음으로 공부해 봤어요

 

 

곰튀김님의 RxSwift 강의를 보면서 기초를 배웠어요

 

 

언제나 그렇듯 직접 말로 하기보다는

실전으로 연습 해볼게요

 

 

우선 제일처음으로 프로젝트를 만들어주고

프로젝트 폴더에서

pod init 명령어를 치면

이렇게 Podfile이라는 파일이 생겨요

그 파일을 클릭해서

pod 'RxSwift'

pod 'RxCocoa' 를 추가해줘요

이렇게 되면 RxSwift를 시작할 준비가 됬어요!

옆에 '~> 5' 는 버전 고정을 뜻하는거에요

 

다 쓴 뒤에는 pod install 명령어로 라이브러리를 설치해 주면 끝이에요

 

 

 

 

 

 

 

스토리뷰 구성이에요

 

아이디와 비밀번호의 형식을주고 입력에 따라 실시간으로 체크해서

오른쪽의 빨간불이 바뀌게 될거에요

 

Delegate를 이용하고 Action을 이용해서 이것저것 손댓어야 할거에요

하지만 이제 RxSwitf를 공부하니까 

RxSwift로 할거에요!!

 

RxSwift가 왜 만들어졌겠어요??

더 편리하게 쓰려고 만들었겠죠!!

 

 

개념설명을 봐도 전 이해하기 어려웠어요

처음보는 개념에 문법에...

우선 실행결과를 보면서

아~ 이런거구나 느낌을 살펴볼게요!

 

 

아래의 코드를 실행 시켜볼거에요

idTextField.rx.text
            .subscribe(onNext: { s in
                print(s)
            })

 

그러면 아이디창에 12345를 칠때마다 각각 로그가 입력되는걸 볼 수 있어요

 

subscribe가 옵저버역할을 하면서 값이바뀌면 받아와서 onNext로 전달해주는거 같아요

 

 

 

 

 

다른 코드를 볼게요

    private func bindUI() {
        
        idTextField.rx.text
            .filter{ $0 != nil }  //nil 아닌경우에만 내려감
            .map { $0! }    // 강제 언랩핑
            .map(checkId)   // 아이디 유형체크
            .subscribe(onNext: { s in   // s: true or false
                print(s)
            })
            .disposed(by: disposeBag)
    }

    private func checkId(_ email: String) -> Bool {
        return email.contains("@") && email.contains(".")
    }
    
    
    /////////////// nil체크와 강제 언랩핑을 해주는 기능 ////////////////
    /////////////// 위와 같은 기능의 코드 입니다 ///////////////
    
    private func bindUI() {
        
        idTextField.rx.text.orEmpty    <<<<<<< orEmpty
            .map(checkId)   // 아이디 유형체크
            .subscribe(onNext: { s in   // s: true or false
                print(s)
            })
            .disposed(by: disposeBag)
    }

 

아래는 실행 시킨 결과에요

 

filter에서 조건을 만족하지 않으면 그 다음 스트림(아래문장)을 실행시키지않아요

map은 들어온 데이터를 사용자가 정의한 모양에 맞게 새로운 데이터로 맵핑해주는 역할을해요

'123@.'에서 '.'이 들어올때 딱 true가 정상적으로 뜨는걸 볼수 있어요

 

 

 

 

이코드를 응용해서

형식에 맞는 아이디가 들어오면 파란불, 맞지않으면 빨간불을 켜보도록 할게요

 

    private func bindUI() {
        
        idTextField.rx.text.orEmpty
            .map(checkId)   // 아이디 유형체크
            .subscribe(onNext: { s in   // s: true or false
                if s {   //아이디 형식이 맞는경우
                    self.idColorView.backgroundColor = UIColor.blue
                }else {  //아이디 형식 아닌경우
                    self.idColorView.backgroundColor = UIColor.red
                }
            })
            .disposed(by: disposeBag)
    }

@를 치고 딱 . 를 치는순간 색이 변하는걸 볼 수 있어요

벌써부터 RxSwift가 좋은거 같다는 느낌이 들어요

 

마찬가지로 비밀번호 체크도 위의 방식대로 똑같이 하시면 되요

 

 

 

이제 버튼쪽을 해볼거에요

 

id와pw 둘다 맞아야 로그인 버튼을 누를 수 있도록 해줄거에요

Observable.combineLatest(
        idTextField.rx.text.orEmpty.map(checkId), //아이디 유형을 체크한 값이 내려옴 true/false
        pwTextField.rx.text.orEmpty.map(checkPw),
        resultSelector: {s1, s2 in s1 && s2}
    )
    .subscribe(onNext: {s in  // s1 && s2 결과값
        self.loginBtn.isEnabled = s
    })
    .disposed(by: disposeBag)
       

여러개의 Observable을 합쳐서 결과를 내주는 기능을 알아볼게요

  • zip
    하나가 변하면 대기상태
    둘다 바껴야 실행되는함수
  • combineLatest
    하나라도 바뀌면 실행
    두 개의 상태를 모두 볼 수 있음
  • merge
    2개를 받아서 내려보내는게아니라
    변하는대로 내려보냄
    두 개의 상태를 볼 수 없음
    (기다리지않고 바로바로 하나하나 들어올 때 마다 실행)

여기서는 아이디창과 비밀번호창의 결과를 받아서 하나라도 바뀌면 버튼에 적용해야 하므로

combineLatest가 어울려요

 

 

 

리팩토링

위의 코드를 간결하게 기능을 나눠서 리팩토링을 하면은!!

 

왼쪽: 리팩토링전 오른쪽: 리팩토링후

 

 

 

 

이제는 함수 외부에 값을 저장하도록 해볼게요

 

//BehaviorSubject는 최근값을 저장해주는 역할 (처음값)디폴트 설정가능
let idValid :BehaviorSubject<Bool> = BehaviorSubject(value: false)
let pwValid :BehaviorSubject<Bool> = BehaviorSubject(value: false)
    
private func bindUI() {
        
    //input  아이디 입력, 비밀번호 입력
    let idInputOb = idTextField.rx.text.orEmpty.asObservable()
    let idCheckOb = idInputOb.map(checkId)
    
    // 외부변수 idValid에 저장하는 방법1
    idCheckOb.subscribe(onNext: {b in
        self.idValid.onNext(b)
    })
    
    // 외부변수 idValid에 저장하는 방법2
    idCheckOb.bind(to: idValid)
    
    // idCheckOb를 사용안하고 바로 외부에 저장
    idInputOb.map(checkId)
        .bind(to: idValid)
        .disposed(by: disposeBag)
    
}

 

onNext 와 bind를 통해서 외부에 있는 변수로 값을 저장할 수 있어요

저런 방식을 사용한다면 이젠

idCheckOb변수가 필요없이 바로 값을 저장해서 사용 할 수 있겠네요

 

 

 

이런 방식을 사용해서 코드를 다시 구현 해볼게요

조금 혼잡 할 수 있어요!!

//BehaviorSubject는 최근값을 저장해주는 역할 (처음값)디폴트 설정가능
    let idValid :BehaviorSubject<Bool> = BehaviorSubject(value: false)
    let pwValid :BehaviorSubject<Bool> = BehaviorSubject(value: false)
    
    let idText :BehaviorSubject<String> = BehaviorSubject(value: "")
    let pwText :BehaviorSubject<String> = BehaviorSubject(value: "")
    
    private func bindUI() {
        
        //input  아이디 입력, 비밀번호 입력
        
        idTextField.rx.text.orEmpty
            .bind(to: idText)          // idText에 현재입력받은 문자열 저장
            .disposed(by: disposeBag)
        
        idText.map(checkId)            // 외부변수에 저장해놓은 문자열 이용
            .bind(to: idValid)         // idValid에 아이디 형식체크 결과값 저장
            .disposed(by: disposeBag)
        
//////////////////////////////////////////////////////////////////////

        외부변수에 저장하기 바꾸기전 코드와 비교

        let idInputOb = idTextField.rx.text.orEmpty.asObservable()
        let idCheckOb = idInputOb.map(checkId)

        idInputOb.map(checkId)
            .bind(to: idValid)
            .disposed(by: disposeBag)
//////////////////////////////////////////////////////////////////////
    }   

 

외부에 저장한 값을 가져와서 다시 bind를 통해 외부변수로 저장했어요

이 값을 output에서 이용할 거에요

 

let idValid :BehaviorSubject<Bool> = BehaviorSubject(value: false)
let pwValid :BehaviorSubject<Bool> = BehaviorSubject(value: false)
    
let idText :BehaviorSubject<String> = BehaviorSubject(value: "")
let pwText :BehaviorSubject<String> = BehaviorSubject(value: "")
    
private func bindInput() {
        
    //input  아이디 입력, 비밀번호 입력
        
    idTextField.rx.text.orEmpty
        .bind(to: idText)
        .disposed(by: disposeBag)
        
    idText.map(checkId)
        .bind(to: idValid)
        .disposed(by: disposeBag)
        
    pwTextField.rx.text.orEmpty
        .bind(to: pwText)
        .disposed(by: disposeBag)
        
    pwText.map(checkPw)
        .bind(to: pwValid)
        .disposed(by: disposeBag)
    }
    
 private func bindOutput(){
    //output 상태컬러, 로그인버튼 활성화
        
    idValid.subscribe(onNext: {b in
        if b {
            self.idColorView.backgroundColor = UIColor.blue
        }else {
            self.idColorView.backgroundColor = UIColor.red
        }
    })
        .disposed(by: disposeBag)
    
    pwValid.subscribe(onNext: {b in
        if b {
            self.pwColorView.backgroundColor = UIColor.blue
        }else {
            self.pwColorView.backgroundColor = UIColor.red
        }
    })
        .disposed(by: disposeBag)
    
    Observable.combineLatest(idValid, pwValid, resultSelector: {$0 && $1})
        .subscribe(onNext: {b in self.loginBtn.isEnabled = b})
        .disposed(by: disposeBag)
        
}

이렇게 input과 output을 구분지어 봤어요..!! 완성...!!!

 

잘몰라도 처음과 비교했을때 더 보기 좋은거 같이 느껴져요

 

 

 

 

 

 

RxSwift가 MVVM이라 불리는 디자인패턴과 잘 맞는다고하는데

아직 디자인패턴에 대한 지식이 부족해요

 

여기서 MVVM을 적용시켜서 더 좋은 코드로 나중에 만들어 볼게요!!

 

 

 

반응형