Search
Duplicate

Combine ๊ฐœ๋…

Created
2024/01/31 10:43
Tags
Combine

ย 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 ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ํ›จ์”ฌ ์ข‹๋‹ค (์‹œ๊ฐ„๋„ ์ ๊ฒŒ ๊ฑธ๋ฆฌ๊ณ , ๋ฉ”๋ชจ๋ฆฌ๋„ ๋” ์ ๊ฒŒ ์‚ฌ์šฉํ•˜์—ฌ ํšจ์œจ์ )