Skip to content

Latest commit

 

History

History
220 lines (187 loc) · 7.66 KB

File metadata and controls

220 lines (187 loc) · 7.66 KB

이글은 Learning RxSwift's Github Sample( http://zh-wang.github.io/blog/2015/10/27/learning-rxswifts-github-sample/ ) 에 대한 번역입니다.

RxSwift Github 샘플 배우기

소개

RwSwift 의 첫 예제는 Github 가입 view controller를 흉내냅니다. 이 예제는 사용자 이름, 패스워드에 대한 유효성을 확인합니다. 그런 다음 가입 절차를 가상으로 수행합니다.

사용자 입력 Listen 하기

Rx framework로 하기 가장 쉬운 일입니다. RxCocoa에 정의 되어 있는 UITextField 의 rx_text 필드가 여러분이 원하는 그것입니다.

let username = usernameOutlet.rx_text

rx_text는 Observable에 발생하는 모든 변경사항을 감싸는 textViewDidChange를 추적합니다. Observable 은 Observer에 의해서 관찰될(Observed) 수 있는 기초 이벤트 sequence 입니다.

각각의 입력 이벤트를 E라고 합시다. 그러면 이 이벤트 sequence는 다음과 같이 표시될 수 있습니다.
---E---E-E-----E-----

버튼에 대한 tap을 처리하는 것은 다음과 같이 쉽니다.

let signupSampler = self.signupOutlet.rx_tap

기초 Observable들 생성하기

일반적인 가입 절차에서 우리는 다음을 확인해야 합니다.

1.사용자 이름이 비어있거나, 허용되지 않는 글자가 포함되어 있다.
2.사용자 이름이 이미 가입되어 있다.
3.패스워드 와 패스워드 재입력이 같다.
4.요청이 적절하게 처리될 수 있다 혹은 그렇지 않다.

사용자 이름 확인하기

이것은 Observable에 대한 flatmap 함수 입니다.(flatmap 에 대한 정의는 이 글( http://zh-wang.github.io/blog/2015/10/09/functor-monad-applicative-in-swift/ )에서 찾을 수 있습니다.)

typealias ValidationResult = (valid: Bool?, message: String?)

func validateUsername(username: String) -> Observable<ValidationResult> {
    if username.characters.count == 0 {
        return just((false, nil))
    }

    // this obviously won't b
    if username.rangeOfCharacterFromSet(NSCharacterSet.alphanumericCharacterSet().invertedSet) != nil {
        return just((false, "Username can only contain numbers or digits"))
    }

    let loadingValue = (valid: nil as Bool?, message: "Checking availabilty ..." as String?)

    return API.usernameAvailable(username)
        .map { available in
            if available {
                return (true, "Username available")
            }
            else {
                return (false, "Username already taken")
            }
        }
        .startWith(loadingValue)
}

Observable에 감싸진 값은 Bool 과 String 의 짝(pair)입니다.

첫 두 if 절은 각기 비었거나 허용되지 않은 문자들에 대한 확인을 위한 것입니다.

나머지 부분은 짧은 시간 후에 결과를 돌려주는 http 요청입니다.

사용자 입력 이벤트 sequence 에 각각의 이벤트에 'validateUsername'를 call 를 한다면,

let usernameValidation = username
.map { username in
    return validationService.validateUsername(username)
}

이벤트 sequence는 이렇게 될 겁니다.(V는 유효성 체크, R은 결과)

---+---+-+-----+-----
   |   | |     |
   V   V V     V
   |   | |     |
   R   | |     |
       R |     R
         R

유효성 체크들이 순서대로 호출되지만, 결과들은 네트워크 상태에 따라 무작위 순서로 리턴됩니다. 그리고 실제로 우리는 가장 최신 유효성 체크의 결과만 필요합니다.

그래서 우리는 여기서 switch 메소드를 사용합니다. switchlatest는 switch의 구현체 중 하나 입니다. switchlatest 는 항상 가장 최근에 발생한 이벤트로 전환되고 이전의 이벤트들은 폐기할 겁니다. switch 에 대한 소개 ( http://www.introtorx.com/content/v1.0.10621.0/12_CombiningSequences.html#Switch )

그리고 shareReplay(1) 는 이 Observer가 새로운 구독(subscription)들을 나중에 하게되더라도 단 하나의 할당만 유지하게 됩니다. RxSwift replay( https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md#sharing-subscription-and-sharereplay-operator )

let usernameValidation = username
.map { username in
    return validationService.validateUsername(username)
}
.switchLatest()
.shareReplay(1)

2.패스워드와 패스워드 재입력 확인하기

func validatePassword(password: String) -> ValidationResult {
    let numberOfCharacters = password.characters.count
    if numberOfCharacters == 0 {
        return (false, nil)
    }

    if numberOfCharacters < minPasswordCount {
        return (false, "Password must be at least \(minPasswordCount) characters")
    }

    return (true, "Password acceptable")
}

func validateRepeatedPassword(password: String, repeatedPassword: String) -> ValidationResult {
    if repeatedPassword.characters.count == 0 {
        return (false, nil)
    }

    if repeatedPassword == password {
        return (true, "Password repeated")
    }
    else {
        return (false, "Password different")
    }
}

이들 두개의 Observable들을 combineLatest를 이용해서 합칩니다.(Combine) 이름 그대로 작동합니다.

let repeatPasswordValidation = combineLatest(password, repeatPassword) { (password, repeatedPassword) in
        validationService.validateRepeatedPassword(password, repeatedPassword: repeatedPassword)
    }
    .shareReplay(1)

3.가입 처리

let signingProcess = combineLatest(username, password) { ($0, $1) }
    .sampleLatest(signupSampler)
    .map { (username, password) in
        return API.signup(username, password: password)
    }
    .switchLatest()
    .startWith(SignupState.InitialState)
    .shareReplay(1)

signup 메소드는 단지 지연된(dalayed) Observable입니다. 2초후에 true 혹은 false를 리턴합니다.

func signup(username: String, password: String) -> Observable<SignupState> {
    // 이 가입 처리는 단지 가짜로 동작하는 겁니다.
    let signupResult = SignupState.SignedUp(signedUp: arc4random() % 5 == 0 ? false : true)
    return [just(signupResult), never()]
        .concat()
        .throttle(2, MainScheduler.sharedInstance)
        .startWith(SignupState.SigningUp)
}

4.가입 가능하게 하기

let signupEnabled = combineLatest(
    usernameValidation,
    passwordValidation,
    repeatPasswordValidation,
    signingProcess) { un, p, pr, signingState in

        return (un.valid ?? false) && (p.valid ?? false) && (pr.valid ?? false) && signingState != SignupState.SigningUp

}

Observer들을 UI에 연결하기

func bindValidationResultToUI(source: Observable<(valid: Bool?, message: String?)>,
    validationErrorLabel: UILabel) {
    source
        .subscribeNext { v in
            let validationColor: UIColor

            if let valid = v.valid {
                validationColor = valid ? okColor : errorColor
            }
            else {
               validationColor = UIColor.grayColor()
            }

            validationErrorLabel.textColor = validationColor
            validationErrorLabel.text = v.message ?? ""
        }
        .addDisposableTo(disposeBag)
}

그런 다음 Observer들을 outlet들에 연결합니다.

bindValidationResultToUI(
    usernameValidation,
    validationErrorLabel: self.usernameValidationOutlet
)

bindValidationResultToUI(
    passwordValidation,
    validationErrorLabel: self.passwordValidationOutlet
)

bindValidationResultToUI(
    repeatPasswordValidation,
    validationErrorLabel: self.repeatedPasswordValidationOutlet
)

viennakanon 에 의해서 작성됨.