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 } }