[WWDC 19 / Combine] Combine 연습하기 - Integrating Combine

박준혁 - Niro·2023년 10월 24일
1

WWDC

목록 보기
5/11
post-thumbnail

1. 들어가기 앞서서...

Combine 은 조합 (Composition) 을 염두하여 설계되었다고 합니다.

앞서 연습예제를 보았듯이, 우리는아주 작은 Publisher 를 가져와서 다양한 변환을 걸쳐 원하는 최종의 Publisher 의 만들었습니다.

이제는 Wizard 학교에 가입할 수 있는 앱을 만들려고 합니다.
그전에 몇가지 요구사항이 있는데

  1. 사용자 이름이 서버에서 유효한지 확인
  2. 비밀번호 필드와 비밀번호 확인 필드가 일치하고 8자 이상인지 확인
  3. 이러한 모든 조건을 충족하는 경우 UI 를 활성화 또는 비활성화 하여야 한다.

이미 우리의 예시에는 비동기 동작이 포함되어 있고 로컬에 동기적인 동작도 포함되어 있기 때문에 동작을 결합해야 합니다.

자, 이제 Combine 이 어떻게 도움이 되는지 살펴봅시다.

먼저, Interface Builder 를 사용하여 비밀번호 필드의 값 변경 속성에 대한 타겟 액션을 설정합니다.

코드로 값을 얻을 수 있도록 다음과 같이 작성합니다.
해당 값은 text 속성을 통해 가져와 각 변수에 저장합니다.

우리는 이러한 코드를 동기식 동작으로 구성해보려고 하는데 어떻게 할까요?

바로 개별 속성에 Publisehd 를 추가하고 Pubslisher 를 추가 할 수 있습니다!


2. Published

Published 는 새로운 Swift 5.1 기능을 사용하는 property wrapper 이고, 간단한 예제와 함께 주어진 속성에 Publisher 를 추가하는 방법을 살펴보겠습니다.

Published property wrapper 는 Publisher 를 추가하려는 속성 앞에 추가됩니다.

코드에서 사용할 때 이전과 같이 작동합니다. 또한 저장할 수 있으며 문자열 값을 얻을 수도 있습니다. 이 경우에는 currentPassword 는 이제 문자열 "1234"입니다.

특별한 경우에는 해당 속성을 달러($) 접두사로 참조합니다.

이 경우에는 래핑된 값에 액세스하고 Publisher 또는 Subscription 에 대한 모든 연산자를 사용할 수 있으며, 이 경우 sink 를 사용하여 해당 속성을 구독합니다.

그리고 해당 속성을 다시 다른 좋은 비밀번호 "password"로 설정하면 해당 속성이 변경될 때 구독자가 해당 값을 받게 됩니다.

우리는 두개의 Publisher 를 동시에 평가해야만 합니다.

우리가 원하는 것은 검증된 단일 비밀번호를 원하는데 이런 종류의 시나리오에 대한 연산자가 있으며 CombineLatest 라고 합니다.


3. Combinelatest

CombineLatest 를 사용하면 속성 래퍼를 달러 기호 접두사와 함께 참조하고 두 속성 중 하나가 변경될 때 전달받게 됩니다.

예를 들어 사용자가 이미 비밀번호 필드에 입력한 후 비밀번호 확인 필드에 입력하기 시작하면 passwordAgain 은 변경되는 동안 password 는 처음 필드에 입력한 원래 값이 될 것입니다.

그런 다음 클로저를 사용하여 비즈니스 요구 사항을 충족하는지 확인할 수 있습니다.

이 경우 두 값이 모두 일치하고 8자 이상인 경우입니다. 그렇지 않으면 nil을 반환합니다.

양식이 유효한지 여부를 결정하기 위해 다른 전달과 함께 해당 전달값을 사용할 것이므로 그렇지 않은경우 nil을 사용합니다.

유형에서 볼 수 있듯 우리가 수행한 모든 단계를 반영하듯이 기본적으로 코드와 똑같이 읽을 수있습니다. 두 가지 published 문자열을 가져와서 가장 최근 값을 결합하여 optional 문자열을 얻게 되었습니다.

그런데 만약 사용자가 나쁜 비밀번호를 사용하지 않도록 하는 요구 사항이 추가되고 map 을 추가하려고 한다면 어떻게 될까요?

이제 두 개의 published 문자열의 최신 값을 결합하고 매핑하여 optional 문자열을 얻게됩니다. 여기서 유형이 변경되는 것을 볼 수 있습니다.

그러나 이 경우에는 이것을 API boundary 로 공개하고 다른 Publisher 와 조합하려고 합니다.

그렇다면 중요한 것에만 집중할 수 있다면 좋지 않을까요?


4. eraseToAnyPublisher

그러기 위해 eraseToAnyPublisher 라는 연산자가 있으며 이는 optional 문자열과 Never 를 반환하는 AnyPublisher 를 생성합니다.

유형이 변경되지 않았다는 것을 알 수 있고 API 경계에서 원하는 정확한 조건을 알리고 구현 세부 정보를 숨길 수 있음을 의미합니다.

지금까지 우리는 문자열인 초기 속성을 가져와서 Published property wrapper 를 사용하여 문자열 Publisher 를 추가했습니다.

그런 다음 CombineLatest 를 사용하여 이 두 Publisher 의 최신 값을 결합하고 비즈니스 논리를 추가했습니다.

그런 다음 나쁜 비밀번호를 필터링하기 위해 map 을 사용하고 마지막으로 eraseToAnyPublishser 를 사용했습니다. 이것은 API 경계이므로 이것을 다른 것과 조합할 것입니다.

다음으로 여기서 모델링 하고 싶은 몇가지 비동기 활동들이 있습니다.

사용자가 빠르게 입력할 것으로 예상되는 사용자 이름을 서버에서 유효한지 확인하려고 합니다.

이전과 같이 문자열 속성 저장에 Published 를 추가하고 valueChanged 속성에 대한 타겟 액션을 연결합니다.

그러나 사용자가 한 문자를 입력할 때마다 네트워크 작업이 발생하지 않도록하려고 합니다. 그렇지 않으면 서버에 스팸을 날릴 것입니다.

우리가 원하는 것은 신호를 조금 더 부드럽게 만드는 것입니다. 이를 위해 debounce 를 사용할 수 있습니다.


5. debounce

Debounce 를 사용하면 값을 수신하려는 창을 지정하고 해당 창보다 빠르게 값을 수신하지 않도록할 수 있습니다. 작동 방식을 예를 통해 살펴보겠습니다.

upstream Publisher 가 있는데 이 경우 텍스트 필드가 될 것이며 가운데에는 debounce 가 있습니다. 사용자가 빠르게 타이핑하면 빠른 신호를 볼 수 있습니다.

하지만 해당 창 내에 단일 신호를 갖도록 이를 매끄럽게 만들 수 있어야 합니다.

사용자가 해당 창 내에서 입력하고 마지막 값이 항상 동일하다면 사용자 이름이 유효한지 확인하기 위해 서버를 다시 방문할 필요가 없습니다.

따라서 사용자가 Merlin 을 입력하면 해당 값을 얻고 n 을 삭제하고 n 을 다시 입력하고 Merlin 을 다시 입력하면 서버에 다시 접속할 필요가 없습니다.

removeDuplicates 가 그 용도로 사용되는 연산자로 해당 창 내에서 동일한 값을 반복해서 게시하지 않도록합니다.


6. removeDuplicates

코드에서 사용하면 Published 를 추가한 username 속성이 있습니다.

그런 다음 debounce 를 사용하여 신호를 조금 더 부드럽게 만듭니다. 마지막으로 중복 항목을 제거하려합니다.

하지만 우리는 아직 비동기식 작업을 처리하지 않았고 방금 신호를 부드럽게 만들었습니다.

우리는 실제로 서버에 연결하고 이것이 유효한지 여부를 확인하려고 합니다.

이를 위해 usernameAvailable 이라는 기존 함수가 있습니다.

하지만 여기서 하고싶은 것은 이것을 Publisher 로 가져오려고 합니다.

Michael 의 예제에서 알 수 있듯이 flatMap 을 사용하면 스트림에서 값을 가져온 다음 새로운 Publisher를 반환할 수 있다는 것을 배웠습니다. 그렇다면 어떻게 호출할 수 있을까요?

이를 위해 Future 라는 것이 있으며 생성할 때 promise 를 사용하는 클로저를 제공합니다.

promise 는 결과를 성공 또는 실패로 제공하는 다른 클로저일 뿐입니다.

사용할 때는 꽤 간단합니다.

usernameAvailable 함수를 호출하고 비동기적으로 완료되면 값이 있는 경우에는 성공으로 promise 를 채우고 이전과 마찬가지로 사용할 수 없는 경우 nil 로 명시합니다.


7. 중간 요약

이러한 단계를 검토하면 처음에는 간단한 Publishers 라는 username Publisher 가 있었습니다.

신호를 부드럽게 만들기 위해 debounce 를 사용하여 그 신호를 조절하고 해당 창 내에서 중복을 제거했습니다.

그런 다음 비동기 네트워크 호출을 수행하는 기존의 API를 래핑하는 Future 를 사용하고 스트림을 분기하기 위해 flatMap 을 사용했습니다.

그런 다음 API 경계이므로 eraseToAnyPublisher 를 사용하여 모든 Publisher 에서 이를 지웠습니다.


8. 마지막 기능!

이제 이 두 가지 사용자 지정 Publisher, 즉 validatedPasswordvalidatedUsername 이 준비되었습니다. 그리고 이들을 결합하려고 합니다.

이제 우리가 하려는 것은 이 두 가지 신호, 하나는 기기 내에서 로컬이고 다른 하나는 비동기 네트워크 호출인 것을 사용하여 UI를 활성화하거나 비활성화하는 것입니다.

잘 알고 있는 대로 CombineLatest 연산자를 사용합니다.

우리가 이전에 만든 두개의 Publisher 를 선택하고 그것들이 유효한지 확인하고 optional 인 tuple 로 반환하거나 유효하지 않으면 nil 을 반환합니다.

이것을 UI에 연결하는 것은 꽤 간단합니다. Sign Up 버튼에 아울렛을 연결합니다. 이 구독을 저장하기 위해 변수를 만들어 ViewController 의 전체 수명 동안 유지합니다.

이렇게 하면 폼이 표시될 때마다 버튼 또는 전체 시간 동안 활성화 및 비활성화되도록 유지할 수 있습니다.

앞서 선언한 Pubulisher 을 signupButtonStream 에 저장할 것입니다. 이것을 버튼의 isEnabled 속성에 할당하기 위해 Boolean 으로 매핑하려고 합니다.

마지막으로 receive(on:) 을 사용하여 UI 코드를 위해 필요한 주 실행 루프로 전환하고 assign 연산자를 사용하여 주어진 key path(on: signupButton) 에 할당하려고 합니다.

조금 물러나보면 처음에는 문자열을 발행하는 이 세 가지 매우 간단한 Publisher 로 시작했습니다.

그런 다음 composition (구성) 을 사용하여 작은 단계로부터 이러한 것들을 빌드하고 나서 그것들을 조합하고 버튼에 할당하기 위해 그것들을 연결했습니다.


마치며..

이것이 정확히 Combine 의 의미이고 바로 시작하면 좋습니다!

애플리케이션의 작은 부분을 사용자 지정 Publisher 로 구성하고 작은 조각의 논리를 작은 작은 Publisher로 분해할 수 있는 작은 부분을 식별하고 composition 을 사용하여 그 모든 것을 연결할 수 있습니다.

모든 것을 한번에 변경할 필요가 없으며 혼합하여 사용할 수도 있습니다!

profile
📱iOS Developer, 🍎 Apple Developer Academy @ POSTECH 1st, 💻 DO SOPT 33th iOS Part

0개의 댓글

관련 채용 정보