-
[iOS/Swift] Combine 알아보기 i야매 iOS 2023. 2. 22. 23:13
Combine은 Functional Reactive Programming framework
Combine을 이해하기 위해선 Functional Reactive Programming이 무엇인지부터 알아야 한다.
Functional Reactive Programming(FRP)의 이해
FRP의 Functional Programming
FRP는 Functional Programming을 기반으로 만들어졌다.
- FRP는 개발자들이 작은 함수들을 조합해 코드를 작성할 수 있도록 한다.
작은 함수 : 들어온 입력인 인자를 사용해 동작하는 함수(함수 바깥에 있는 것들을 변경하지 않는다) 이후에 나올 순수함수 개념
map
대표적인 예시로는 swift에서 제공하는 map 함수가 있다.
// map 함수읜 인자로 받은 클로저에 의해 배열의 모든 요소 2를 곱한 값을 갖는 새로운 배열을 생성한다. [1, 2, 3].map { $0 * 2 }
map 함수를 사용해 배열 안에 있는 정수들을 다른 무언가로 변환할 수 있게 한다.
- map 함수의 결과로 기존의 배열이 영향을 받지 않고 새로운 배열을 생성한다.
- map에 클로저를 전달함으로써 기존 배열과 다른 컨텐츠를 가진 배열을 생성할 수 있고 변환에 필요한 모든 동작의 정의는 클로저에 담긴다.
고차함수
또 다른 함수나 클로저를 매개변수(parameter)로 받는 함수
순수함수
받는 인자로만 동작하는 함수
FRP의 Reactive Programming
동기적으로 동작하지 않고 어떤 일이 발생했을 때 조합한 함수와 연산을 거쳐 새로운 값을 받는다.
- 위의 map예시를 보면 [1, 2, 3] 배열에 map을 적용해 [2, 4, 6]이라는 새로운 배열을 생성한다. (하지만map은 동기적으로 이뤄진다.)
시간이 지남에 따라 이벤트나 값이 방출 될 때 map과 같은 함수를 사용해 원하는 값을 얻을 때까지 방출된 값을 변환시킬 수 있다.
// 탭 이벤트 전달 someButton.onTap.map { _ in // 이벤트 무시하고 랜덤 Bool 값 생성 return Bool.random() }.sink { didWin in // 받은 Bool 값으로 메시지 출력 if didWin { print("Win") } else { print("Didn't Win") } }
위의 예시에는 유저가 버튼을 탭하면 버튼으로부터 방출되는 이벤트들을 사용한다.
- 이 이벤트들을 stream이라고 한다.
- stream을 값들을 담는 배열이라고 생각할 수 있다. 이 값들은 시간이 지남에 따라 전달된다.
Combine에서 sink메서드로 값을 받는다.
→ FRP는 함수형 프로그래밍을 사용해 시간이 지남에 따라 발생되는 이벤트에 비동기적으로 대응할 수 있도록 한다.
FRP를 사용해 가독성 높이기
FRP는 코드 가독성을 높이고 코드의 복잡도를 줄인다.
- FRP의 가장 큰 단점은 높은 learning curve를 갖는 것
전통적인 callback 접근법과 Combine을 사용했을 때의 차이
// 전통적인 Callback 접근법 let myUrl = URL(string: "<https://www.google.com>")! func requestData(_ completion: @escaping (Result<data, error="">) -> Void) { URLSession.shared.dataTask(with: myUrl) { data, response, error in if let error = error { completion(.failure(error)) return } completion(.success(data)) }.resume() } </data,>
위 코드는 복잡하지 않지만 여러 data task를 chaining 해서 사용하는 것은 매우 어렵다.
- pyramid of doom
간단한 요청이지만 많은 boilerplace 코드가 포함되어 있다.
- error와 data의 옵셔널 unwrapping
- requestData를 호출할 때 Result 객체를 사용하기 위해서 unpack해야 하는 코드
// Combine 사용했을 때 let myUrl = URL(string: "<https://www.google.com>")! func requestData() -> AnyPublisher<data, urlerror=""> { URLSession.shared.dataTaskPublisher(for: myUrl) .map(\\.data) .eraseToAnyPublisher() } </data,>
Combile을 사용하면 두 개의 completionHandler에서 error와 success를 처리할 필요가 없다.
completion과 에러 이벤트들은 Combine의 value stream을 통해 내려온다.
- 실패 타입으로 generic Error를 담고 있는 객체를 반환 하는 대신 실패 타입으로 URLError를 갖는 publisher를 반환한다.
- 함수를 호출하는 쪽에서 더욱 편리하게 사용할 수 있다
→ Combine은 여러 boilerplate를 없애고 애플리케이션을 구현하는데 집중할 수 있도록 도와준다.
Combine은 operator라 불리는 여러 작은 순수 함수들을 사용해 chain을부터 내려오는 값들을 변환할 수 있다.
- 읽기 쉽고, boilerplate-free한 변환을 실행할 수 있다.
// API 요청 requestData() // Model 타입으로 decoding .decode(type: Model.self, decoder: JSONDecoder()) // Model에서 이름 추출 .map { $0.name } // 추출한 이름 출력 .sink(receiveCompletion: { _ in }, receiveValue: { print($0) })
- 읽기 쉬운 Combine operator 체인이 만들어졌다
- 여러 연산자를 조합해 chain of transformation을 만들어 작고, 고립된 변화를 데이터에 적용할 수 있다.
Combine과 RxSwift의 비교
RxSwift
- 3rd party library
- RxSwift를 유지보수하는 사람들을 믿어야 한다.
- 버그-free
- 안전성 있는 코드
- 계속해서 업데이트 해줄 거라는 믿음
Combine
- 애플이 만든 라이브러리
- 버그가 있으면 다음 iOS 업데이트에 수정될 수 있다.
- 호환성 보장
- 일부 API들과 통합되어 있음
요약
FRP
- 함수형 프로그래밍의 여러 원칙을 빌려 순수 함수와 고차함수를 사용해 여러 복잡한 행동들을 여러 작은 연산자의 조합으로 해결한다.
가독성
- callback-based API를 여러 작은 연산자로 나눠 복잡도를 줄이고 가독성을 높인다
'야매 iOS' 카테고리의 다른 글
[iOS/Swift] Combine 알아보기 iii (0) 2023.02.25 [iOS/Swift] Combine 알아보기 ii (0) 2023.02.23 [Swift] Array(repeating: , count:)와 UIStackView (0) 2023.02.19 [iOS/Swift] How to use iOS WidgetKit / 위젯 만들기 (0) 2023.02.19 Inversion of Control (IOC) (1) 2022.12.05