Replies: 1 comment 1 reply
-
https://github.com/Lia316/Login-MVVM-Combine 제 깃헙 레포에 전체 코드를 올려놨으니, 참고하실 분들은 여기로 가시면 됩니다! |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
🎁 훨씬 보기 편한 Notion 링크
컴바인 공부
🍄
로그인 예제
로 텍스트필드의 비동기 이벤트를 처리하는 과정을 하나하나 뜯어보았다. 본문을 통해 TextField에 Publisher를 만드는 것, MVVM 패턴에서 역할을 어떻게 분리해야하는지, 어떤 operator를 사용해서 값을 전달하는지, 메모리 누수는 어떻게 방지하는 지 등을 다뤘다.정의
👉 비동기 이벤트를 처리할 수 있게 해주는 프레임워크
자, 일단 중요한 두 주인공
Publisher
Subscriber
Publisher는 upstream Publisher의 값을 전달받아 다시 그 값을 다른 Publisher에게 넘겨주며
값을 뿜뿜한다
.그러면, Subscriber는 위와 같이 꼬리에 꼬리를 무는 Publisher 체인의 끝에서, 받은 값을 처리하는 액션을 취한다.
컴바인을 사용하면, 체이닝을 통해 콜백 지옥에서 빠져나와서 더 쉽게 이벤트 핸들링을 할 수 있다!
바로 예제로 딥다이브 해봅시다.
로그인 예제
(1) 화면 만들기
일단 먼저 화면을 만들어줍니다.
✅ **원하는 동작은 다음과 같아요**(2) TextField에 Publisher 만들기
일단 Publisher를 만들기에 앞서 왜 필요한지 간략하게 설명하겠습니다!
우리는 MVVM 패턴을 사용해서 View와 ViewModel의 역할을 분리할 거예요.
TextField는 입력되는 정보이므로, 이를 ViewModel에게 잘 전해줘야겠죠?
이 정보는 사용자가 한 글자 한 글자 칠 때마다 변하므로, 변할 때마다 ViewModel에게 알려줘야 합니다.
그래서 TextField의 변화를 감지하고 변화 때마다 이벤트 발생을 구독자에게 알려줄
Publisher
가 필요한 겁니다.Apple Developer Documentation
여길 보면, TextField는
NotificationCenter
로 잘 만들라고 하네요. 만들겠습니다.코드 설명
AnyPublisher<String, Never>
:String
타입에, 에러가 없는 Publisher 타입NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: self)
default
를 통해 지원해줍니다for:
에는 Notification의 이름이 들어가야하는데, 텍스트필드에서는 변화를 감지하는 노티의 이름을 제공해줘요object:
에는 해당 노티를 발생할 객체가 누구냐인건데, 텍스트필드가 노티를 발생시키니 얘가 해당되겠죠compactMap
: 옵셔널 바인딩이 되어 non-nil 값만 반환합니다eraseToAnyPublisher()
: 체이닝이 길어지면, 해당 Publisher의 타입도 함께 주렁주렁 길어지는데, 이런 타입을 간소화하기 위해 사용해요. 가장 마지막 타입만 남게 됩니다.(3) V: 입력값 ViewModel에게 넘겨주기 (feat. assign)
여기까지 해서 우리는 입력값의 변화를 이벤트로 발행하는 textfield publisher까지 만들었어요
아직까지 한 게 없죠??
지금부터 할 일은, 실제로 아이디 textfield와 비번 textfield의 publisher를 만들어서 ViewModel에 입력값을 변할 때마다 전달해줄 거예요.
그럼 먼저, publisher를 만듭니다.
assign
: keypath를 사용해서 새로운 값을 받을 때마다 그 값을 전달해주는 역할을 해요func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable
keyPath
에는 값을 전달할 프로퍼티를 ‘경로’ 형태로 적어줘야해요object
에는 그 프로퍼티를 가지고 있는 객체를 적어줍니다즉, ViewModel에 있는 idText에 아이디 TextField의 값을 변할 때마다 전달해줘! 라고 말한 겁니다~
(4) VM: 전달받은 입력값 뷰로직으로 가공하고 V에게 넘겨주기 (feat. map - logic)
그러면 ViewModel은 TextField의 입력값이 변할 때마다
idText
와pwText
에 값을 받아오겠죠?그때마다 뷰의 모델을 가공해주려고 합니다. 그래서
@Published
프로퍼티 래퍼로 Publisher 로 만들어줬습니다.이제 중간까지 왔어요. view logic으로 model을 가공해서 넘겨줄 거예요!
View로부터 받은 input의 변화를 감지해서, 바로 view logic에 mapping하여 output으로 전달하는 코드를 작성해봅시다.
로직은 testable 하게 입출력과 분리해서 작성했는데, map에 깔끔하게 클로저 형태로 들어가서 보기도 이쁘네요.
assign
의 경우, 이번에는 viewModel, 즉 자기에게 있는 프로퍼티에 접근해서 할당해주니까on object:
에 self를 넣어줬어요.(5) V: ViewModel에게 받은 출력값으로 뷰 그리기 (feat. sink)
이제 다 했네요. 마지막으로 View에서 ViewModel에게 받은 값을 사용하기만 하면 돼요!
sink
를 통해 뷰모델의 output 프로퍼티를 구독하고 있네요.여기서는 받은 값인 receiveValue만 사용해서 뷰를 그려줬어요
sink
의 정의부는func sink(receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
로, 결과 값을 클로저 형태로 받고 있습니다.즉, 값을 캡쳐하기 때문에 reference count가 하나 증가하여
weak self
를 사용하지 않고 내부 프로퍼티에 접근하면 스트림이 끝나도 ViewModel 객체가 메모리에 남아있을 수 있습니다.weak self
잘 쓰세요~메모리 누수 방지
메모리 누수에 관해서는 이런 것보다 앞서 ARC에 대해 알아야 설명이 이해가 되니, ARC에 대해 먼저 이해하고 나서 들으시는 것을 추천드립니다.
Cancellable의 cancel()
위에서 주의사항을 말씀드렸다시피, Subscriber는 클로저 형태로 스트림을 받기 때문에 개발자가 신경 쓰지 않으면 메모리가 누수될 여지가 있습니다.
그런데 클로저를 사용한다고 항상 메모리 누수가 발생하는 것은 아닌데, 왜 그런 차이가 생기는지
cancel()
과 관련해서 설명하려고 해요.Subscriber은
AnyCancellable
타입을 반환합니다.이 말은, 언제든 취소할 수 있어! 라는 말이죠. 즉, 구독을 언제든 취소할 수 있는 구독자라는 거예요.
구독은
cancel()
을 통해 취소할 수 있으며,구독을 취소할 경우, 스트림이 끝나면서 클로저 내부에서 참조한 객체의 reference count도 1 감소하게 됩니다.
위에서 언급한 클로저를 사용해도 메모리 누수가 발생하지 않는 상황은 이런 경우인데요,
작업을 완료하고
cancel()
을 적절한 때에 미리 하면 reference count 관리가 되어, 객체가 메모리에 남지 않게 됩니다.제가 위에서 작성한 예제 코드에서는
.store(in: &cancelBag)
을 사용해서 한번에 구독을 취소할 수 있게 모든 구독자들을 한 곳으로 원기옥을 모았는데요,NavigationViewController로 뷰가 이동해서 뷰가 해제된다거나 하면서 객체가 사라지면
AnyCancellable
타입은 자동으로cancel()
을 호출한다고 합니다. (공식문서 참고)그러니까 그냥 자동 해제된 거예요.
레퍼런스
Beta Was this translation helpful? Give feedback.
All reactions