ย Combine ๊ฐ๋
Combine์ ์ ํ์ด Swift๋ฅผ ์ํด ๊ฐ๋ฐํ ๋ฐ์ํ ํ๋ก๊ทธ๋๋ฐ ํ๋ ์์ํฌ์ด๋ค. ์ด๋ ๋น๋๊ธฐ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ ์ธ์ API๋ฅผ ์ ๊ณตํ์ฌ, ์๊ฐ์ ๋ฐ๋ผ ๋ณํ๋ ๊ฐ๊ณผ ์ฌ์ฉ์ ์ธํฐํ์ด์ค ์ด๋ฒคํธ, ๋คํธ์ํฌ ์๋ต๊ณผ ๊ฐ์ ๋น๋๊ธฐ ์ด๋ฒคํธ๋ฅผ ๊ด๋ฆฌํ๋ ์๋ก์ด ๋ฐฉ๋ฒ์ ์๊ฐํ๋ค.
Combine์ ์ฃผ์ ๋ชฉ์ ์ ์ฝ๋์ ๊ฐ๋
์ฑ๊ณผ ์ ์ง ๊ด๋ฆฌ๋ฅผ ํฅ์์ํค๋ฉด์, ๋ณต์กํ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ณ ์ ์ธ์ ์ผ๋ก ํํํ๋ ๊ฒ์ด๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋ฐ์๋ ๋น๋๊ธฐ ์ฝ๋์ ์คํ ํ๋ฆ์ ์ฝ๊ฒ ์ถ์ ํ๊ณ , ์ค๋ฅ๋ฅผ ๋ ์ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ค.
<์ ํ ๊ฐ๋ฐ ๋ฌธ์ - Combine>
Combine ํ๋ ์์ํฌ๋ ์๊ฐ์ ๋ฐ๋ผ ๋ณํํ๋ ๊ฐ๋ค์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ ์ธ์ ์ค์ํํธ API๋ฅผ ์ ๊ณตํ๋ค.
์ด ๊ฐ๋ค์ ๋ค์ํ ์ข
๋ฅ์ ๋น๋๊ธฐ ์ด๋ฒคํธ๋ฅผ ๋ํํ ์ ์๋ค. Combine์ ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ๋ณ๊ฒฝ๋ ์ ์๋ ๊ฐ์ ๋
ธ์ถํ๋ ๋ฐํ์(Publishers)๋ฅผ ์ ์ธํ๊ณ , ์ด๋ฌํ ๊ฐ์ ๋ฐํ์๋ก๋ถํฐ ์์ ํ๋ ๊ตฌ๋
์(Subscribers)๋ฅผ ์ ์ธํ๋ค.
Publisher ํ๋กํ ์ฝ์ ์๊ฐ์ ๊ฑธ์ณ ์ผ๋ จ์ ๊ฐ์ ์ ๋ฌํ ์ ์๋ ํ์
์ ์ ์ธํ๋ค. ๋ฐํ์๋ ์์ ๋ฐํ์๋ก๋ถํฐ ๋ฐ์ ๊ฐ์ ์ฒ๋ฆฌํ๊ณ ์ฌ๋ฐํํ๊ธฐ ์ํ ์ฐ์ฐ์๋ฅผ ๊ฐ์ง๊ณ ์๋ค. โ Rx์์ Observable!
Publisher ์ฒด์ธ์ ๋์์, Subscriber๋ ์์ ํ๋ ์์๋ค์ ๋ํด ์์
์ ์ํํ๋ค. Publisher๋ Subscriber๊ฐ ๋ช
์์ ์ผ๋ก ์์ฒญํ์ ๋๋ง ๊ฐ์ ๋ฐํํ๋ค. ์ด๋ Subscriber ์ฝ๋๊ฐ ์ฐ๊ฒฐ๋ Publisher๋ก๋ถํฐ ์ด๋ฒคํธ๋ฅผ ๋ฐ๋ ์๋๋ฅผ ์ ์ดํ ์ ์๊ฒ ํ๋ค.
์ฌ๋ฌ Foundation ํ์
๋ค์ Timer, NotificationCenter, URLSession ๋ฑ์ ํฌํจํ์ฌ Publisher๋ฅผ ํตํด ๊ธฐ๋ฅ์ ๋
ธ์ถํ๋ค. Combine์ ๋ํ Key-Value Observing์ ์ค์ํ๋ ๋ชจ๋ ์์ฑ์ ๋ํ ๋ด์ฅ Publisher๋ฅผ ์ ๊ณตํ๋ค.
์ฌ๋ฌ Publisher์ ์ถ๋ ฅ์ ๊ฒฐํฉํ๊ณ ๊ทธ๋ค์ ์ํธ์์ฉ์ ์กฐ์ ํ ์ ์๋ค. ์๋ฅผ๋ค์ด ํ
์คํธํ๋์ Publisher๋ก๋ถํฐ ์
๋ฐ์ดํธ๋ฅผ ๊ตฌ๋
ํ๊ณ , ํ
์คํธ๋ฅผ ์ฌ์ฉํ์ฌ URL ์์ฒญ์ ์ํํ ์ ์๋ค. ๊ทธ๋ฐ ๋ค์ ๋ค๋ฅธ Publisher๋ฅผ ์ฌ์ฉํ์ฌ ์๋ต์ ์ฒ๋ฆฌํ๊ณ ์ฑ์ ์
๋ฐ์ดํธํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ค.
Combine์ ์ฑํํจ์ผ๋ก์จ, ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ฝ๋๋ฅผ ์ค์ ์ง์คํํ๊ณ ์ค์ฒฉ๋ ํด๋ก์ ์ ๊ด๋ก ๊ธฐ๋ฐ ์ฝ๋ฐฑ๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋๋ ๊ธฐ์ ์ ์ ๊ฑฐํจ์ผ๋ก์จ ์ฝ๋๋ฅผ ๋ ์ฝ๊ฒ ์ฝ๊ณ ์ ์ง๋ณด์ํ ์ ์๊ฒ ๋๋ค.
โข
๋น๋๊ธฐ ์์
๋ค์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ฐ์ฐ์๋ก ๊ฒฐํฉํ์ฌ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ
Combine ์ฌ์ฉ ์ (ํด๋ก์ ์ฌ์ฉ)
โฆ
๊ธฐ๋ณธ์ ์ธ ๋น๋๊ธฐ ๋คํธ์ํฌ ์์
URLSession.shared.dataTask(with: URL(string: "http://example.com")!) {
if let data = data {
DispatchQueue.main.async {
// UI Update
}
}
else if let error = error {
print("Error: \(error)")
}
}.resume()
Swift
๋ณต์ฌ
Combine ์ฌ์ฉ ํ
URLSession.shared.dataTaskPublisher(for: URL(string: "https://example.com")!) {
.map { $0.data } // map์ ํตํด ๋ฐ์ดํฐ๋ง ์ถ์ถํ๊ณ ,
.sink(receiveCompletion: { _ in }, // sink๋ฅผ ์ฌ์ฉํ์ฌ completion ํธ๋ค๋ง๊ณผ ๋ฐ์ดํฐ ์์ ์ฒ๋ฆฌ
receiveValue: { data in // ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ผ๋ฉด
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐ UI ์
๋ฐ์ดํธ
})
.store(in: &cancellables) // store๋ฅผ ํตํด ์์ฑ๋ ๋ชจ๋ ๊ตฌ๋
์ Cancellables์ ์ ์ฅํ์ฌ
// ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง -> Rx์ disposeBag ๊ฐ์ ๊ฒ
Swift
๋ณต์ฌ
โข
์ ์ธ์ ์ธ ํ๋ก๊ทธ๋๋ฐ ํํ๋ก ์ฌ์ฉ
Combine ์ฌ์ฉ ์ (ํด๋ก์ ์ฌ์ฉ)
NotificationCenter.default.addObserver(forName: UITextField.textDidNotification, object: nil, queue: nil) { notification in
if let textField = notification.object as? UITextField {
// ํ
์คํธํ๋ ๊ฐ ์ฒ๋ฆฌ
}
}
Swift
๋ณต์ฌ
Combine ์ฌ์ฉ ํ
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification)
.map { ($0.object as? UITextField)?.text ?? "" }
.sink { text in
// ํ
์คํธํ๋ ๊ฐ ์ฒ๋ฆฌ
}
.store(in: &cancellables)
Swift
๋ณต์ฌ
โข
Stream ํ๋๋ฅผ ๋ง๋ค๊ณ ๊ทธ Stream์ ํ์ํ operator๋ฅผ ๋ซ๋ถ์ฌ์ ์ฌ์ฉํ๋ ์ ์ธ์ ์ธ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์
// ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ Combine ์์ ์ฝ๋
$username
.debounce(for: 0.1, scheduler: RunLoop.main)
.removeDuplicates()
.map { $0.count >= 2 }
.assgin(to: \.valid, on: self)
.store(in: &cancellableSet)
Swift
๋ณต์ฌ
โข
์ ์ธ์ ์ธ ํ๋ก๊ทธ๋๋ฐ์ด ๋๋ฉด์, ํด๋ก์ ๋ฅผ ์ด์ฉํ ์ฝ๋ฐฑ์ง์ฅ์์ ๋ฒ์ด๋ ์ ์๋ ์ฅ์ ์ด ์กด์ฌ
Combine ์ฌ์ฉ ์ (ํด๋ก์ ์ฌ์ฉ, ์ฝ๋ฐฑ ๊ธฐ๋ฐ์ ์ ๊ทผ ๋ฐฉ์)
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: URL(string: "https://example.com")!) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
completion(.success(data ?? Data()))
}.resume()
}
fetchData { result in // ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ํด๋ก์
switch result {
case .success(let data):
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ
case .failure(let error):
print("Error: \(error)")
}
}
Swift
๋ณต์ฌ
Combine ์ฌ์ฉ ํ
URLSession.shared.dataTaskPublisher(for: URL(string: "https://example.com")!)
.map { $0.data }
.sink(receiveCompletion: { _ in },
receiveValue: { data in
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ
})
.store(in: &cancellables)
Swift
๋ณต์ฌ
โข
Combine์ ์ฌ์ฉํ๋ฉด ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ฝ๋๋ค์ด ์ค์ ์ง์คํ๊ฐ ๋์ด ๋์ฑ ์ฝ๊ธฐ ์ฌ์ด ์ฝ๋๋ก ๊ตฌํ
โฆ
๋ง์ฝ Combine์ด ์๋ async-awailt๋ closure๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ์ด๋ค ์ฝ๋๊ฐ ์ด๋์ ์คํ๋ ์ง ํ์
์ด ์ด๋ ต๋ค.
Combine ์ฌ์ฉ ์ (ํด๋ก์ ๋ฐ async-await ์ฌ์ฉํ ๋คํธ์ํน ์์ฒญ)
func fetchUser() async throws -> User {
let (data, _) = try await URLSession.shared.data(from: URL(string: "https://example.com/user")!)
return try JSONDecoder().decode(User.self, from: data)
}
func fetchPosts() async throws -> [Post] {
let (data, _) = try await URLSession.shared.data(from: URL(string: "https//example.com/posts")!)
return try JSONDecoder().decode([Post].self, from: data)
}
// async-await๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์ ์ ๋ณด์ ๊ฒ์๋ฌผ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ๋ค.
Task {
do {
let user = try await fetchUser()
let posts = try await fetchPosts()
}
catch {
print("Error: \(error)")
}
}
// ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ ํธ์ถ
Swift
๋ณต์ฌ
Combine ์ฌ์ฉ ํ
let userPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://example.com/user")!)
.map { $0.data }
.decode(type: User.self, decoder: JSONDecoder())
let postsPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://example.com/posts")!)
.map { $0.data }
.decode(type: [Post].self, decoder: JSONDecoder())
// Publisher.Zip์ ์ฌ์ฉํ์ฌ ๋ ๊ฐ์ ํผ๋ธ๋ฆฌ์
๋ฅผ ๊ฒฐํฉํ๊ณ , ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ์ค๋น๋๋ฉด,
// .sink๋ฅผ ํตํด ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ค.
Publisher.Zip(userPublisher, postsPublisher)
.sink(receiveCompletion: { _ in },
receiveValue: { user, posts in
// ์ฌ์ฉ์์ ๊ฒ์๋ฌผ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
})
.store(in : &cancellables)
// ๋ง์ฐฌ๊ฐ์ง๋ก ์์ฑ๋ ๋ชจ๋ ๊ตฌ๋
์ Cancellables์ ์ ์ฅ๋๋ค.
Swift
๋ณต์ฌ
ย Combine ์ ๋ฆฌ
Combine์ ์๊ฐ์ ๋ฐ๋ผ ๊ฐ์ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ ์ธ์ (declarative) Swift API๋ฅผ ์ ๊ณตํ๋ค.
โ ๋ง์ ์ข
๋ฅ์ ๋น๋๊ธฐ ์ด๋ฒคํธ๋ฅผ ๋ํ๋ผ ์ ์๋ค.
Combine์ Publishers๊ฐ ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ๋ณ๊ฒฝ๋ ์ ์๋ ๊ฐ์ expose ํ๊ณ , Subscribers๊ฐ Publisher๋ก ๋ถํฐ ํด๋น ๊ฐ์ ๋ฐ๋๋ก ์ ์ธํ๋ค.
Combine์ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์ฝ๋๋ฅผ ์ค์ ์ง์คํ(Centralizing)ํ๊ณ , ์ค์ฒฉ๋ Closures ๋ฐ ์ฝ๋ฐฑ๊ณผ ๊ฐ์ ๊น๋ค๋ก์ด ๊ธฐ์ ์ ์ ๊ฑฐํ์ฌ ์ฝ๋๋ฅผ ์ฝ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฝ๊ฒ ๋ง๋ ๋ค.
ย Combine๊ณผ RxSwift์ ์ฃผ์ ์ฐจ์ด์
โข
Combine๊ณผ RxSwift ๋ชจ๋ Publisher.Subscriber ํ๋ ์ปดํฌ๋ํธ๊ฐ ์๊ณ , stream์ ๋ง๋ค์ด์ ๊ทธ stream ์์ operator๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๋ ํํ์ด๋ค.
โข
Combine์ RxSwift ๋ณด๋ค ์ฑ๋ฅ์ด ํจ์ฌ ์ข๋ค (์๊ฐ๋ ์ ๊ฒ ๊ฑธ๋ฆฌ๊ณ , ๋ฉ๋ชจ๋ฆฌ๋ ๋ ์ ๊ฒ ์ฌ์ฉํ์ฌ ํจ์จ์ )