Skip to content

Commit

Permalink
Merge pull request #9 from Valbrand/documentation
Browse files Browse the repository at this point in the history
Adding documentation
  • Loading branch information
Valbrand committed Aug 23, 2017
2 parents 5c9b231 + 7a13b25 commit e29e388
Show file tree
Hide file tree
Showing 58 changed files with 7,374 additions and 4 deletions.
34 changes: 32 additions & 2 deletions Sources/Tibei/client/ClientMessenger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,36 @@

import UIKit

/**
Represents a messenger that sends and receives messages from the client-side.
*/
public class ClientMessenger: Messenger {
var services: [String:NetService] = [:]
var isReady: Bool = false

/// The chain of registered message responders
public var responders: ResponderChain = ResponderChain()
var connection: Connection?
var serviceBrowser: TibeiServiceBrowser

/**
Default initializer.
Note that after this call, the `ClientMessenger` instance won't be browsing for services yet.
- SeeAlso: ```browseForServices(withIdentifier:)```
*/
public init() {
self.serviceBrowser = TibeiServiceBrowser()

self.serviceBrowser.delegate = self
}

/**
Browses for services that are currently being published via Bonjour with a certain service identifier.
- Parameter serviceIdentifier: Service identifier being currently browsed for
*/
public func browseForServices(withIdentifier serviceIdentifier: String) {
if self.serviceBrowser.isBrowsing {
if !self.services.isEmpty {
Expand All @@ -34,12 +50,18 @@ public class ClientMessenger: Messenger {
self.serviceBrowser.startBrowsing(forServiceType: serviceIdentifier)
}

/// Stops browsing for services.
/// ConnectionResponders will stop receiving `availableServicesChanged` calls after this is called.
public func stopBrowsingForServices() {
self.serviceBrowser.stopBrowsing()
}

public func connect(serviceName: String) throws {
guard let service = self.services[serviceName] else {
/// Connects to a service based on its provider's identifier
///
/// - Parameter serviceName: The name of the service to connect to
/// - Throws: `ConnectionError.inexistentService` if the `serviceName` parameter is provided, and `ConnectionError.connectionFailure` if some error occurred while obtaining input stream from connection
public func connect(serviceIdentifier: String) throws {
guard let service = self.services[serviceIdentifier] else {
throw ConnectionError.inexistentService
}

Expand All @@ -57,13 +79,18 @@ public class ClientMessenger: Messenger {
newConnection.open()
}

/// Disconnects from the currently connected service. Does nothing if this messenger is not connected to any service.
public func disconnect() {
self.isReady = false

self.connection?.close()
self.connection = nil
}

/// Sends a message to the currently active connection.
///
/// - Parameter message: Message to send.
/// - Throws: `ConnectionError.notConnected` if there is no connection to send the message to.
public func sendMessage<Message: JSONConvertibleMessage>(_ message: Message) throws {
guard self.isReady else {
throw ConnectionError.notConnected
Expand All @@ -72,6 +99,9 @@ public class ClientMessenger: Messenger {
self.connection?.sendMessage(message)
}

/// Registers a new responder to this messenger's responder chain.
///
/// - Parameter responder: Responder to register in the chain.
public func registerResponder(_ responder: ConnectionResponder) {
if responder is ClientConnectionResponder {
self.responders.append(ClientResponderChainNode(responder: responder))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@

import Foundation

/**
A specialization of `ConnectionResponder` with additional behavior for the client side.
*/
public protocol ClientConnectionResponder: ConnectionResponder {
/**
If the `ClientMessenger` instance is browsing for services, this method gets called whenever a new service becomes available (or if an available service goes offline), receiving the updated service list as a parameter.
*/
func availableServicesChanged(availableServiceIDs: [String])
}

public extension ClientConnectionResponder {
/// Does nothing
func availableServicesChanged(availableServiceIDs: [String]) {
}
}
14 changes: 14 additions & 0 deletions Sources/Tibei/common/Messenger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,29 @@

import Foundation

/**
Represents an entity that receives messages from a connection. This protocol is implemented by both `ClientMessenger` and `ServerMessenger`.
*/
public protocol Messenger {
/// The chain of registered message responders
var responders: ResponderChain { get }
}

extension Messenger {
/**
Registers a new responder to the responder chain.
- Parameter responder: the `ConnectionResponder` to be registered to the chain.
*/
public func registerResponder(_ responder: ConnectionResponder) {
self.responders.append(ResponderChainNode(responder: responder))
}

/**
Removes a responder from the responder chain.
- Parameter responder: the `ConnectionResponder` to be removed from the chain.
*/
public func unregisterResponder(_ responder: ConnectionResponder) {
self.responders.remove(ResponderChainNode(responder: responder))
}
Expand Down
37 changes: 37 additions & 0 deletions Sources/Tibei/common/responderChain/ConnectionResponder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,66 @@
//
//

/**
Represents an entity that responds to received messages. It can explicitly state which types of messages it expects, and will only be prompted to process a message if appropriated.
*/
public protocol ConnectionResponder: class {
/**
An array of types representing the messages it wants to receive.
*/
var allowedMessages: [JSONConvertibleMessage.Type] { get }

/**
Processes a message that was received through an active connection.
- Parameters:
- message: The message received through the connection
- connectionID: The identifier of the connection that received the message
*/
func processMessage(_ message: JSONConvertibleMessage, fromConnectionWithID connectionID: ConnectionID)
/**
Notifies the responder that a connection has been accepted
- Parameter connectionID: The identifier of the accepted connection
*/
func acceptedConnection(withID connectionID: ConnectionID)
/**
Notifies the responder that a connection has been lost
- Parameter connectionID: The identifier of the lost connection
*/
func lostConnection(withID connectionID: ConnectionID)
/**
Processes an error that occurred while handling an active connection
- Parameters:
- error: The error that occurred.
- connectionID: The identifier of the connection that raised the error.
*/
func processError(_ error: Error, fromConnectionWithID connectionID: ConnectionID?)
}

public extension ConnectionResponder {
/**
An empty array. Note that this will cause the responder not to receive any messages. You have to explicitly state the messages that your responder will handle.
*/
var allowedMessages: [JSONConvertibleMessage.Type] {
return []
}

/// :nodoc:
func processMessage(_ message: JSONConvertibleMessage, fromConnectionWithID connectionID: ConnectionID) {
}

/// :nodoc:
func acceptedConnection(withID connectionID: ConnectionID) {
}

/// :nodoc:
func lostConnection(withID connectionID: ConnectionID) {
}

/// :nodoc:
func processError(_ error: Error, fromConnectionWithID connectionID: ConnectionID?) {
}
}
8 changes: 8 additions & 0 deletions Sources/Tibei/common/responderChain/ResponderChain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ class ResponderChainNode {
}
}

/**
In Tibei, incoming messages are processed by a chain of objects that may be set as responders in a chain. Responders must conform to the `ConnectionResponder` or `ClientConnectionResponder` protocols.
Currently, you have to manage the order in which elements are added to the chain, so take care if this is any relevant.
- SeeAlso: `ConnectionResponder`
- SeeAlso: `ClientConnectionResponder`
*/
public class ResponderChain {
var head: ResponderChainNode?
var tail: ResponderChainNode?
Expand Down
6 changes: 6 additions & 0 deletions Sources/Tibei/connection/Connection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@

import UIKit

/**
Represents a single connection between server and client. The same class is used on either side.
*/
public class Connection: NSObject, StreamDelegate {
let outwardMessagesQueue: OperationQueue = OperationQueue()
/// An unique identifier for the connection
public let identifier: ConnectionID

var input: InputStream
Expand All @@ -21,6 +25,7 @@ public class Connection: NSObject, StreamDelegate {
}
var isReady: Bool = false
var pingTimer = Timer()
/// :nodoc:
override public var hashValue: Int {
return self.identifier.id.hashValue
}
Expand Down Expand Up @@ -132,6 +137,7 @@ public class Connection: NSObject, StreamDelegate {

// MARK: - StreamDelegate protocol

/// :nodoc:
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.errorOccurred:
Expand Down
27 changes: 27 additions & 0 deletions Sources/Tibei/connection/ConnectionError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,42 @@

import Foundation

/**
An error that may occur at any point of the connection's lifecycle.
*/
public enum ConnectionError: Error {
/**
Thrown if the `OutputStream` of a `Connection` can't take any more data.
*/
case inputUnavailable
/**
Thrown if a responder receives a message with a `_type` field that is not valid (i.e. is not a string).
*/
case invalidMessageType([String:Any])
/**
Thrown if an error occurs while reading from a `Connection`'s `InputStream`.
*/
case inputError

/**
Thrown if an error occurs while writing to a `Connection`'s `OutputStream`.
*/
case outputError
/**
Thrown if a `Connection`'s output stream has no space left.
*/
case outputStreamUnavailable
/**
Thrown if an attempt to send a message is made without an active `Connection`.
*/
case notConnected

/**
Thrown if an attempt to connect to a service that doesn't exists is made.
*/
case inexistentService
/**
Thrown if an error occurred while trying to obtain a `Connection`'s input and output streams.
*/
case connectionFailure
}
5 changes: 5 additions & 0 deletions Sources/Tibei/connection/ConnectionID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@

import Foundation

/**
A struct that's used to identify existing connections. An uuid is generated upon this struct's instantiation, and its hashValue is used for comparison.
*/
public struct ConnectionID: Hashable {
let id: UUID

/// :nodoc:
public var hashValue: Int {
return self.id.hashValue
}
Expand All @@ -19,6 +23,7 @@ public struct ConnectionID: Hashable {
self.id = UUID()
}

/// :nodoc:
public static func ==(lhs: ConnectionID, rhs: ConnectionID) -> Bool {
return lhs.hashValue == rhs.hashValue
}
Expand Down
9 changes: 9 additions & 0 deletions Sources/Tibei/message/JSONConvertibleMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@

import Foundation

/**
A protocol that allows Tibei to convert your custom messages to a JSON format. Note that it is mandatory to represent your object as a dictionary. That's because Tibei embeds metadata in the data before sending it.
*/
public protocol JSONConvertibleMessage {
/**
Initializes an object from a dictionary representing the JSON object. It's called upon receiving a message
*/
init(jsonObject:[String:Any])

/**
Transforms the instance of the message into a JSON dictionary.
*/
func toJSONObject() -> [String:Any]
}

Expand Down
32 changes: 31 additions & 1 deletion Sources/Tibei/server/ServerMessenger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,38 @@

import Foundation

/**
Represents a messenger that sends and receives messages on the server side.
*/
public class ServerMessenger: Messenger {
var connections: [ConnectionID: Connection] = [:]

/**
The current responder chain
*/
public var responders: ResponderChain = ResponderChain()
var gameControllerServer: TibeiServer!

/**
Initializes the messenger. The service will **not** be published through Bonjour until `publishService` is called.
- Parameter serviceIdentifier: The string that will distinguish this service from others in the Bonjour discovery process
*/
public init(serviceIdentifier: String) {
self.gameControllerServer = TibeiServer(messenger: self, serviceIdentifier: serviceIdentifier)
}


/**
Publishes the service through Bonjour, making it discoverable to any client that searches for this server's service identifier
*/
public func publishService() {
self.gameControllerServer.publishService()
}

/**
Make this server's service undiscoverable. No clients will be able to connect from the moment this method is called.
*/
public func unpublishService() {
self.gameControllerServer.unpublishService()
}
Expand All @@ -33,14 +51,26 @@ public class ServerMessenger: Messenger {
connection.open()
}

public func sendMessage<Message: JSONConvertibleMessage>(_ message: Message, toConnectionWithID connectionID: ConnectionID) throws {
/**
Sends a message to a client identified by its connection identifier.
- Parameters:
- message: The message to be sent
- connectionID: The identifier of the connection through which the message should be sent
*/
public func sendMessage<Message: JSONConvertibleMessage>(_ message: Message, toConnectionWithID connectionID: ConnectionID) {
guard let connection = self.connections[connectionID] else {
return
}

connection.sendMessage(message)
}

/**
Sends a message to all active connections.
- Parameter message: The message to be sent
*/
public func broadcastMessage<Message: JSONConvertibleMessage>(_ message: Message){
for (_, connection) in self.connections{
connection.sendMessage(message)
Expand Down
Loading

0 comments on commit e29e388

Please sign in to comment.