[SWIFT] [macOS] Traiter l'action cible avec la combinaison

Combiner semble être amusant

J'ai donc essayé de rendre Target-Action traitable avec Combine.

C'est pour macOS, mais si vous le modifiez un peu, vous pouvez le faire avec iOS.

Comment utiliser

Maintenant que NSControl etc. peut renvoyer Publisher, cela ressemble à ceci:

let b = NSButton(frame: NSRect(x: 0, y: 0, width: 200, height: 120))
let cancel = b.actionPublisher().sink { print($0) }

b.performClick(nil)
// prints <NSButton: 0xXXXXXXX>

b.performClick(nil)
// prints <NSButton: 0xXXXXXXX>

Puisque ʻOutput of Publisher` est défini sur Sender dans Target-Action, je pense que vous pouvez migrer sans vous sentir trop mal à l'aise.

la mise en oeuvre

ActionPublisher Cet éditeur d'action est généralement la seule chose dont les utilisateurs doivent se soucier.

public struct ActionPublisher: Publisher {
    
    public typealias Output = ActionPerfomer
    public typealias Failure = Never
    
    private let actionReceiver: ActionReceiver
    
    init(actionPerfomer: Output) {
        
        self.actionReceiver = .init(actionPerfomer: actionPerfomer)
    }
    
    public func receive<S: Subscriber>(subscriber: S)
        where Failure == S.Failure, Output == S.Input {
            
            actionReceiver.handler = { performer in _ = subscriber.receive(performer) }
            
            subscriber.receive(subscription: ActionSubscription(actionReceiver: actionReceiver))
    }
}

ActionSubscription Il apparaît comme ʻAnyCancellable lors de l'annulation de Handling, mais généralement vous n'avez pas à vous soucier de sa forme concrète, ʻAction Subscription.

public struct ActionSubscription: Subscription {
    
    public let combineIdentifier = CombineIdentifier()
    let actionReceiver: ActionReceiver
    
    public func request(_ demand: Subscribers.Demand) {}
    
    public func cancel() {
        
        actionReceiver.handler = nil
    }
}

ActionReceiver Une classe d'assistance qui gère réellement Target-Action. Cette classe est cachée de l'extérieur.

internal final class ActionReceiver: NSObject {
    
    private(set) weak var actionPerfomer: ActionPerfomer!
    var handler: ((ActionPerfomer) -> Void)?
    
    init(actionPerfomer: ActionPerfomer) {
        
        self.actionPerfomer = actionPerfomer
        
        super.init()
        
        actionPerfomer.target = self
        actionPerfomer.action = #selector(action)
    }
    
    @IBAction private func action(_ sender: Any) {
        
        handler?(actionPerfomer)
    }
}

ActionPerfomer C'est le protocole que la classe qui devient l'expéditeur dans Target-Action doit se conformer. Puisque Target-Action fonctionne sous des contraintes assez lâches, seul le minimum requis est déclaré.

Ici, «NSControl» et «NSMenuItem», qui peuvent être Sender en général, sont conformes.

De plus, la méthode pour récupérer ʻActionPublisher` est implémentée en tant qu'extension.

public protocol ActionPerfomer: AnyObject {
    
    var target: AnyObject?  { get set }
    var action: Selector? { get set }
}

extension NSControl: ActionPerfomer {}
extension NSMenuItem: ActionPerfomer {}

extension ActionPerfomer {
    
    func actionPublisher() -> ActionPublisher {
        
        .init(actionPerfomer: self)
    }
}

Recommended Posts

[macOS] Traiter l'action cible avec la combinaison
Message de validation de processus avec Decorator
URLSession avec URLSession et Combine normalement
Communication de processus utilisant AMQP avec RabbitMQ