최근 몇년간 iOS 개발자들 사이에서는 가장 핫하게 차지하는 토픽이 RxSwift 였습니다.
많은 컨퍼런스에서 iOS 세션에서 발표되는 내용 역시도 Rx에 관한 것임을 쉽게 볼 수 있었죠.
외쳐 RxSwift의 시대 였던 것 같습니다.
그래서 그동안 엿보고만 있었지 공식 튜토리얼에 들어가더라도 쉽게 이해가 되지 않았던게 상황이었습니다.
RxSwift는 훌륭하지만 가장 큰 단점이 러닝커브가 높다는 것이죠.
그러니까 나중에 봐야지 고이고이 모셔두다가 이번에 회사에서 Rx를 도입하면서 공부한 내용들을 적으려고 합니다.
가장 먼저 집중했던 부분은
"그래서 Rx가 왜 필요한데?"
일단 공부하는 첫 시작에서 RxSwift가 왜 필요한지에 대한 이야기 먼저 해보려고 한다.
그러나 그전에 알아야 할 것이 있습니다.
RxSwift를 알기 위해선 Reactive Programming을 알아야 합니다.
Reactive Programming을 알기 위해선 Functional Programming을 알아야 합니다.
Functional Programming을 알기 위해선 Declarative(선언형)과 Imperative(명령형) Programming을 알아야 합니다.
ㅎㅎㅎㅎ.....
차근차근 알아가보도록 합시다.
순서상으론 선언형과 명령형을 알아야하겠지만 필요성에 대해서는 Functional이 먼저일 것 같습니다.
이런 배경에서 문제가 생겨났습니다.
동시성으로 여러 인스턴스에서 하나의 데이터에 접근한다고 했을 때, 읽을 때 문제는 되지 않지만 새 값을 쓰게 될 때 기대하는 값이 나오지 못하는 문제가 있었습니다.
실행되는 순서에 따라 다른 인스턴스에서 기대하지 않던 값을 써버린 다던가하는 문제 말이죠.
그러면서 많은 버그를 초래하며, 위험성이 높은 프로그램이 발생을 했다고 합니다.
Functional Programming은 위에서 다룬 문제를 해결하기 위해서 태어났습니다.
목적이라고 한다면 위들의 문제들을 해결하면서 더 가독성 높고 간결한 코드를 만드는 것 입니다.
Functional Programming은 아래와 같은 기능으로 위의 목적을 실현하고자 합니다.
값이 변하는게 무서우니까 그냥 값을 못바꾸게 만들자:)
그래서 사용되는게 var 구문이 아닌 let 구문이죠.
그럼 변하는 데이터는 어떻게 처리해?
메모리가 빵빵하니까 그냥 필요할 때 새로 만들어 쓰자~
그렇게 데이터 불변성을 보장하기 되었습니다.
다음은 순수한 함수라고 하는데요.
왜 필요할까요?
그것은 Side Effects 때문인데요.
즉, 함수의 scope 밖으로 영향을 끼치지 않는 함수를 뜻합니다. 아래와 같은 규칙들을 가집니다.
그럼 코드로 잠깐 볼게요.
var value = 10
func addValue(_ addValue: Int) {
value += addValue
}
addValue(10)
위의 함수는 순수함수가 아닙니다.
scope 밖의 상태를 바꾸고 있으니까요.
저기서 함수 2번을 실행하면 결과물이 동일 할까요?
아닙니다, 두번을 하면 30이라는 결과가 출력되겠죠.
그럼 순수함수를 한번 봅시다.
func addTwoValue(a: Int, b: Int) -> Int {
return a + b
}
addTwoValue(a: 1, b: 2)
//3 출력
addTwoValue(a: 1, b: 2)
//3 출력
네, 이게 순수함수라고 합니다.
함수는 1급 객체에 해당하게 합니다.
함수가 1급 객체란 아래와 같은 조건을 충족해야합니다.
Swift에서 함수는 1급 객체이다.
함수를 결합해서 사용할 수 있다.
func getTen() -> Int {
return 10
}
func addTwo(a: Int, b: Int) -> Int {
return a + b
}
func output(value: Int) {
print("\(value)")
}
output(value: addTwo(a: getTen(), b: getTen())))
간단하게는 Swift의 Closure에 해당
기존에 우리가 알고 있던 코드에 작성은 위에서 아래로 if와 for 같은 조건문들을 걸어 프로그램을 명령에 따라 돌아가게 됩니다.
어떤 목적을 수행하기까지에 How에 입각해서 시간순서대로 명령으로 프로그래밍하는 것이 기존에 우리가 하는 방식이었죠.
데이터를 어떻게 바꿀까 하는 과정들을 써내려가는 코딩입니다.
하지만 함수형 프로그래밍에서는 이런 명령형의 방식이 아닌 선언형의 프로그래밍을 하게 됩니다.
what에 입각해서 무엇을 할지 기술에 초점을 맞추는 코딩방식 입니다.
기존보다 코드스럽기보단 조금 더 자연어와 가깝다는 생각이 들며, 그렇게 되어 가독성도 높아지며 더 간결해집니다.
이 선언형 프로그래밍은 함수형의 위 기능들로 가능하게 합니다.
간단하게 예시를 보자면 가장 크게는 아래와 같습니다.
var numbers = [1, 3, 5, 6, 10, 7]
// Imperative
var newNumbers = [Int]()
for number in numbers {
if number % 2 == .zero {
newNumbers.append(number)
}
}
// Declarative
newNumbers = numbers.filter { $0 % 2 == .zero }
그냥 코드줄인거 아니야? 라고 말하겠지만
for문을 돌린다는 것은 데이터를 다루는 과정으로 읽혀서
"numbers 돌면서 2로 나눈 나머지가 0인 아이들을 찾는다"
filter라는 고차함수를 사용했기 때문에
"2로 나누애를 zero인 애들만 filter를 할거야."
라는 선언, 목표를 보여주게 된다.
이것이 선언형과 명령형 언어 패러다임의 차이입니다.
아마 기존 코딩에서 생각의 전환이 필요한 방법입니다.
우리는 기존에 비동기 처리를 하기 위해서 delegate pattern 또는 @escaping 함수를 closure로 전달해 해결하곤 했습니다.
아래와 같이요.
class API {
static func getSearchResult(_ item: String, completion: @escaping(_ result: [Model]) -> Void) {
Alamofire.request(url, method: .get, headers: header).responseArray { response in
completion(response.result)
}
}
}
func loadSearchResult(_ word: String) {
API.getSearchResult( word ) { response in
}
}
그렇지만 이 방식은 코드를 읽고 유지하는데에 큰 리소스를 쓰게 됩니다.
이리저리 돌아가며 그 코드를 읽어야하기 때문이죠.
곰튀김님은 시선이 분산된다고 설명을 해주신 부분이 있는데, 아마 그게 이 내용이지 않을까 싶습니다.
그래서 훌륭한 우리의 개발자들은 이것을 해결하기 위해 아이디어를 냅니다.
async한 처리에서도 functional 하게 사용하자는 것 입니다.
그 방법은 아래와 같습니다.
stream이라는 비동기 데이터 흐름을 만들고 그것에 data를 흘려 보내는 것이죠.
흘러오는 데이터를 사용할 수 있도록 하는 것 입니다.
그리고 그 Reactive하게 Swift를 사용할 수 있게해주는 라이브러리가 RxSwift 입니다.
그럼 다음장부터 RxSwift를 알아보도록 합시다.
너무 유용한 것 같아요:>