Classes

The following classes are available globally.

  • A ServerBootstrap is an easy way to bootstrap a ServerSocketChannel when creating network servers.

    Example:

        let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
        defer {
            try! group.syncShutdownGracefully()
        }
        let bootstrap = ServerBootstrap(group: group)
            // Specify backlog and enable SO_REUSEADDR for the server itself
            .serverChannelOption(ChannelOptions.backlog, value: 256)
            .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
    
            // Set the handlers that are applied to the accepted child `Channel`s.
            .childChannelInitializer { channel in
                // Ensure we don't read faster then we can write by adding the BackPressureHandler into the pipeline.
                channel.pipeline.addHandler(BackPressureHandler()).flatMap { () in
                    // make sure to instantiate your `ChannelHandlers` inside of
                    // the closure as it will be invoked once per connection.
                    channel.pipeline.addHandler(MyChannelHandler())
                }
            }
    
            // Enable SO_REUSEADDR for the accepted Channels
            .childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
            .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16)
            .childChannelOption(ChannelOptions.recvAllocator, value: AdaptiveRecvByteBufferAllocator())
        let channel = try! bootstrap.bind(host: host, port: port).wait()
        /* the server will now be accepting connections */
    
        try! channel.closeFuture.wait() // wait forever as we never close the Channel
    

    The EventLoopFuture returned by bind will fire with a ServerSocketChannel. This is the channel that owns the listening socket. Each time it accepts a new connection it will fire a SocketChannel through the ChannelPipeline via fireChannelRead: as a result, the ServerSocketChannel operates on Channels as inbound messages. Outbound messages are not supported on a ServerSocketChannel which means that each write attempt will fail.

    Accepted SocketChannels operate on ByteBuffer as inbound data, and IOData as outbound data.

    See more

    Declaration

    Swift

    public final class ServerBootstrap
  • A ClientBootstrap is an easy way to bootstrap a SocketChannel when creating network clients.

    Usually you re-use a ClientBootstrap once you set it up and called connect multiple times on it. This way you ensure that the same EventLoops will be shared across all your connections.

    Example:

        let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        defer {
            try! group.syncShutdownGracefully()
        }
        let bootstrap = ClientBootstrap(group: group)
            // Enable SO_REUSEADDR.
            .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
            .channelInitializer { channel in
                // always instantiate the handler _within_ the closure as
                // it may be called multiple times (for example if the hostname
                // resolves to both IPv4 and IPv6 addresses, cf. Happy Eyeballs).
                channel.pipeline.addHandler(MyChannelHandler())
            }
        try! bootstrap.connect(host: "example.org", port: 12345).wait()
        /* the Channel is now connected */
    

    The connected SocketChannel will operate on ByteBuffer as inbound and on IOData as outbound messages.

    See more

    Declaration

    Swift

    public final class ClientBootstrap
  • A DatagramBootstrap is an easy way to bootstrap a DatagramChannel when creating datagram clients and servers.

    Example:

        let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        defer {
            try! group.syncShutdownGracefully()
        }
        let bootstrap = DatagramBootstrap(group: group)
            // Enable SO_REUSEADDR.
            .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
            .channelInitializer { channel in
                channel.pipeline.addHandler(MyChannelHandler())
            }
        let channel = try! bootstrap.bind(host: "127.0.0.1", port: 53).wait()
        /* the Channel is now ready to send/receive datagrams */
    
        try channel.closeFuture.wait()  // Wait until the channel un-binds.
    

    The DatagramChannel will operate on AddressedEnvelope<ByteBuffer> as inbound and outbound messages.

    See more

    Declaration

    Swift

    public final class DatagramBootstrap
  • A list of ChannelHandlers that handle or intercept inbound events and outbound operations of a Channel. ChannelPipeline implements an advanced form of the Intercepting Filter pattern to give a user full control over how an event is handled and how the ChannelHandlers in a pipeline interact with each other.

    Creation of a pipeline

    Each Channel has its own ChannelPipeline and it is created automatically when a new Channel is created.

    How an event flows in a pipeline

    The following diagram describes how I/O events are typically processed by ChannelHandlers in a ChannelPipeline. An I/O event is handled by either a ChannelInboundHandler or a ChannelOutboundHandler and is forwarded to the next handler in the ChannelPipeline by calling the event propagation methods defined in ChannelHandlerContext, such as ChannelHandlerContext.fireChannelRead and ChannelHandlerContext.write.

                                                       I/O Request
                                                       via `Channel` or
                                                       `ChannelHandlerContext`
                                                         |
     +---------------------------------------------------+---------------+
     |                           ChannelPipeline         |               |
     |                                TAIL              \|/              |
     |    +---------------------+            +-----------+----------+    |
     |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
     |    +----------+----------+            +-----------+----------+    |
     |              /|\                                  |               |
     |               |                                  \|/              |
     |    +----------+----------+            +-----------+----------+    |
     |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
     |    +----------+----------+            +-----------+----------+    |
     |              /|\                                  .               |
     |               .                                   .               |
     | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
     |        [ method call]                       [method call]         |
     |               .                                   .               |
     |               .                                  \|/              |
     |    +----------+----------+            +-----------+----------+    |
     |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
     |    +----------+----------+            +-----------+----------+    |
     |              /|\                                  |               |
     |               |                                  \|/              |
     |    +----------+----------+            +-----------+----------+    |
     |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
     |    +----------+----------+            +-----------+----------+    |
     |              /|\             HEAD                 |               |
     +---------------+-----------------------------------+---------------+
                     |                                  \|/
     +---------------+-----------------------------------+---------------+
     |               |                                   |               |
     |       [ Socket.read ]                    [ Socket.write ]         |
     |                                                                   |
     |  SwiftNIO Internal I/O Threads (Transport Implementation)         |
     +-------------------------------------------------------------------+
    

    An inbound event is handled by the inbound handlers in the head-to-tail direction as shown on the left side of the diagram. An inbound handler usually handles the inbound data generated by the I/O thread on the bottom of the diagram. The inbound data is often read from a remote peer via the actual input operation such as Socket.read. If an inbound event goes beyond the tail inbound handler, it is discarded silently, or logged if it needs your attention.

    An outbound event is handled by the outbound handlers in the tail-to-head direction as shown on the right side of the diagram. An outbound handler usually generates or transforms the outbound traffic such as write requests. If an outbound event goes beyond the head outbound handler, it is handled by an I/O thread associated with the Channel. The I/O thread often performs the actual output operation such as Socket.write.

    For example, let us assume that we created the following pipeline:

    ChannelPipeline p = ...
    let future = p.add(name: "1", handler: InboundHandlerA()).flatMap {
      p.add(name: "2", handler: InboundHandlerB())
    }.flatMap {
      p.add(name: "3", handler: OutboundHandlerA())
    }.flatMap {
      p.add(name: "4", handler: OutboundHandlerB())
    }.flatMap {
      p.add(name: "5", handler: InboundOutboundHandlerX())
    }
    // Handle the future as well ....
    

    In the example above, a class whose name starts with Inbound is an inbound handler. A class whose name starts with Outbound is an outbound handler.

    In the given example configuration, the handler evaluation order is 1, 2, 3, 4, 5 when an event goes inbound. When an event goes outbound, the order is 5, 4, 3, 2, 1. On top of this principle, ChannelPipeline skips the evaluation of certain handlers to shorten the stack depth:

    • 3 and 4 don’t implement ChannelInboundHandler, and therefore the actual evaluation order of an inbound event will be: 1, 2, and 5.
    • 1 and 2 don’t implement ChannelOutboundHandler, and therefore the actual evaluation order of a outbound event will be: 5, 4, and 3.
    • If 5 implements both ChannelInboundHandler and ChannelOutboundHandler, the evaluation order of an inbound and a outbound event could be 125 and 543 respectively.

    Forwarding an event to the next handler

    As you might noticed in the diagram above, a handler has to invoke the event propagation methods in ChannelHandlerContext to forward an event to its next handler. Those methods include:

    Building a pipeline

    A user is supposed to have one or more ChannelHandlers in a ChannelPipeline to receive I/O events (e.g. read) and to request I/O operations (e.g. write and close). For example, a typical server will have the following handlers in each channel’s pipeline, but your mileage may vary depending on the complexity and characteristics of the protocol and business logic:

    • Protocol Decoder - translates binary data (e.g. ByteBuffer) into a struct / class
    • Protocol Encoder - translates a struct / class into binary data (e.g. ByteBuffer)
    • Business Logic Handler - performs the actual business logic (e.g. database access)

    Thread safety

    A ChannelHandler can be added or removed at any time because a ChannelPipeline is thread safe.

    See more

    Declaration

    Swift

    public final class ChannelPipeline : ChannelInvoker
  • Every ChannelHandler has – when added to a ChannelPipeline – a corresponding ChannelHandlerContext which is the way ChannelHandlers can interact with other ChannelHandlers in the pipeline.

    Most ChannelHandlers need to send events through the ChannelPipeline which they do by calling the respective method on their ChannelHandlerContext. In fact all the ChannelHandler default implementations just forward the event using the ChannelHandlerContext.

    Many events are instrumental for a ChannelHandler‘s life-cycle and it is therefore very important to send them at the right point in time. Often, the right behaviour is to react to an event and then forward it to the next ChannelHandler.

    See more

    Declaration

    Swift

    public final class ChannelHandlerContext : ChannelInvoker
  • An EventLoop that is embedded in the current running context with no external control.

    Unlike more complex EventLoops, such as SelectableEventLoop, the EmbeddedEventLoop has no proper eventing mechanism. Instead, reads and writes are fully controlled by the entity that instantiates the EmbeddedEventLoop. This property makes EmbeddedEventLoop of limited use for many application purposes, but highly valuable for testing and other kinds of mocking.

    Warning

    Unlike SelectableEventLoop, EmbeddedEventLoop is not thread-safe. This is because it is intended to be run in the thread that instantiated it. Users are responsible for ensuring they never call into the EmbeddedEventLoop in an unsynchronized fashion.
    See more

    Declaration

    Swift

    public final class EmbeddedEventLoop : EventLoop
  • EmbeddedChannel is a Channel implementation that does neither any actual IO nor has a proper eventing mechanism. The prime use-case for EmbeddedChannel is in unit tests when you want to feed the inbound events and check the outbound events manually.

    To feed events through an EmbeddedChannel‘s ChannelPipeline use EmbeddedChannel.writeInbound which accepts data of any type. It will then forward that data through the ChannelPipeline and the subsequent ChannelInboundHandler will receive it through the usual channelRead event. The user is responsible for making sure the first ChannelInboundHandler expects data of that type.

    EmbeddedChannel automatically collects arriving outbound data and makes it available one-by-one through readOutbound.

    Note

    EmbeddedChannel is currently only compatible with EmbeddedEventLoops and cannot be used with SelectableEventLoops from for example MultiThreadedEventLoopGroup.

    Warning

    Unlike other Channels, EmbeddedChannel is not thread-safe. This is because it is intended to be run in the thread that instantiated it. Users are responsible for ensuring they never call into an EmbeddedChannel in an unsynchronized fashion. EmbeddedEventLoops notes also apply as EmbeddedChannel uses an EmbeddedEventLoop as its EventLoop.
    See more

    Declaration

    Swift

    public final class EmbeddedChannel : Channel
  • Returned once a task was scheduled to be repeatedly executed on the EventLoop.

    A RepeatedTask allows the user to cancel() the repeated scheduling of further tasks.

    See more

    Declaration

    Swift

    public final class RepeatedTask
  • An EventLoopGroup which will create multiple EventLoops, each tied to its own NIOThread.

    The effect of initializing a MultiThreadedEventLoopGroup is to spawn numberOfThreads fresh threads which will all run their own EventLoop. Those threads will not be shut down until shutdownGracefully or syncShutdownGracefully is called.

    Note

    It’s good style to call MultiThreadedEventLoopGroup.shutdownGracefully or MultiThreadedEventLoopGroup.syncShutdownGracefully when you no longer need this EventLoopGroup. In many cases that is just before your program exits.

    Warning

    Unit tests often spawn one MultiThreadedEventLoopGroup per unit test to force isolation between the tests. In those cases it’s important to shut the MultiThreadedEventLoopGroup down at the end of the test. A good place to start a MultiThreadedEventLoopGroup is the setUp method of your XCTestCase subclass, a good place to shut it down is the tearDown method.
    See more

    Declaration

    Swift

    public final class MultiThreadedEventLoopGroup : EventLoopGroup
  • Holder for a result that will be provided later.

    Functions that promise to do work asynchronously can return an EventLoopFuture<Value>. The recipient of such an object can then observe it to be notified when the operation completes.

    The provider of a EventLoopFuture<Value> can create and return a placeholder object before the actual result is available. For example:

    func getNetworkData(args) -> EventLoopFuture<NetworkResponse> {
        let promise = eventLoop.makePromise(of: NetworkResponse.self)
        queue.async {
            . . . do some work . . .
            promise.succeed(response)
            . . . if it fails, instead . . .
            promise.fail(error)
        }
        return promise.futureResult
    }
    

    Note that this function returns immediately; the promise object will be given a value later on. This behaviour is common to Future/Promise implementations in many programming languages. If you are unfamiliar with this kind of object, the following resources may be helpful:

    If you receive a EventLoopFuture<Value> from another function, you have a number of options: The most common operation is to use flatMap() or map() to add a function that will be called with the eventual result. Both methods returns a new EventLoopFuture<Value> immediately that will receive the return value from your function, but they behave differently. If you have a function that can return synchronously, the map function will transform the result of type Value to a the new result of type NewValue and return an EventLoopFuture<NewValue>.

    let networkData = getNetworkData(args)
    
    // When network data is received, convert it.
    let processedResult: EventLoopFuture<Processed> = networkData.map { (n: NetworkResponse) -> Processed in
        ... parse network data ....
        return processedResult
    }
    

    If however you need to do more asynchronous processing, you can call flatMap(). The return value of the function passed to flatMap must be a new EventLoopFuture<NewValue> object: the return value of flatMap() is a new EventLoopFuture<NewValue> that will contain the eventual result of both the original operation and the subsequent one.

    // When converted network data is available, begin the database operation.
    let databaseResult: EventLoopFuture<DBResult> = processedResult.flatMap { (p: Processed) -> EventLoopFuture<DBResult> in
        return someDatabaseOperation(p)
    }
    

    In essence, future chains created via flatMap() provide a form of data-driven asynchronous programming that allows you to dynamically declare data dependencies for your various operations.

    EventLoopFuture chains created via flatMap() are sufficient for most purposes. All of the registered functions will eventually run in order. If one of those functions throws an error, that error will bypass the remaining functions. You can use flatMapError() to handle and optionally recover from errors in the middle of a chain.

    At the end of an EventLoopFuture chain, you can use whenSuccess() or whenFailure() to add an observer callback that will be invoked with the result or error at that point. (Note: If you ever find yourself invoking promise.succeed() from inside a whenSuccess() callback, you probably should use flatMap() or cascade(to:) instead.)

    EventLoopFuture objects are typically obtained by:

    • Using .flatMap() on an existing future to create a new future for the next step in a series of operations.
    • Initializing an EventLoopFuture that already has a value or an error

    Threading and Futures

    One of the major performance advantages of NIO over something like Node.js or Python’s asyncio is that NIO will by default run multiple event loops at once, on different threads. As most network protocols do not require blocking operation, at least in their low level implementations, this provides enormous speedups on machines with many cores such as most modern servers.

    However, it can present a challenge at higher levels of abstraction when coordination between those threads becomes necessary. This is usually the case whenever the events on one connection (that is, one Channel) depend on events on another one. As these Channels may be scheduled on different event loops (and so different threads) care needs to be taken to ensure that communication between the two loops is done in a thread-safe manner that avoids concurrent mutation of shared state from multiple loops at once.

    The main primitives NIO provides for this use are the EventLoopPromise and EventLoopFuture. As their names suggest, these two objects are aware of event loops, and so can help manage the safety and correctness of your programs. However, understanding the exact semantics of these objects is critical to ensuring the safety of your code.

    Callbacks

    The most important principle of the EventLoopPromise and EventLoopFuture is this: all callbacks registered on an EventLoopFuture will execute on the thread corresponding to the event loop that created the Future, regardless of what thread succeeds or fails the corresponding EventLoopPromise.

    This means that if your code created the EventLoopPromise, you can be extremely confident of what thread the callback will execute on: after all, you held the event loop in hand when you created the EventLoopPromise. However, if your code is handed an EventLoopFuture or EventLoopPromise, and you want to register callbacks on those objects, you cannot be confident that those callbacks will execute on the same EventLoop that your code does.

    This presents a problem: how do you ensure thread-safety when registering callbacks on an arbitrary EventLoopFuture? The short answer is that when you are holding an EventLoopFuture, you can always obtain a new EventLoopFuture whose callbacks will execute on your event loop. You do this by calling EventLoopFuture.hop(to:). This function returns a new EventLoopFuture whose callbacks are guaranteed to fire on the provided event loop. As an added bonus, hopTo will check whether the provided EventLoopFuture was already scheduled to dispatch on the event loop in question, and avoid doing any work if that was the case.

    This means that for any EventLoopFuture that your code did not create itself (via EventLoopPromise.futureResult), use of hopTo is strongly encouraged to help guarantee thread-safety. It should only be elided when thread-safety is provably not needed.

    The thread affinity of EventLoopFutures is critical to writing safe, performant concurrent code without boilerplate. It allows you to avoid needing to write or use locks in your own code, instead using the natural synchronization of the EventLoop to manage your thread-safety. In general, if any of your ChannelHandlers or EventLoopFuture callbacks need to invoke a lock (either directly or in the form of DispatchQueue) this should be considered a code smell worth investigating: the EventLoop-based synchronization guarantees of EventLoopFuture should be sufficient to guarantee thread-safety.

    See more

    Declaration

    Swift

    public final class EventLoopFuture<Value>
  • A NIOFileHandle is a handle to an open file.

    When creating a NIOFileHandle it takes ownership of the underlying file descriptor. When a NIOFileHandle is no longer needed you must close it or take back ownership of the file descriptor using takeDescriptorOwnership.

    Note

    One underlying file descriptor should usually be managed by one NIOFileHandle only.

    Warning

    Failing to manage the lifetime of a NIOFileHandle correctly will result in undefined behaviour.

    Warning

    NIOFileHandle objects are not thread-safe and are mutable. They also cannot be fully thread-safe as they refer to a global underlying file descriptor.

    See more

    Declaration

    Swift

    public final class NIOFileHandle : FileDescriptor
  • A thread pool that should be used if some (kernel thread) blocking work needs to be performed for which no non-blocking API exists.

    When using NIO it is crucial not to block any of the EventLoops as that leads to slow downs or stalls of arbitrary other work. Unfortunately though there are tasks that applications need to achieve for which no non-blocking APIs exist. In those cases NIOThreadPool can be used but should be treated as a last resort.

    Note

    The prime example for missing non-blocking APIs is file IO on UNIX. The OS does not provide a usable and truly non-blocking API but with NonBlockingFileIO NIO provides a high-level API for file IO that should be preferred to running blocking file IO system calls directly on NIOThreadPool. Under the covers NonBlockingFileIO will use NIOThreadPool on all currently supported platforms though.
    See more

    Declaration

    Swift

    public final class NIOThreadPool
  • A ThreadSpecificVariable is a variable that can be read and set like a normal variable except that it holds different variables per thread.

    ThreadSpecificVariable is thread-safe so it can be used with multiple threads at the same time but the value returned by currentValue is defined per thread.

    See more

    Declaration

    Swift

    public final class ThreadSpecificVariable<Value> where Value : AnyObject