Component implementation structure - Swift

Create a simple non-ui component

Component Structure

The component is comprised of a swift class which implements the functionality of the component. The class can call on other classes, and additional functionality beyond the main class to implement the component.

In cases where UI is also associated with a component, additional structs which represent the UI view is implemented.

Component Protocol

A component that will be consumed and usable by the SDK must conform to a ComponentSubscriber protocol.

The ComponentSubscriber protocol ensures that the component implements several required functions and variables to make the component compatible with the SDK.

ComponentSubscriber

Basic code structure

The following is a basic implementation of the required functions of a component

import CloneableCore
import CloneablePlatformiOS

public class SampleComponent: ComponentSubscriber {
    // Required properties properties
    var staticComponentID: String
    var dynamicComponentID: String
    var component: DeployedWorkflow_components

    // Implementing protocol functions
    // Called just prior to the platform sending it's first inputs to the component
    required init(dynamicComponentID: String, staticComponentID: String, component: DeployedWorkflow_components) {
        self.dynamicComponentID = dynamicComponentID
        self.staticComponentID = staticComponentID
        self.component = component

        // Additional logic to run when init is called can be placed here
        ...
        // Required call to register this instance of the component with the cloneable framework
        // workflowFramework is a global variable which holds reference to the framework which is running the workflows
        workflowFramework?.subscribeComponent(subscriber: self, dynamicComponentID: dynamicComponentID, staticComponentID: staticComponentID)
    }

    public func componentWillDeInit(final: Bool) {
        // Handle de-initialization logic here
    }

    public func acceptNewInputs(inputs: [DataInput]) {
        // Handle new inputs here
    }
}

staticComponentID

The static component id is the unique id (uuidV4) of the component's definition.

dynamicComponentID

This unique id (uuidV4) that is assigned to the component at the time of workflow compile in the builder. This allows you to differentiate individual components in a workflow if their are multiple of the same component in the workflow

component: DeployedWorkflow_components

The full JSON definition of the component. This will have all of the information about the component, inputs, outputs, and customizable parameters for the component as they are defined by the user in the builder

Workflow Component Type

Subscribe to the workflow framework

In order for the Cloneable SDK to use a component implementation during a workflow, the component must subscribe itself to the workflowFramework when it is ready to begin receiving inputs.

In most cases it's best to subscribe at the end of the init when everything in the component is setup. In some complex cases you may have asynchronous tasks occurring which need to complete to setup the component before it is ready to accept inputs (ie. loading an AI model)

// Call subscribeComponent and pass self as the subscriber along with the component ids
workflowFramework!.subscribeComponent(subscriber: self, dynamicComponentID: dynamicComponentID, staticComponentID: staticComponentID)

Accepting Inputs

The framework will call the acceptNewInputs function in the component when inputs are being passed to the component.

Grouping of inputs

The inputs will arrive as an array of Data Input, each data input represents a single input from a single outputs. When inputs arrive together in the same array in a singular function call, we consider those inputs to be grouped into a batch together. Grouping of inputs allows you to process the inputs in advanced ways.

See:How Data Flowsfor more information.

See: Data Input for more information on how Data inputs are structured

Working with the input data

Access the data in the input at input.data. The data is stored in a type unwrapped AnyCloneableData read more about AnyCloneableData here: AnyCloneableData

Below is an example of parsing a String data type from AnyCloneableData

AnyCloneableData
func acceptNewInputs(inputs: [DataInput]) {
    if let input = inputs.first { // usually you will want to get the input by its inputID
        let inputDataType: String = input.data.getTypeAsString()
    }
}

Sending Outputs

You can send outputs from your component at any point in it's life cycle. You may also send any individual inputs at any point as there are not requirements to send all inputs at the same time. However, it is good practice to send inputs at the same time that share a group id together as this is represented in the UI of the builder together.

Outputs are sent as Data Output. See the reference for more detailed information on data references.

Data outputs must be AnyCloneableData See usage here:

AnyCloneableData

Call the output function on workflowFramework

Send your outputs as an array of DataOutput's to workflowFramework.sendOutputsToFramework

Below is a basic example for outputting data from a single output to the SDK:

import CloneableCore
import CloneablePlatformiOS

public class SampleComponent: ComponentSubscriber {
    // Required properties properties
    var staticComponentID: String
    var dynamicComponentID: String
    var component: DeployedWorkflow_components
    ...
    // function which will be called by our component to process and send an output
    func sendAnOutput() {
        // we need to get the dynamicID of the output that we are going to send
        // we are going to do this, by filtering the outputs from the component
        // to find the input with the id
        if let outputId = component.outputs.first(where: {$0.outputId == "5b7dc149-2682-45e4-8da5-772f8ffe17fa"})?.dynamicOutputId {
            // create the AnyCloneableData to wrap the string data we are going to send
            let data = AnyCloneableData(data: CloneableString("This is my output"))
            
            // construct the output
            let output = DataOutput(
                            data: data, 
                            staticComponentID: staticComponentID, 
                            dynamicComponentID: dynamicComponentID, 
                            dynamicOutputID: outputId, 
                            outputComponentType: .processing)
            
            // send the output to the framework
            workflowFramework?.sendOutputsToFramework(outputs: [output])

        }
    }
}

Notifying the component that it will de-initialize

The platform will call the componentWillDeInit function within our component prior to the component being de-initialized. The platform will also pass final if the component is going to go out of scope for a while or if it will be permanently de-initialized.

Last updated