

RxSwift와 Combine은 Disposable과 Cancellable 개념을 통해 리소스와 메모리 관리를 도와줍니다.@State, @Binding과 같은 프로퍼티 래퍼를 사용하여 데이터와 UI 간의 바인딩을 선언적으로 정의할 수 있습니다.swiftCopy code
// GCD를 사용하여 백그라운드에서 작업을 수행하고, 그 결과를 메인 스레드에서 처리
DispatchQueue.global().async {
let result = "비동기 작업 결과"
DispatchQueue.main.async {
print("메인 스레드에서 결과 출력: \(result)")
}
}
Observable과 Observer 패턴을 기반으로 하며, 스트림의 변화에 반응하는 코드를 작성합니다.swiftCopy code
let observable = Observable<String>.create { observer in
observer.onNext("비동기 작업 결과")
observer.onCompleted()
return Disposables.create()
}
observable
.observeOn(MainScheduler.instance) // 메인 스레드에서 결과를 처리
.subscribe(onNext: { result in
print("메인 스레드에서 결과 출력: \(result)")
})
.disposed(by: disposeBag)
Observable 스트림을 통해 전달되며, 이 스트림에 반응하는 방식으로 코드를 작성합니다.따라서, RxSwift를 사용하면 비동기 작업과 그 결과를 더 선언적이고 체인 형식으로 표현할 수 있습니다. 이로 인해 복잡한 비동기 로직도 간결하게 표현할 수 있게 됩니다.
.map 함수를 사용하여 "모든 항목을 2배로 만들 것"이라고 선언합니다.let numbers = Observable.of(1, 2, 3, 4, 5)
numbers
.map { $0 * 2 } // 각 숫자를 2배로 만듭니다.
.filter { $0 > 5 } // 5보다 큰 숫자만 선택합니다.
.subscribe(onNext: { print($0) })
.map 연산자는 숫자를 2배로 만드는 작업을 "선언"합니다..filter 연산자는 5보다 큰 숫자만 선택하는 작업을 "선언"합니다.SwiftUI는 애플이 제공하는 선언형 UI 프레임워크입니다.RxSwift는 리액티브 프로그래밍을 위한 스위프트 라이브러리입니다.RxSwift 자체는 UI에 종속되지 않으므로, 애플의 플랫폼 뿐만 아니라 다른 환경에서도 사용될 수 있습니다.SwiftUI는 UI 생성 및 관리에 중점을 둔 애플의 프레임워크이며, RxSwift는 리액티브 프로그래밍을 위한 일반적인 라이브러리입니다. 두 도구는 리액티브 프로그래밍 원칙에 기반을 둔다는 공통점이 있지만, 그 목적과 사용법은 다릅니다. 실제로 SwiftUI와 RxSwift는 함께 사용될 수 있어, RxSwift를 통해 생성된 데이터 스트림을 SwiftUI의 UI 요소에 바인딩하는 것이 가능합니다.데이터 스트림은 다양한 작업을 수행할 수 있습니다. 일반적으로 사용되는 작업은:
enum PostContent {
case text(String)
case image(UIImage)
}
var contents: [PostContent] = []
// 가정: textView는 사용자의 텍스트 입력을 위한 UITextView입니다.
// imageView는 사용자가 선택한 이미지를 보여주기 위한 UIImageView입니다.
// addButton은 사용자가 텍스트나 이미지를 포스트에 추가하기 위한 버튼입니다.
let textObservable = textView.rx.text.orEmpty.asObservable()
let imageObservable = imageView.rx.tapGesture().when(.recognized).map { _ in imageView.image }
let addObservable = addButton.rx.tap.asObservable()
addObservable.withLatestFrom(textObservable)
.filter { !$0.isEmpty }
.map { PostContent.text($0) }
.bind { [weak self] content in
self?.contents.append(content)
}
.disposed(by: disposeBag)
addObservable.withLatestFrom(imageObservable)
.filter { $0 != nil }
.map { PostContent.image($0!) }
.bind { [weak self] content in
self?.contents.append(content)
}
.disposed(by: disposeBag)
함수형 프로그래밍 (Functional Programming)
map, filter, reduce와 같은 고차 함수를 사용하여 함수형 스타일로 코드를 작성할 수 있습니다.간단히 말하면, 함수형 프로그래밍은 함수와 데이터의 관계에 중점을 둡니다. 코드는 데이터 변환을 위한 함수의 연속으로 구성됩니다.
let 나의성적 = [“A”, “A”, “A”, “A”, “B”, “D”]
var 행복한성적: [String] = []
for grade in recentGrade {
if grade == “A” {
행복한성적.append(grade)
} else {
print(“엄마는 행복하지 않았다.”)
}
}
// print(행복한성적) // [“A”, “A”, “A”, “A”]```
func stringFilter(grade: [String], returnBool: (String) -> Bool) -> [String] {
return grade.filter(returnBool)
}
stringFilter(grade: 나의성적, returnBool: { $0 == “A” })
// [“A”, “A”, “A”, “A”]
for 루프 예제는 "성적이 A인 것을 찾기 위해 어떻게 리스트를 반복하고 조건을 확인하며 결과 배열에 추가할 것인가?"에 대한 절차를 기술하고 있습니다.stringFilter 함수는 "나는 A 성적만 원한다"라는 의도를 명확하게 선언하며, 그 세부적인 처리는 함수 내부에 숨겨져 있습니다.stringFilter를 선언형이라 부르는 이유는, 이 함수를 사용할 때 우리는 "어떤 성적을 원하는지"만 선언하면 되고, 그 세부적인 처리 방법(어떻게 필터링 할 것인가)은 함수 내부에 은닉되어 있기 때문입니다.var a = 1
var b = 3
a + b // 4
a를 변경 하고 싶어서 아래와 같이 코드를 짠다면 결과가 변경 되지는 않을겁니다.var a = 1
var b = 3
a + b // 처음(4) -> 마지막(4)
a = 3
a + b의 코드는 아무것도 신경 쓰지 않습니다.// T 타입의 값을 가질 수 있는 Reactive 클래스를 선언합니다.
class Reactive<T> {
// 값이 변경될 때마다 호출될 클로저의 타입을 Listener라는 별칭으로 정의합니다.
typealias Listener = (T) -> Void
// 값이 변경될 때 호출될 리스너(클로저)를 저장할 private 변수입니다.
private var listener: Listener?
// value 변수의 값이 변경될 때마다 didSet 프로퍼티 옵저버가 호출됩니다.
var value: T {
didSet {
// 값이 변경되면 등록된 리스너를 호출하여 새 값을 전달합니다.
listener?(value)
}
}
// 초기화 메서드에서 초기 값을 받습니다.
init(_ value: T) {
self.value = value
}
// 값을 관찰하고자 할 때 호출되는 메서드입니다.
// 리스너를 등록하면 값의 변화를 알 수 있습니다.
func bind(listener: @escaping Listener) {
// 전달받은 리스너(클로저)를 저장합니다.
self.listener = listener
// 리스너를 등록할 때 현재 값을 한번 호출해줍니다.
listener(value)
}
}
// 사용 예제
let reactiveA = Reactive(1) // 1 값을 갖는 Reactive 인스턴스 생성
let reactiveB = Reactive(3) // 3 값을 갖는 Reactive 인스턴스 생성
// reactiveA의 값 변화를 구독(관찰)합니다.
reactiveA.bind { newValue in
print("A의 값이 \(newValue)로 변경되었습니다.")
}
// reactiveB의 값 변화를 구독(관찰)합니다.
reactiveB.bind { newValue in
print("B의 값이 \(newValue)로 변경되었습니다.")
}
reactiveA.value = 3 // "A의 값이 3으로 변경되었습니다." 출력
reactiveB.value = 4 // "B의 값이 4로 변경되었습니다." 출력
typealias는 Swift에서 타입에 대한 다른 이름을 제공하는 방법입니다. 주로 코드를 더 읽기 쉽게 만들거나 복잡한 타입의 이름을 간단하게 만들 때 사용됩니다. 예제에서는typealias Listener = (T) -> Void 이 코드는 (T) -> Void 타입의 함수 (즉, T 타입의 인자를 받아 반환값이 없는 함수)에 대한 별칭으로 Listener라는 이름을 사용하겠다는 것을 의미합니다. 이후 코드에서 Listener라는 이름을 사용하면 (T) -> Void 타입의 함수를 의미하는 것이 됩니다.private var listener: Listener? 와var value: T {
didSet {
listener?(value)
}
}listener는 Listener 타입의 옵셔널 변수로, 리스너(클로저)를 저장하는 프로퍼티입니다.
value는 T 타입의 변수로, 해당 Reactive 인스턴스가 감싸고 있는 값을 나타냅니다. didSet은 프로퍼티 옵저버로, value의 값이 바뀔 때마다 호출되는 블록입니다. 여기서는 값이 변경되면 저장된 listener를 호출하여 새로운 값을 전달합니다.
이 두 프로퍼티 모두 해당 Reactive 객체 내에서 값을 저장하기 위해 사용되는 저장 프로퍼티입니다.
// 사용 예제
let reactiveA = Reactive(1) // 1 값을 갖는 Reactive 인스턴스 생성
let reactiveB = Reactive(3) // 3 값을 갖는 Reactive 인스턴스 생성
reactiveA.value + reactiveB.value
let 기기방향 = UIDevice.current
let 반응형방향 = Reactive(기기방향)
반응형방향.observe { 현재방향 in
switch 현재방향 {
case .오른쪽가로모드: print(“가로!”)
case .위아래바뀐세로모드: print(“이상한 세로!”)
case .왼쪽가로모드: print(“가로!”)
case .세로모드 { print(“세로!”) }
}
}
반응형방향.observe { 현재방향 in
.filter { 현재방향 == .세로모드 }
.observe { 현재방향 in
switch 현재방향 {
case .오른쪽가로모드: print(“가로!”)
case .위아래바뀐세로모드: print(“이상한 세로!”)
case .왼쪽가로모드: print(“가로!”)
case .세로모드 { print(“세로!”) }
}
}
filter는 true와 false를 반환 합니다.
true 즉 현재방향이 == .세로모드 일 때 아래의 switch문을 실행 합니다.
closure 버전 입니다.
.filter { $0 == .세로모드 } // 아니라면 돌아갑니다
.observe {
switch $0 {
case .오른쪽가로모드: print(“가로!”)
case .위아래바뀐세로모드: print(“이상한 세로!”)
case .왼쪽가로모드: print(“가로!”)
case .세로모드 { print(“세로!”) }
}
}