ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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를 여러 작은 연산자로 나눠 복잡도를 줄이고 가독성을 높인다
Designed by Tistory.