2
1

More than 3 years have passed since last update.

[macOS] Target-ActionをCombineで処理する

Last updated at Posted at 2020-10-01

Combineたのしそう

ということで、Target-ActionをCombineで処理出来るようにしてみました。

for macOSですが、ちょっと変えればiOSとかでも出来るのではないでしょうか?

使い方

NSControlなどがPublisherを返せるようにしたので、以下のようになります。

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>

PublisherOutputをTarget-ActionでいうところのSenderにしていますので、あまり違和を感じずに移行できると思います。

実装

ActionPublisher

利用者が気にするべきなのは通常このActionPublisherだけです。

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

Handlingをキャンセルする時のためのAnyCancellableとして現れますが、通常はその具象型であるActionSubscriptionを気にかける必要はありません。

public struct ActionSubscription: Subscription {

    public let combineIdentifier = CombineIdentifier()
    let actionReceiver: ActionReceiver

    public func request(_ demand: Subscribers.Demand) {}

    public func cancel() {

        actionReceiver.handler = nil
    }
}

ActionReceiver

実際にTarget-Actionをハンドリングするヘルパークラスです。
このクラスは外部からは隠蔽されています。

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

Target-ActionにおけるSenderとなるクラスが準拠するべきプロトコルです。
Target-Actionがかなり緩い制約の下で動いているので必要最低限のみが宣言されています。

ここでは一般的にSenderとなりうるNSControlNSMenuItemを準拠させています。

また、ActionPublisherを取り出すメソッドをextentionとして実装しています。

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)
    }
}
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1