Swift NIO and µExpress
Is it the same like Noze.io? How is it different? What about WebObjects?
First of all, Swift NIO is essentially a port of the popular Java Netty framework. In a way Swift NIO is for Netty developers what Noze.io is for Node developers.
Relating NIO to Noze.io is quite difficult, there is some overlap in functionality and concepts, but the goals are a little different. It is probably OK to say that NIO is quite low level and focused on raw performance and scalability, while Noze.io focuses on proper use of Swift language features (like type safety) and ease of use. While still providing excellent performance 😬
Some arbitrary notes on the 1.1 release:
- NIO “handler” objects are a little like Noze.io transform streams.
They can take in one type (e.g. a ByteBuffer) and emit a higher
level type (e.g. a HTTP request object).
- NIO wraps all objects in an
NIOAnyfor performance reasons. Which is fast but “not great”.
- Noze.io strongly types all streams. This affects piping. E.g. if a stream emits Int’s you cannot pipe that into a stream expecting bytes.
- NIO wraps all objects in an
- NIO maintains a “pipeline”. This is more explict than what Noze.io does.
In Noze.io you can just instantly form pipelines via
|(this is a good demo).
- NIO has promises/futures and uses them a lot. Those actually are typesafe in NIO. Noze.io can also give you promises, but due to the way the API is structured, this is rarely necessary (you rather form pipes).
- NIO doesn’t (seem to) have support for buffering. Noze.io streams maintain such. That affects backpressure which is automatic in Noze.io, and manual in NIO (there is a backpressure handler, but this just ties pressure to the write end, which is useless for the common examples)
- NIO itself doesn’t seem to facilitate batching. Noze.io streams always work on batches of items. This is actually more similar to Node, which also doesn’t have batching for object-streams. Only bytes are batched (NIO ByteBuffer vs. Node Buffer). Batching is a must have for many real world endpoints.
- NIO is multithreaded. Noze.io is single threaded. Well, actually they work both pretty much the same :-) (Ignoring some exceptions) NIO is single-threaded within a thread. I.e. NIO stuff is usually not thread safe (needs to be invoked from the same event-loop).
- NIO doesn’t integrate or is built around Swift Foundation. It essentially reimplements all the stuff needed. Data ➭ ByteBuffer, DispatchQueue ➭ EventLoop, Lock ➭ Lock, URLRequest ➭ HTTPRequestHead, FileHandle ➭ Channel, etc.
In general NIO has a sound design and is consistent within itself. It is however very apparent where it comes from. But more importantly it is fast and the setup looks reliable. (But don’t be confused by the 1.1 release tag, that is just typical Swift versioning. It is still in flux and not a release having 1.1 quality yet).
So, does it make sense to combine Noze.io and NIO? Jein. Noze.io currently layers on top of GCD DispatchIO to do the async operations. We already considered switching to libuv, the same thing Node uses under the hood. A big disadvantage of both, libuv and NIO, is that it introduces an external dependency. Noze.io as it is setup today is self contained and does not require any dependency at all.
The current thinking is that instead of forcing Noze.io as-is on top of NIO, we rather rethink Noze.io streams around the abstractions provided by NIO, and see what comes out of that. Streams which are NIO handlers? We’ll investigate.
The other idea is to have the Noze.io Express parts w/o the Noze.io streams. Something which we started to explore with µExpress.
Exciting times. Stay tuned.
P.S.: So what about WebObjects? Unless the design changes significantly, WO is very hard to do in Swift due to the lack of enough reflection to do r/w KVC. But maybe that is fixed in Swift 5 already - Who knows what WWDC 2018 will bring? 🤓