CKSwift/Action.swift (121 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
import Foundation
import ComponentKit
/// Represents a parameter-less action.
public struct Action {
/// The type erased handler representing the action.
private let handler: () -> Void
fileprivate init(handler: @escaping () -> Void) {
self.handler = handler
}
init<View: CKSwift.View>(handler: @escaping (View) -> Void) {
let actionResolver = ScopedResponderActionResolver<View>()
self.handler = {
if let view = actionResolver.targetView {
handler(view)
}
}
}
init<View: CKSwift.View>(handler: @escaping (View) -> () -> Void) {
self.init() { view in
handler(view)()
}
}
/// Creates an action from a target + handler. Use this when the target of an action isn't a view/component.
public init<Target: AnyObject>(target: Target, handler: @escaping (Target) -> () -> Void) {
self.handler = { [weak weakTarget = target] in
guard let strongTarget = weakTarget else { return }
handler(strongTarget)()
}
}
/// Invokes the action.
func invoke() {
handler()
}
/// The objective-c bridgeable representation of a parameter-less action.
public var swiftBridge: Action_SwiftBridge {
return {
self.invoke()
}
}
}
/// Represents an action which can be invoked at a later date. An action will always
/// be invoked on the first non-null generation of a component.
public struct ActionWith<Param> {
/// The type erased handler representing the action.
private let handler: (Param) -> Void
private init(handler: @escaping (Param) -> Void) {
self.handler = handler
}
init<View: CKSwift.View>(handler: @escaping (View, Param) -> Void) {
let actionResolver = ScopedResponderActionResolver<View>()
self.handler = { param in
if let view = actionResolver.targetView {
handler(view, param)
}
}
}
init<View: CKSwift.View>(handler: @escaping (View) -> (Param) -> Void) {
self.init() { view, param in
handler(view)(param)
}
}
/// Creates an action from a target + handler. Use this when the target of an action isn't a view/component.
public init<Target: AnyObject>(target: Target, handler: @escaping (Target) -> (Param) -> Void) {
self.handler = { [weak weakTarget = target] param in
guard let strongTarget = weakTarget else { return }
handler(strongTarget)(param)
}
}
/// Invokes the action.
/// - Parameter param: The parameter to pass to the action.
public func invoke(_ param: Param) {
handler(param)
}
/// The objective-c bridgeable representation of a parametrized action.
public var swiftBridge: ActionWithId_SwiftBridge {
return { aParam in
guard let param = aParam as? Param else {
fatalError("Expecting to find \(Param.self) but got \(type(of: aParam))")
}
self.invoke(param)
}
}
/// The objective-c bridgeable representation of a parametrized action (to be removed soon)
public var swiftBridgeWithType: ActionWithId_SwiftBridge {
swiftBridge
}
/// Demotes an action with a parameter to a parameter less param.
/// - Parameter param: The value to pass to the action handler.
/// - Returns: The parameterless `Action`.
public func demote(passing param: Param) -> Action {
Action {
handler(param)
}
}
}
/// Marker protocol to indicate that a view can supply actions.
public protocol Actionable : TreeNodeLinkableView { }
extension View where Self: Actionable {
public func onAction<Param>(_ handler: @escaping (Self, Param) -> Void) -> ActionWith<Param> {
ActionWith(handler: handler)
}
public func onAction<Param>(_ handler: @escaping (Self) -> (Param) -> Void) -> ActionWith<Param> {
ActionWith(handler: handler)
}
public func onAction(_ handler: @escaping (Self) -> Void) -> Action {
Action(handler: handler)
}
public func onAction(_ handler: @escaping (Self) -> () -> Void) -> Action {
Action(handler: handler)
}
/// Creates a `Action` which receives a param not specified by the caller but on creation.
public func onAction<Param>(_ handler: @escaping (Self) -> (Param) -> Void, passing param: Param) -> Action {
Action() { view in
handler(view)(param)
}
}
}
/// Artificial object which helps to share responder related logic
private struct ScopedResponderActionResolver<View: CKSwift.View> {
private let responder: ScopedResponder
private let key: ScopedResponderKey
init() {
var responder: ScopedResponder?
var key: ScopedResponderKey = -1
guard CKSwiftInitializeAction(SwiftComponent<View>.self, &responder, &key),
let scopedResponder = responder,
key != -1 else {
fatalError("Attempting to initialise action outside of the right body function")
}
self.responder = scopedResponder
self.key = key
}
var targetView: View? {
guard let untypedComponent = responder.responder(forKey: key) as? Component else { return nil }
guard let component = untypedComponent as? SwiftComponent<View> else {
fatalError("Expecting \(SwiftComponent<View>.self) when invoking action but got \(untypedComponent) instead")
}
return component.view
}
}