### Manually Connect a Timer Publisher Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Create a Timer publisher and manually connect it using .connect() to start receiving values. This allows for delayed activation of the timer. ```swift let timerPublisher = Timer.publish(every: 1.0, on: RunLoop.main, in: .default) let cancellableSink = timerPublisher .sink { print("passed through: ", $0) } // no values until the following is invoked elsewhere/later: let cancellablePublisher = timerPublisher.connect() ``` -------------------------------- ### Swift Combine Pipeline Example Source: https://heckj.github.io/swiftui-notes/index.html Demonstrates a basic Combine pipeline starting with a `Just` publisher, transforming the value with a `map` operator, and terminating with a `sink` subscriber. The `Just` publisher emits a single value and never fails. The `map` operator transforms the emitted value's type, and `sink` acts as the final subscriber to process the result. ```swift let _ = Just(5) \ .map { value -> String in \ // do something with the incoming value here // and return a string return "a string" } .sink { receivedValue in \ // sink is the subscriber and terminates the pipeline print("The end result was \(receivedValue)") } ``` -------------------------------- ### FormViewController Setup with Combine Source: https://heckj.github.io/swiftui-notes/index.html Sets up IBOutlets for text fields, buttons, and labels, along with IBAction methods to update @Published properties when text fields change. This forms the basis for reactive updates. ```swift import UIKit import Combine class FormViewController: UIViewController { @IBOutlet weak var value1_input: UITextField! @IBOutlet weak var value2_input: UITextField! @IBOutlet weak var value2_repeat_input: UITextField! @IBOutlet weak var submission_button: UIButton! @IBOutlet weak var value1_message_label: UILabel! @IBOutlet weak var value2_message_label: UILabel! @IBAction func value1_updated(_ sender: UITextField) { __**(1)** value1 = sender.text ?? "" } @IBAction func value2_updated(_ sender: UITextField) { value2 = sender.text ?? "" } @IBAction func value2_repeat_updated(_ sender: UITextField) { value2_repeat = sender.text ?? "" } @Published var value1: String = "" @Published var value2: String = "" @Published var value2_repeat: String = "" var validatedValue1: AnyPublisher { __**(2)** return $value1.map { value1 in guard value1.count > 2 else { DispatchQueue.main.async { __**(3)** self.value1_message_label.text = "minimum of 3 characters required" } return nil } DispatchQueue.main.async { self.value1_message_label.text = "" } return value1 }.eraseToAnyPublisher() } var validatedValue2: AnyPublisher { return $value2.combineLatest($value2_repeat) .receive(on: RunLoop.main) __**(5)** .map { value2, value2_repeat in guard value2_repeat == value2, value2.count > 4 else { self.value2_message_label.text = "values must match and have at least 5 characters" return nil } self.value2_message_label.text = "" return value2 }.eraseToAnyPublisher() } var readyToSubmit: AnyPublisher<(String, String)?, Never> { return Publishers.CombineLatest(validatedValue2, validatedValue1) .map { value2, value1 in guard let realValue2 = value2, let realValue1 = value1 else { return nil } return (realValue2, realValue1) } .eraseToAnyPublisher() } private var cancellableSet: Set = [] __**(7)** override func viewDidLoad() { super.viewDidLoad() self.readyToSubmit .map { $0 != nil } __**(8)** .receive(on: RunLoop.main) .assign(to: \.isEnabled, on: submission_button) .store(in: &cancellableSet) __**(9)** } } ``` -------------------------------- ### Create Publisher with Resolved Failure Source: https://heckj.github.io/swiftui-notes/index.html A `Future` can also be created to immediately resolve with a failure. This is useful for publishers that should indicate an error from the start. ```swift enum ExampleFailure: Error { case oneCase } let resolvedFailureAsPublisher = Future { promise in promise(.failure(ExampleFailure.oneCase)) }.eraseToAnyPublisher() ``` -------------------------------- ### Create a Repeating Timer Publisher Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Use Timer.publish to create a publisher that emits dates at a specified interval. autoconnect() starts the timer immediately upon subscription. ```swift let cancellable = Timer.publish(every: 1.0, on: RunLoop.main, in: .common) .autoconnect() .sink { print("passed through: ", $0) } ``` -------------------------------- ### Setup Coordinated Pipeline Source: https://heckj.github.io/swiftui-notes/index.html Sets up a complex Combine pipeline involving serial and parallel asynchronous operations. It uses `createFuturePublisher` and operators like `flatMap` and `Zip3` to orchestrate the workflow. This is typically called within `viewDidLoad`. ```swift // MARK: - view setup override func viewDidLoad() { super.viewDidLoad() self.activityIndicator.stopAnimating() // Do any additional setup after loading the view. coordinatedPipeline = createFuturePublisher(button: self.step1_button) .flatMap { flatMapInValue -> AnyPublisher in let step2_1 = self.createFuturePublisher(button: self.step2_1_button) let step2_2 = self.createFuturePublisher(button: self.step2_2_button) let step2_3 = self.createFuturePublisher(button: self.step2_3_button) return Publishers.Zip3(step2_1, step2_2, step2_3) .map { _ -> Bool in return true } .eraseToAnyPublisher() } .flatMap { _ in return self.createFuturePublisher(button: self.step3_button) } .flatMap { _ in return self.createFuturePublisher(button: self.step4_button) } .eraseToAnyPublisher() } ``` -------------------------------- ### Observe Network Activity Publisher Source: https://heckj.github.io/swiftui-notes/index.html Subscribes to a network activity publisher to animate an activity indicator. Starts animating when network activity begins and stops when it ends. ```swift let apiActivitySub = GithubAPI.networkActivityPublisher __**(1)** .receive(on: RunLoop.main) .sink { doingSomethingNow in if (doingSomethingNow) { self.activityIndicator.startAnimating() } else { self.activityIndicator.stopAnimating() } } ``` -------------------------------- ### Test DataTaskPublisher with XCTestExpectation Source: https://heckj.github.io/swiftui-notes/index.html This example demonstrates testing a one-shot publisher, URLSession.dataTaskPublisher, using XCTestExpectation. It sets up an expectation to wait for the publisher to complete successfully, fulfilling the expectation in the `receiveCompletion` closure. Failures are explicitly handled with `XCTFail()`. ```swift func testDataTaskPublisher() { // setup let expectation = XCTestExpectation(description: "Download from \(String(describing: testURL))") __**(1)** let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: self.testURL!) // validate .sink(receiveCompletion: { fini in print(".sink() received the completion", String(describing: fini)) switch fini { case .finished: expectation.fulfill() __**(2)** case .failure: XCTFail() __**(3)** } }, receiveValue: { (data, response) in guard let httpResponse = response as? HTTPURLResponse else { XCTFail("Unable to parse response an HTTPURLResponse") return } XCTAssertNotNil(data) // print(".sink() data received \(data)") XCTAssertNotNil(httpResponse) XCTAssertEqual(httpResponse.statusCode, 200) __**(4)** // print(".sink() httpResponse received \(httpResponse)") }) XCTAssertNotNil(remoteDataPublisher) wait(for: [expectation], timeout: 5.0) __**(5)** } ``` -------------------------------- ### Create LocationHeadingProxy Publisher Source: https://heckj.github.io/swiftui-notes/index.html Wraps CLLocationManager to publish heading updates using a PassthroughSubject. Requires CoreLocation and Combine frameworks. The proxy starts and stops heading updates via its enable/disable methods. ```swift import Foundation import Combine import CoreLocation final class LocationHeadingProxy: NSObject, CLLocationManagerDelegate { let mgr: CLLocationManager __**(1)** private let headingPublisher: PassthroughSubject __**(2)** var publisher: AnyPublisher __**(3)** override init() { mgr = CLLocationManager() headingPublisher = PassthroughSubject() publisher = headingPublisher.eraseToAnyPublisher() super.init() mgr.delegate = self __**(4)** } func enable() { mgr.startUpdatingHeading() __**(5)** } func disable() { mgr.stopUpdatingHeading() } // MARK - delegate methods /* * locationManager:didUpdateHeading: * * Discussion: * Invoked when a new heading is available. */ func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { headingPublisher.send(newHeading) __**(6)** } /* * locationManager:didFailWithError: * Discussion: * Invoked when an error has occurred. Error types are defined in "CLError.h". */ func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { headingPublisher.send(completion: Subscribers.Completion.failure(error)) __**(7)** } } ``` -------------------------------- ### Subscribe to Custom Notifications Source: https://heckj.github.io/swiftui-notes/index.html Subscribe to a custom notification using its defined name. This example demonstrates how to receive and print the notification details, including its name, object, and user info. ```swift let cancellable = NotificationCenter.default.publisher(for: .myExampleNotification, object: nil) .sink { receivedNotification in print("passed through: ", receivedNotification) // receivedNotification.name // receivedNotification.object - object sending the notification (sometimes nil) // receivedNotification.userInfo - often nil } ``` -------------------------------- ### Run Asynchronous Sequence Source: https://heckj.github.io/swiftui-notes/index.html Initiates the entire asynchronous sequence. It cancels any ongoing sequence, resets UI elements, starts an activity indicator, and attaches a sink to the coordinated pipeline to begin execution. ```swift func runItAll() { if self.cancellable != nil) { print("Cancelling existing run") cancellable?.cancel() self.activityIndicator.stopAnimating() } print("resetting all the steps") self.resetAllSteps() // driving it by attaching it to .sink self.activityIndicator.startAnimating() print("attaching a new sink to start things going") self.cancellable = coordinatedPipeline? .print() .sink(receiveCompletion: { completion in print(".sink() received the completion: ", String(describing: completion)) self.activityIndicator.stopAnimating() }, receiveValue: { value in print(".sink() received value: ", value) }) } ``` -------------------------------- ### Create a Fail Publisher (inferred types) Source: https://heckj.github.io/swiftui-notes/index.html Initialize Fail by providing the output and failure types as parameters. This publisher immediately terminates with the specified error. ```swift let cancellable = Fail(outputType: String.self, failure: TestFailureCondition.exampleFailure) ``` -------------------------------- ### Create a Fail Publisher (with types) Source: https://heckj.github.io/swiftui-notes/index.html Initialize Fail by explicitly specifying the output and failure types. This publisher immediately terminates with the specified error. ```swift let cancellable = Fail(error: TestFailureCondition.exampleFailure) ``` -------------------------------- ### Create a Sequence Publisher Source: https://heckj.github.io/swiftui-notes/index.html Use the .publisher extension on any Sequence to create a Publishers.Sequence. This publisher emits elements from the sequence as demanded by the subscriber. ```swift let initialSequence = ["one", "two", "red", "blue"] _ = initialSequence.publisher .sink { print($0) } } ``` -------------------------------- ### Get Min Value of Publisher Stream Source: https://heckj.github.io/swiftui-notes/index.html Use `.min()` when the upstream publisher's output type conforms to `Comparable`. If not, provide a closure to define the ordering. ```swift .min() ``` ```swift .min { (struct1, struct2) -> Bool in return struct1.property1 < struct2.property1 } ``` -------------------------------- ### Create a Connectable Publisher Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Use `makeConnectable()` to wrap a publisher, making it explicitly connectable. The subscription will not activate until `connect()` is called. ```swift var cancellables = Set() let publisher = Just("woot") .makeConnectable() publisher.sink { value in print("Value received in sink: ", value) } .store(in: &cancellables) ``` -------------------------------- ### Get Max Value of Publisher Stream Source: https://heckj.github.io/swiftui-notes/index.html Use `.max()` when the upstream publisher's output type conforms to `Comparable`. If not, provide a closure to define the ordering. ```swift .max() ``` ```swift .max { (struct1, struct2) -> Bool in return struct1.property1 < struct2.property1 } ``` -------------------------------- ### Pipeline with Subscribe, Receive, and Assign Source: https://heckj.github.io/swiftui-notes/index.html This pipeline demonstrates subscribing to a publisher on a background queue, receiving on the main run loop for UI updates, and assigning the result to a property. ```swift networkDataPublisher .subscribe(on: backgroundQueue) __**(1)** .receive(on: RunLoop.main) __**(2)** .assign(to: \.text, on: yourLabel) __**(3)** ``` -------------------------------- ### Create an Autoconnectable Publisher Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Use `autoconnect()` on a connectable publisher to automatically establish the connection upon the first subscription. This makes the publisher behave like a regular publisher. ```swift var cancellables = Set() let publisher = Just("woot") .makeConnectable() __**(1)** .autoconnect() __**(2)** publisher.sink { value in print("Value received in sink: ", value) } .store(in: &cancellables) ``` -------------------------------- ### Explicit Type Mapping with map Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html When the compiler cannot infer the return type, explicitly define the return type in the `map` closure. This example maps a struct to its boolean property. ```swift struct MyStruct { isValid: bool = true } // Just(MyStruct()) .map { inValue -> Bool in inValue.isValid } ``` -------------------------------- ### Use first operator to get the first element Source: https://heckj.github.io/swiftui-notes/index.html The first operator publishes only the first element it receives and then finishes. If the upstream publisher finishes before any values are published, no values are emitted. ```swift .first() ``` -------------------------------- ### Create a Record Publisher in Swift Source: https://heckj.github.io/swiftui-notes/index.html Use Record to create a publisher that replays a predefined sequence of values and a completion event. This is useful for testing or scenarios requiring deterministic output. The timing of value delivery is not controlled. ```swift let recordedPublisher = Record { example in // example : type is Record.Recording example.receive("one") example.receive("two") example.receive("three") example.receive(completion: .finished) } ``` -------------------------------- ### Use last operator to get the last element Source: https://heckj.github.io/swiftui-notes/index.html The last operator waits for the upstream publisher to complete, then publishes the last element it received. If no elements were received before completion, no value is published. ```swift .last() ``` -------------------------------- ### Manually Connect a Publisher Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html After creating a connectable publisher with `makeConnectable()`, explicitly call `connect()` to enable the subscription and allow data flow. ```swift publisher .connect() .store(in: &cancellables) ``` -------------------------------- ### 使用 handleEvents 调试 Combine 管道 Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html handleEvents 操作符允许在管道中的特定点插入自定义逻辑,例如打印日志或更新 UI。它可以接收多个闭包来处理不同的事件,如订阅、请求、取消、输出和完成。 ```swift .handleEvents(receiveSubscription: { aValue in print("receiveSubscription event called with \(String(describing: aValue))") __**(2)** }, receiveOutput: { aValue in __**(3)** print("receiveOutput was invoked with \(String(describing: aValue))") }, receiveCompletion: { aValue in __**(4)** print("receiveCompletion event called with \(String(describing: aValue))") }, receiveCancel: { __**(5)** print("receiveCancel event invoked") }, receiveRequest: { aValue in __**(1)** print("receiveRequest event called with \(String(describing: aValue))") }) ``` -------------------------------- ### Reduce: Accumulate String Values Source: https://heckj.github.io/swiftui-notes/index.html The reduce operator collects values from an upstream publisher and applies a closure to accumulate them into a single result, which is only published upon successful completion. This example concatenates strings. ```swift .reduce("", { prevVal, newValueFromPublisher -> String in return prevVal+newValueFromPublisher }) ``` -------------------------------- ### Request Data from Alternate URL When Network is Constrained Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Implement an adaptive network loader that attempts a primary URL and falls back to a secondary URL if the network is constrained. This uses tryCatch to detect specific network errors and switch the request. ```swift func adaptiveLoader(regularURL: URL, lowDataURL: URL) -> AnyPublisher { var request = URLRequest(url: regularURL) request.allowsConstrainedNetworkAccess = false return URLSession.shared.dataTaskPublisher(for: request) .tryCatch { error -> URLSession.DataTaskPublisher in guard error.networkUnavailableReason == .constrained else { throw error } return URLSession.shared.dataTaskPublisher(for: lowDataURL) } .tryMap { data, response -> Data in guard let httpResponse = response as? HTTPUrlResponse, httpResponse.statusCode == 200 else { throw MyNetworkingError.invalidServerResponse } return data } .eraseToAnyPublisher() ``` -------------------------------- ### Swift Combine Map Operator Example Source: https://heckj.github.io/swiftui-notes/index.html Demonstrates the use of the `map` operator in Combine to transform an Int to a String. The closure provided to `map` handles the transformation logic. The output is then printed using the `sink` operator. ```swift let _ = Just(5) .map { value -> String in __**(1)** switch value { case _ where value < 1: return "none" case _ where value == 1: return "one" case _ where value == 2: return "couple" case _ where value == 3: return "few" case _ where value > 8: return "many" default: return "some" } } .sink { receivedValue in print("The end result was \(receivedValue)") } ``` -------------------------------- ### Prepend Publisher to Another Publisher Source: https://heckj.github.io/swiftui-notes/index.html Use `prepend` to emit all elements from a first publisher before emitting elements from a second publisher. Both publishers must match on Output and Failure types. ```swift secondPublisher .prepend(firstPublisher) ``` -------------------------------- ### Create Publisher from URLSession Data Task Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Utilize `URLSession.shared.dataTaskPublisher(for:)` to create a publisher that fetches data from a given URL or URLRequest. The publisher emits a tuple containing the fetched Data and the URLResponse. This is a convenient way to handle network requests within a Combine pipeline. ```swift let request = URLRequest(url: regularURL) return URLSession.shared.dataTaskPublisher(for: request) ``` -------------------------------- ### Transform Int Publisher to String Subscriber with map Source: https://heckj.github.io/swiftui-notes/index.html Use the `map` operator to transform an Int publisher to a String subscriber. Explicitly define the return type of the closure if the compiler cannot infer it. This example demonstrates converting an integer value into a descriptive string. ```swift let _ = Just(5) \ .map { value -> String in \ switch value { case _ where value < 1: return "none" case _ where value == 1: return "one" case _ where value == 2: return "couple" case _ where value == 3: return "few" case _ where value > 8: return "many" default: return "some" } } .sink { receivedValue in \ print("The end result was \(receivedValue)") } ``` -------------------------------- ### Select element by index using output Source: https://heckj.github.io/swiftui-notes/index.html Use the `output(at:)` operator to select a single element from a publisher by its 0-indexed position. The upstream publisher must publish at least to the specified index. ```swift .output(at: 3) ``` -------------------------------- ### Map Publisher Output to Optional Type for Assignment Source: https://heckj.github.io/swiftui-notes/index.html When a publisher's output type does not match the optionality of the target property's KeyPath, use a `map` operator to adjust the type. This example converts a non-optional `UIImage` to an optional `UIImage?` to match `UIImageView.image`. ```swift .map { image -> UIImage? in image } ``` -------------------------------- ### Subscribe to AppKit Text Field Changes Source: https://heckj.github.io/swiftui-notes/index.html Subscribe to `textDidChangeNotification` from an AppKit `NSTextField` using a Combine publisher. This example filters notifications by the specific text field and maps the received notification to the text field's string value, assigning it to a view model property. ```swift let sub = NotificationCenter.default.publisher(for: NSControl.textDidChangeNotification, object: filterField) .map { ($0.object as! NSTextField).stringValue } .assign(to: \MyViewModel.filterString, on: myViewModel) ``` -------------------------------- ### Adaptive URL Loading with tryCatch Source: https://heckj.github.io/swiftui-notes/index.html Use this function to create a publisher that attempts to load data from a primary URL and falls back to a secondary URL if the network is constrained. Setting `allowsConstrainedNetworkAccess` to false on the request triggers the fallback mechanism. The `tryCatch` operator intercepts specific network errors, and `tryMap` validates the server response. ```swift func adaptiveLoader(regularURL: URL, lowDataURL: URL) -> AnyPublisher { var request = URLRequest(url: regularURL) request.allowsConstrainedNetworkAccess = false return URLSession.shared.dataTaskPublisher(for: request) .tryCatch { guard error.networkUnavailableReason == .constrained else { throw error } return URLSession.shared.dataTaskPublisher(for: lowDataURL) }.tryMap { guard let httpResponse = response as? HTTPUrlResponse, httpResponse.statusCode == 200 else { throw MyNetworkingError.invalidServerResponse } return data } .eraseToAnyPublisher() } ``` -------------------------------- ### Create Publisher with Resolved Success Source: https://heckj.github.io/swiftui-notes/index.html A `Future` can be created to immediately resolve with a successful value. This is useful for publishers that have a known, immediate result. ```swift let resolvedSuccessAsPublisher = Future { promise in promise(.success(true)) }.eraseToAnyPublisher() ``` -------------------------------- ### Select elements by range using output Source: https://heckj.github.io/swiftui-notes/index.html Use the `output(at:)` operator with a Swift range to select a sequence of elements from a publisher. The upstream publisher must publish at least up to the end of the specified range. ```swift .output(at: 2...3) ``` -------------------------------- ### Multicast Publisher with Inline Subject Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Create a multicast publisher by providing a closure that returns a `PassthroughSubject`. This consolidates upstream requests for multiple subscribers. Remember to explicitly connect the publisher using `connect()` or `autoconnect()`. ```swift let multicastPublisher = somepublisher .multicast { PassthroughSubject() } ``` -------------------------------- ### Create Subscriber with Sink (Simple) Source: https://heckj.github.io/swiftui-notes/index.html Use the simple version of `sink` to receive values from a publisher. This closure is invoked each time a value is emitted. ```swift let cancellablePipeline = publishingSource.sink { someValue in // do what you want with the resulting value passed down // be aware that depending on the publisher, this closure // may be invoked multiple times. print(".sink() received \(someValue)") }) ``` -------------------------------- ### Create an Empty Publisher Source: https://heckj.github.io/swiftui-notes/index.html Use Empty to create a publisher that never publishes values and optionally finishes immediately. Define output and failure types when they cannot be inferred. ```swift let myEmptyPublisher = Empty() ``` -------------------------------- ### Prepend Sequence to Publisher Source: https://heckj.github.io/swiftui-notes/index.html Use `prepend` with a sequence to immediately publish sequence values upon subscriber demand. Further demand is propagated to the second publisher. ```swift secondPublisher .prepend(["one", "two"]) ``` -------------------------------- ### Prefix Until Output from Another Publisher Source: https://heckj.github.io/swiftui-notes/index.html Use `prefix(untilOutputFrom:)` to republish elements until a second publisher emits an element. The stream terminates after the trigger publisher publishes. ```swift .prefix(untilOutputFrom: secondPublisher) ``` -------------------------------- ### Subscribe to a Record Publisher in Swift Source: https://heckj.github.io/swiftui-notes/index.html Sink into a Record publisher to receive its pre-recorded values and completion. Ensure the expectation is fulfilled upon completion for testing purposes. ```swift let cancellable = recordedPublisher.sink(receiveCompletion: { err in print(".sink() received the completion: ", String(describing: err)) expectation.fulfill() }, receiveValue: { value in print(".sink() received value: ", value) }) ``` -------------------------------- ### 使用 breakpoint 操作符触发调试器 Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html breakpoint 操作符允许在管道中的特定点触发调试器。可以通过返回 true 来激活调试器,这对于在满足特定条件时检查管道状态非常有用。 ```swift .breakpoint(receiveSubscription: { subscription in return false // return true to throw SIGTRAP and invoke the debugger }, receiveOutput: { value in return false // return true to throw SIGTRAP and invoke the debugger }, receiveCompletion: { completion in return false // return true to throw SIGTRAP and invoke the debugger }) ``` -------------------------------- ### 使用 assign 创建一个订阅者 Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html 使用 `assign` 将发布者的数据设置到对象的属性上。需要确保在主线程更新 UI 元素,并且可以随时取消管道。 ```swift let cancellablePipeline = publishingSource __**(1)** .receive(on: RunLoop.main) __**(2)** .assign(to: \.isEnabled, on: yourButton) __**(3)** cancellablePipeline.cancel() __**(4)** ``` -------------------------------- ### Create Publisher from KVO-Compliant Object Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Use the `publisher(for:)` method on any NSObject to create a Combine publisher that emits values when a specified KeyPath changes. This is useful for observing changes in Objective-C objects. ```swift private final class KVOAbleNSObject: NSObject { @objc dynamic var intValue: Int = 0 @objc dynamic var boolValue: Bool = false } let foo = KVOAbleNSObject() let _ = foo.publisher(for: \.intValue) .sink { someValue in print("value updated to: >>\(someValue)<<") } ``` -------------------------------- ### Handle Publisher Data with onReceive Source: https://heckj.github.io/swiftui-notes/index.html Use `onReceive` to link publishers to local views and trigger state changes. The publisher's failure type must be ``. This subscriber allows reacting to published data by updating local view properties or transforming the data. -------------------------------- ### Test Publisher with Scheduled Send using DispatchQueue Source: https://heckj.github.io/swiftui-notes/index.html Use XCTestExpectation and DispatchQueue to test a pipeline's timing by scheduling .send() invocations. Ensure sufficient timeout is set for queued calls. ```swift func testKVOPublisher() { let expectation = XCTestExpectation(description: self.debugDescription) let foo = KVOAbleNSObject() let q = DispatchQueue(label: self.debugDescription) __**(1)** let _ = foo.publisher(for: \.intValue) .print() .sink { someValue in print("value of intValue updated to: >>\(someValue)<<") } q.asyncAfter(deadline: .now() + 0.5, execute: { __**(2)** print("Updating to foo.intValue on background queue") foo.intValue = 5 expectation.fulfill() __**(3)** }) wait(for: [expectation], timeout: 5.0) __**(4)** } ``` -------------------------------- ### Retrieve GitHub User Data with Combine Source: https://heckj.github.io/swiftui-notes/index.html Fetches a GitHub user's data, returning a publisher that emits a list containing a single user or an empty list on failure. Prevents requests for usernames shorter than 3 characters. ```swift static func retrieveGithubUser(username: String) -> AnyPublisher<[GithubAPIUser], Never> { if username.count < 3 { return Just([]).eraseToAnyPublisher() } let assembledURL = String("https://api.github.com/users/\(username)") let publisher = URLSession.shared.dataTaskPublisher(for: URL(string: assembledURL)!) .handleEvents(receiveSubscription: { _ in networkActivityPublisher.send(true) }, receiveCompletion: { _ in networkActivityPublisher.send(false) }, receiveCancel: { networkActivityPublisher.send(false) }) .tryMap { data, response -> Data in guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw APIFailureCondition.invalidServerResponse } return data } .decode(type: GithubAPIUser.self, decoder: JSONDecoder()) .map { [$0] } .catch { // When I originally wrote this method, I was returning // a GithubAPIUser? optional. // I ended up converting this to return an empty // list as the "error output replacement" so that I could // represent that the current value requested didn't *have* a // correct github API response. return Just([]) } .eraseToAnyPublisher() return publisher } ``` -------------------------------- ### first Source: https://heckj.github.io/swiftui-notes/index.html Publishes the first element of a stream and then finishes. If no values are received before the stream finishes, no values are published. ```APIDOC ## first ### Description Publishes the first element received from the upstream publisher and then completes. ### Details This operator passes through the first value it receives and then sends a `.finish` completion. If no values are received before the upstream publisher completes, no values are published. ```swift .first() ``` ``` -------------------------------- ### Update UI from User Input with Combine Source: https://heckj.github.io/swiftui-notes/index.html This snippet shows how to connect a UITextField's changes to a Combine publisher. Use @Published to create a publisher from a variable, and then subscribe to it to trigger network requests and update UI elements declaratively. Ensure proper handling of background queues and main run loop updates. ```swift import UIKit import Combine class ViewController: UIViewController { @IBOutlet weak var github_id_entry: UITextField! __**(1)** var usernameSubscriber: AnyCancellable? // username from the github_id_entry field, updated via IBAction // @Published is creating a publisher $username of type @Published var username: String = "" __**(2)** // github user retrieved from the API publisher. As it's updated, it // is "wired" to update UI elements @Published private var githubUserData: [GithubAPIUser] = [] // MARK - Actions @IBAction func githubIdChanged(_ sender: UITextField) { username = sender.text ?? "" __**(3)** print("Set username to ", username) } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. usernameSubscriber = $username __**(4)** .throttle(for: 0.5, scheduler: myBackgroundQueue, latest: true) __**(5)** // ^^ scheduler myBackGroundQueue publishes resulting elements // into that queue, resulting on this processing moving off the // main runloop. .removeDuplicates() __**(6)** .print("username pipeline: ") // debugging output for pipeline .map { username -> AnyPublisher<[GithubAPIUser], Never> in __**(7)** return GithubAPI.retrieveGithubUser(username: username) } // ^^ type returned by retrieveGithubUser is a Publisher, so we use // switchToLatest to resolve the publisher to its value // to return down the chain, rather than returning a // publisher down the pipeline. .switchToLatest() __**(8)** // using a sink to get the results from the API search lets us // get not only the user, but also any errors attempting to get it. .receive(on: RunLoop.main) .assign(to: \.githubUserData, on: self) __**(9)** ``` -------------------------------- ### Wrap Future with Deferred for Demand-Based Execution Source: https://heckj.github.io/swiftui-notes/index.html Demonstrates how to use `Deferred` to make a `Future` publisher execute its closure only when a subscription is made, enabling it to work with operators like `retry`. The `Deferred` publisher invokes its closure, which in turn creates and resolves the `Future`. ```swift let deferredPublisher = Deferred { __**(1)** return Future { promise in __**(2)** self.asyncAPICall(sabotage: false) { (grantedAccess, err) in if let err = err { return promise(.failure(err)) } return promise(.success(grantedAccess)) } } }.eraseToAnyPublisher() ``` -------------------------------- ### Drop Elements Until Another Publisher Emits (dropUntilOutput) Source: https://heckj.github.io/swiftui-notes/index.html Use dropUntilOutput to ignore elements from an upstream publisher until a second publisher emits a value. Errors from either publisher will terminate the pipeline. ```swift .drop(untilOutputFrom: triggerPublisher) ``` -------------------------------- ### 使用 breakpointOnError 操作符触发调试器 Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html breakpointOnError 操作符是一个便捷的工具,可以在管道中发生任何错误时自动触发调试器,无需额外的配置。 ```swift .breakpointOnError() ``` -------------------------------- ### Use min() operator Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Use the min() operator when the upstream publisher's output type conforms to Comparable to find the minimum value upon completion. ```swift .min() ``` -------------------------------- ### Create Publisher from Swift Result Type Source: https://heckj.github.io/swiftui-notes/index_zh-CN.html Combine extends Swift's standard `Result` type with a `.publisher` property. This allows you to convert a `Result` instance into a publisher that emits the success value or a failure error, followed by a .finished or .failure completion. ```swift Result.publisher ``` -------------------------------- ### Handle Network Activity with PassthroughSubject Source: https://heckj.github.io/swiftui-notes/index.html Exposes a publisher to indicate network request status. Closures within `handleEvents` trigger updates to this publisher on subscription, completion, or cancellation. ```swift static let networkActivityPublisher = PassthroughSubject() ``` -------------------------------- ### Wrap Contacts Access Request with Future Source: https://heckj.github.io/swiftui-notes/index.html Illustrates how to wrap a completion-handler-based asynchronous API, like requesting Contacts access, into a Combine Future publisher. The Future's closure is executed immediately upon subscription. ```swift import Contacts let futureAsyncPublisher = Future { promise in __**(1)** CNContactStore().requestAccess(for: .contacts) { grantedAccess, err in __**(2)** // err is an optional if let err = err { __**(3)** promise(.failure(err)) } return promise(.success(grantedAccess)) __**(4)** } } ```