CoatySwift Tutorial
This tutorial will show you how to set up a minimal CoatySwift application. Please note that the tutorial is focused on setting up the base structure needed to get Coaty up and running, with little to no focus on actual application logic. To gain in-depth insights into CoatySwift, take a look at the Developer Guide.
The source code of this tutorial can be found in the
CoatySwiftExample
folder of the CoatySwift repo. Just clone the repo, wait for XCode to resolve all of the dependencies and open the Example/Example.xcodeproj
in XCode.
Note
Unfortunately there is no sequential way (where everything compiles after each step) to
set up a Coaty container until you added all of the required components beforehand.
Do not worry if you are getting errors, if you have followed all steps in this tutorial
correctly, your project will compile successfully.
Prerequisite
Start a Broker
CoatySwift needs an MQTT broker in order to run. We recommend checking out one of the following brokers:
- Coaty broker (development broker provided by Coaty JS)
- Mosquitto
- HiveMQ
- VerneMQ
Step 1
Decide on a Message Broker and start it.
Its host IP address and port are important for Step 3a.
Section 1
Create a New Project and
Integrate CoatySwift
First, create a new project CoatySwiftExample
and integrate CoatySwift.
CoatySwift is available as a CocoaPod.
Step 1
Integrate CoatySwift in your Podfile
and install it via
pod install
.
Podfile
target 'CoatySwiftExample' do
use_frameworks!
# Pods for CoatySwiftExample
pod 'CoatySwift', '~> 2.0'
end
Section 2
Create a new Coaty Object Type
If you want to use custom objects with your Coaty application, you will have to define each of them. We will now create such an example object type and integrate it accordingly.
Step 1
Add custom objects by creating classes that extend from CoatyObject
or one of the other builtin Coaty core types.
Ensure that the class for your custom Coaty object type is registered with CoatySwift.
This is necessary to properly decode custom objects received over the wire.
Also make sure that the class implements the Codable
interface, implementing the methods required init(from decoder: Decoder)
and override func encode(to encoder: Encoder)
.
ExampleObject.swift
import Foundation
import CoatySwift
final class ExampleObject: CoatyObject {
// MARK: - Class registration.
override class var objectType: String {
return register(objectType: "hello.coaty.ExampleObject", with: self)
}
// MARK: - Properties.
let myValue: String
// MARK: - Initializers.
init(myValue: String) {
self.myValue = myValue
super.init(coreType: .CoatyObject,
objectType: ExampleObject.objectType,
objectId: .init(),
name: "ExampleObject Name :)")
}
// MARK: Codable methods.
enum CodingKeys: String, CodingKey {
case myValue
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.myValue = try container.decode(String.self, forKey: .myValue)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.myValue, forKey: .myValue)
}
}
Section 3
Setting up the Structure
Step 1
Create a global variable container
.
This will hold a reference to our Coaty container for the lifetime of the app.
It is needed because otherwise all of our references go out of scope and
communication is terminated.
AppDelegate.swift
import UIKit
import CoatySwift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
/// Save a reference of your container in the app delegate to
/// make sure it stays alive during the entire lifetime of the app.
var container: Container?
...
}
Step 2a
Create a new function in your AppDelegate.swift
called
launchContainer()
. Make sure to call this method in your
application(_ application:, didFinishLaunchingWithOptions:)
method. Also make sure to call the container.shutdown()
method
in the applicationWillTerminate(_ application:)
method to
gracefully release all container components when the app terminates.
This will be our main method for setting up the Coaty
application.
Step 2b
Register controllers
and custom object types
in the
launchContainer()
method.
Here, you define which Coaty controllers and object types you want to use in your application.
Note that the controller keys do not have to have the exact name of their
controller class. Feel free to give them any unique names you want. The mapping is the
important thing, so which name maps to what controller class.
AppDelegate.swift
// MARK: - Coaty Container setup methods.
/// This method sets up the Coaty container necessary to run our application.
private func launchContainer() {
// Register controllers and custom object types.
let components = Components(controllers: [
"ExampleControllerPublish": ExampleControllerPublish.self,
"ExampleControllerObserve": ExampleControllerObserve.self
],
objectTypes: [
ExampleObject.self
])
...
}
Step 3a
Create a new method called createExampleConfiguration()
.
Note the mqttClientOptions
variable in particular. These define your broker's
host address and port.
AppDelegate.swift
/// This method creates an exemplary Coaty configuration. You can use it as a basis for your
/// application.
private func createExampleConfiguration() -> Configuration? {
return try? .build { config in
// This part defines optional common options shared by all container components.
config.common = CommonOptions()
// Adjusts the logging level of CoatySwift messages, which is especially
// helpful if you want to test or debug applications (default is .error).
config.common?.logLevel = .info
// Configure an expressive `name` of the container's identity here.
config.common?.agentIdentity = ["name": "Example Agent"]
// You can also add extra information to your configuration in the form of a
// [String: String] dictionary.
config.common?.extra = ["ContainerVersion": "0.0.1"]
// Define communication-related options, such as the host address of your broker
// (default is "localhost") and the port it exposes (default is 1883). Define a
// unqiue communication namespace for your application and make sure to immediately
// connect with the broker, indicated by `shouldAutoStart: true`.
let mqttClientOptions = MQTTClientOptions(host: brokerHost,
port: UInt16(brokerPort))
config.communication = CommunicationOptions(namespace: "com.example",
mqttClientOptions: mqttClientOptions,
shouldAutoStart: true)
}
}
Step 3b
Go back to your launchContainer()
method and load the configuration from
the
createExampleConfiguration()
method.
AppDelegate.swift
// MARK: - Coaty Container setup methods.
/// This method sets up the Coaty container necessary to run our application.
private func launchContainer() {
...
// Create a configuration.
guard let configuration = createExampleConfiguration() else {
print("Invalid configuration! Please check your options.")
return
}
...
}
Step 4
Resolve everything and assign the variable container
with the return
value of
container.resolve(...)
, while passing in
our previously defined components
and configuration
.
AppDelegate.swift
// MARK: - Coaty Container setup methods.
/// This method sets up the Coaty container necessary to run our application.
private func launchContainer() {
...
// Resolve everything!
container = Container.resolve(components: components,
configuration: configuration)
}
Section 4
Create a new Controller
To be able to add your own communication business logic for a CoatySwift application you
need to define one or
multiple controllers. All controllers are created and managed by CoatySwift through lifecycle
methods.
For the example, we will create two controllers named ExampleControllerPublish
and ExampleControllerObserve
. ExampleControllerPublish
will periodically publish an Advertise event for an ExampleObject
that will
be received and printed by ExampleControllerObserve
.
Step 4a
To create ExampleControllerPublish
, extend the Controller
class.
In the onCommunicationManagerStarting()
method, we set up a timer that
periodically executes the advertiseExampleObject()
method below.
The advertiseExampleObject()
method implements a basic communication pattern for
Coaty applications. We create an ExampleObject
, add it to a new
AdvertiseEvent
and publish it using the publishAdvertise()
method of the CommunicationManager
.
ExampleControllerPublish.swift
import Foundation
import CoatySwift
import RxSwift
class ExampleControllerPublish: Controller {
private var timer: Timer?
override func onCommunicationManagerStarting() {
super.onCommunicationManagerStarting()
// Start RxSwift timer to publish an AdvertiseEvent every 5 seconds.
_ = Observable
.timer(RxTimeInterval.seconds(0),
period: RxTimeInterval.seconds(5),
scheduler: MainScheduler.instance)
.subscribe(onNext: { (i) in
self.advertiseExampleObject(i + 1)
})
.disposed(by: self.disposeBag)
}
func advertiseExampleObject(_ counter: Int) {
// Create the object.
let object = ExampleObject(myValue: "Hello Coaty! (\(counter))")
// Create the event.
let event = try! AdvertiseEvent.with(object: object)
// Publish the event by the communication manager.
self.communicationManager.publishAdvertise(event)
print("[ExampleControllerPublish] published Advertise event:\t\(object.myValue)")
}
}
Step 4b
We add a second controller named ExampleControllerObserve
and observe Advertise events for objects of a certain object type. Again,
we use the CommunicationManager
and call the
observeAdvertise(withObjectType)
method. The method returns an Observable
.
We subscribe to onNext
events that are emitted by the
observable and extract the ExampleObject
from the event data.
Since we observe only CoatyObjects of the specified custom object type,
we can force cast the received CoatyObject to an ExampleObject
and print a message with the myValue
attribute.
ExampleControllerObserve.swift
import Foundation
import CoatySwift
import RxSwift
class ExampleControllerObserve: Controller {
override func onCommunicationManagerStarting() {
super.onCommunicationManagerStarting()
self.observeAdvertiseExampleObjects()
}
private func observeAdvertiseExampleObjects() {
try! self.communicationManager
.observeAdvertise(withObjectType: ExampleObject.objectType)
.subscribe(onNext: { (event) in
let object = event.data.object as! ExampleObject
print("[ExampleControllerObserve] received Advertise event:\t\(object.myValue)")
})
.disposed(by: self.disposeBag)
}
}