Skip to content

Instantly share code, notes, and snippets.

@woodycatliu
Last active April 10, 2023 06:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save woodycatliu/d6d989fd15dbe1bba66a4d0a5a5736bc to your computer and use it in GitHub Desktop.
Save woodycatliu/d6d989fd15dbe1bba66a4d0a5a5736bc to your computer and use it in GitHub Desktop.
Swift Combine extension for support assign a function or closure

Swift 5.3 Combine

With RxSwift, it can support bind to a function/closure to more readability.

Example

textField.rx.text.orEmpty.asObservable()
            .subscribeOn(MainScheduler.instance)
            .bind(onNext: receiveText(_:))
            .disposed(by: disBag)

Combine Extension For This

Version 1.0

With Combine in Swift, there is a operator assign for support bindding is achieved by KeyPath(ReferenceWritableKeyPath). ReferenceWritableKeyPath doest not support the function. But We still can use Closure Type to make this.

Code

public func assign(to action: @escaping ((Output)->()))-> AnyCancellable {
        
        sink(receiveCompletion: { _ in } , receiveValue: {
            action($0)
        })
    }

Example

 textField.textPublisher()
            .map { Optional($0) }
            .receive(on: DispatchQueue.main)
            .assign(to: changeText(_:))
            .store(in: &bag)

Now I can use it like this, but I found it is not perfect.
There are some potential crisis, like retain cycle.
So how can I do.......

Version 2.0

In order to prevent from retaining cycle, I specify the action been Instace Method, and change paramaters to Method Type and Instance. There are not any potential crisis with Retain Cycle to make new version . "

public func assign<T: AnyObject>(to action: @escaping ((T)->(Output)->()), on target: T)-> AnyCancellable {
                
        return sink(receiveCompletion: { _ in } , receiveValue: {
            [weak target] in
            guard let target = target else { return }
            action(target)($0)
        }) 
    }

Use For

 textField.textPublisher()
            .map { Optional($0) }
            .receive(on: DispatchQueue.main)
            .assign(to: NewViewController.changeText, on: self)
            .store(in: &bag)
import Foundation
import Combine
import Combine
extension Publisher where Failure == Never {
/// you can change funcation name to assign
func assignUnRetain<Root: AnyObject>(to keyPath: ReferenceWritableKeyPath<Root, Output>, on root: Root)-> AnyCancellable {
sink(receiveValue: { [weak root] in
root?[keyPath: keyPath] = $0
})
}
/// will not retain cycle
/// failure: fail to do (closure/function)
/// success: to do (closure/function)
/// on: function contain
/// - Returns: AnyCancellable
/// example: assign(failure: A.faulure, success: A.success, on: A())
public func assign<T: AnyObject>(failure failureAction: @escaping ((T)->(Failure)->()), success successAction: @escaping ((T)->(Output)->()), on target: T)-> AnyCancellable {
return sink(receiveCompletion: { [weak target] in
if let target = target,
case let .failure(error) = $0 { failureAction(target)(error) }
} , receiveValue: { [weak target] in
guard let target = target else { return }
successAction(target)($0)
})
}
/// will not retain cycle
/// action: success to do (closure/function)
/// on: function contain
/// - Returns: AnyCancellable
/// example: assign(failure: A.faulure, success: A.success, on: A())
public func assign<T: AnyObject>(to action: @escaping ((T)->(Output)->()), on target: T)-> AnyCancellable {
return sink(receiveCompletion: { _ in } , receiveValue: {
[weak target] in
guard let target = target else { return }
action(target)($0)
})
}
/// important: maybe make retain cycle
/// action: success to do (closure/function)
/// - Returns: AnyCancellable
public func assign(to action: @escaping ((Output)->()))-> AnyCancellable {
sink(receiveCompletion: { _ in } , receiveValue: {
action($0)
})
}
}
extension Publisher {
/// will not retain cycle
/// failure: fail to do (closure/function)
/// success: to do (closure/function)
/// on: function contain
/// - Returns: AnyCancellable
/// example: assign(failure: A.faulure, success: A.success, on: A())
public func assign<T: AnyObject>(failure failureAction: @escaping ((T)->(Failure)->()), success successAction: @escaping ((T)->(Output)->()), on target: T)-> AnyCancellable {
return sink(receiveCompletion: { [weak target] in
if let target = target,
case let .failure(error) = $0 { failureAction(target)(error) }
} , receiveValue: { [weak target] in
guard let target = target else { return }
successAction(target)($0)
})
}
/// will not retain cycle
/// action: success to do (closure/function)
/// on: function contain
/// - Returns: AnyCancellable
/// example: assign(failure: A.faulure, success: A.success, on: A())
public func assign<T: AnyObject>(to action: @escaping ((T)->(Output)->()), on target: T)-> AnyCancellable {
return sink(receiveCompletion: { _ in } , receiveValue: {
[weak target] in
guard let target = target else { return }
action(target)($0)
})
}
/// important: maybe make retain cycle
/// action: success to do (closure/function)
/// - Returns: AnyCancellable
public func assign(to action: @escaping ((Output)->()))-> AnyCancellable {
sink(receiveCompletion: { _ in } , receiveValue: {
action($0)
})
}
/// will not retain cycle
/// failure: fail to do (closure/function)
/// on: function contain
/// - Returns: AnyCancellable
/// example: assign(failure: A.faulure, success: A.success, on: A())
public func assign<T: AnyObject>(failure failureAction: @escaping ((T)->(Failure)->()), on target: T) -> AnyCancellable {
return sink(receiveCompletion: { [weak target] in
if let target = target,
case let .failure(error) = $0 { failureAction(target)(error) }
} , receiveValue: { _ in })
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment