1648944600
subscrib
_
__ _____| |_ _ __ ___ __ _ _ __ ___
\ \/ / __| __| '__/ _ \/ _` | '_ ` _ \
> <\__ \ |_| | | __/ (_| | | | | | |
/_/\_\___/\__|_| \___|\__,_|_| |_| |_|
Example
import xs from 'xstream'
// Tick every second incremental numbers,
// only pass even numbers, then map them to their square,
// and stop after 5 seconds has passed
var stream = xs.periodic(1000)
.filter(i => i % 2 === 0)
.map(i => i * i)
.endWhen(xs.periodic(5000).take(1))
// So far, the stream is idle.
// As soon as it gets its first listener, it starts executing.
stream.addListener({
next: i => console.log(i),
error: err => console.error(err),
complete: () => console.log('completed'),
})
Installation
npm install xstream
Usage
import xs from 'xstream'
var xs = require('xstream').default
API
addListener
removeListener
subscribe
map
mapTo
filter
take
drop
last
startWith
endWhen
fold
replaceError
flatten
compose
remember
debug
imitate
shamefullySendNext
shamefullySendError
shamefullySendComplete
setDebugListener
To keep the core of xstream small and simple, less frequently-used methods are available under the xstream/extra
directory, and must be imported separately. See EXTRA_DOCS for documentation.
Overview
XStream has four fundamental types: Stream, Listener, Producer, and MemoryStream.
A Stream is an event emitter with multiple Listeners. When an event happens on the Stream, it is broadcast to all its Listeners at the same time.
Streams have methods attached to them called operators, such as map
, filter
, fold
, take
, etc. When called, an operator creates and returns another Stream. Once the first Stream broadcasts an event, the event will pass through the operator logic and the output Stream may perhaps broadcast its own event based on the source one.
You can also trigger an event to happen on a Stream with the shamefullySend*
methods. But you don't want to do that. Really, avoid doing that because it's not the reactive way and you'll be missing the point of this library. Ok?
A Listener is an object with one to three functions attached to it: next
, error
, and complete
. There is usually one function for each type of event a Stream may emit but only next
is always required.
next
events are the typical type, they deliver a value.error
events abort (stop) the execution of the Stream, and happen when something goes wrong in the Stream (or upstream somewhere in the chain of operators)complete
events signal the peaceful stop of the execution of the Stream.This is an example of a typical listener:
var listener = {
next: (value) => {
console.log('The Stream gave me a value: ', value);
},
error: (err) => {
console.error('The Stream gave me an error: ', err);
},
complete: () => {
console.log('The Stream told me it is done.');
},
}
And this is how you would attach that Listener to a Stream:
stream.addListener(listener)
And when you think the Listener is done, you can remove it from the Stream:
stream.removeListener(listener)
A Producer is like a machine that produces events to be broadcast on a Stream.
Events from a Stream must come from somewhere, right? That's why we need Producers. They are objects with two functions attached: start(listener)
and stop()
. Once you call start
with a listener
, the Producer will start generating events and it will send those to the listener. When you call stop()
, the Producer should quit doing its own thing.
Streams are also Listeners (actually they are "InternalListeners", not Listeners, but that's a detail you can ignore), so you can theoretically give a Stream as the listener in producer.start(streamAsListener)
. Then, essentially the Producer is now generating events that will be broadcast on the Stream. Nice, huh? Now a bunch of listeners can be attached to the Stream and they can all get those events originally coming from the Producer. That's why xs.create(producer)
receives a Producer to be the heart of a new Stream. Check this out:
var producer = {
start: function (listener) {
this.id = setInterval(() => listener.next('yo'), 1000)
},
stop: function () {
clearInterval(this.id)
},
id: 0,
}
// This fellow delivers a 'yo' next event every 1 second
var stream = xs.create(producer)
But remember, a Producer has only one listener, but a Stream may have many listeners.
You may wonder "when is start
and stop
called", and that's actually a fairly tricky topic, so let's get back to that soon. First let me tell you about MemoryStreams.
A MemoryStream is just like a Stream: it has operators, it can have listeners attached, you can shamefully send events to it, blabla. But it has one special property: it has memory. It remembers the most recent (but just one) next
event that it sent to its listeners.
Why is that useful? If a new Listener is added after that next
event was sent, the MemoryStream will get its value stored in memory and will send it to the newly attached Listener.
This is important so MemoryStreams can represent values or pieces of state which are relevant even after some time has passed. You don't want to lose those, you want to keep them and send them to Listeners that arrive late, after the event was originally created.
A Stream controls its Producer according to its number of Listeners, using reference counting with a synchronous start
and a cancelable asynchronous stop
. That's how a Stream starts and stops, basically. Usually this part of XStream is not so relevant to remember when building applications, but if you want to understand it for debugging or curiosity, it's explained in plain English below.
When you create a Stream with xs.create(producer)
, the start()
function of the Producer is not yet called. The Stream is still "idle". It has the Producer, but the Producer was not turned on.
Once the first Listener is added to the Stream, the number of Listeners attached suddenly changed from 0
to 1
. That's when the Stream calls start
, because after all there is at least one Listener interested in this Stream.
More Listeners may be added in the future, but they don't affect whether the Producer will continue working or stop. Just the first Listener dictates when the Stream starts.
What matters for stopping the Producer is stream.removeListener
. When the last Listener leaves (or in other words, when the number of Listeners suddenly changes from 1
to 0
), the Stream schedules producer.stop()
to happen on the next event loop. That is, asynchronously. If, however, a new Listener is added (number goes from 0
to 1
) before that scheduled moment, the producer.stop()
will be cancelled, and the Producer will continue generating events for its Stream normally.
The reason the Producer is not suddenly (synchronously) stopped, is that it is often necessary to swap the single listener of a Stream, but still keep its ongoing execution. For instance:
var listenerA = {/* ... */}
var listenerB = {/* ... */}
// number goes from 0 to 1, so the Stream's Producer starts
stream.addListener(listenerA)
// ...
// number goes from 1 to 0, but then immediately goes back
// to 1, because listenerB was added
stream.removeListener(listenerA)
stream.addListener(listenerB)
// Stream's Producer does not stop, everything continues as before
It's still useful to eventually (asynchronously) stop a Stream's internal Producer, because you don't want useless computation lying around producing gibberish. At least I don't.
Factories
Factories are functions that create Streams, such as xs.create()
, xs.periodic()
, etc.
create(producer)
Creates a new Stream given a Producer.
producer: Producer
An optional Producer that dictates how to start, generate events, and stop the Stream.createWithMemory(producer)
Creates a new MemoryStream given a Producer.
producer: Producer
An optional Producer that dictates how to start, generate events, and stop the Stream.never()
Creates a Stream that does nothing when started. It never emits any event.
Marble diagram:
never
-----------------------
empty()
Creates a Stream that immediately emits the "complete" notification when started, and that's it.
Marble diagram:
empty
-|
throw(error)
Creates a Stream that immediately emits an "error" notification with the value you passed as the error
argument when the stream starts, and that's it.
Marble diagram:
throw(X)
-X
error
The error event to emit on the created stream.from(input)
Creates a stream from an Array, Promise, or an Observable.
input: Array|PromiseLike|Observable
The input to make a stream from.of(a, b)
Creates a Stream that immediately emits the arguments that you give to of, then completes.
Marble diagram:
of(1,2,3)
123|
a
The first value you want to emit as an event on the stream.b
The second value you want to emit as an event on the stream. One or more of these values may be given as arguments.fromArray(array)
Converts an array to a stream. The returned stream will emit synchronously all the items in the array, and then complete.
Marble diagram:
fromArray([1,2,3])
123|
array: Array
The array to be converted as a stream.fromPromise(promise)
Converts a promise to a stream. The returned stream will emit the resolved value of the promise, and then complete. However, if the promise is rejected, the stream will emit the corresponding error.
Marble diagram:
fromPromise( ----42 )
-----------------42|
promise: PromiseLike
The promise to be converted as a stream.fromObservable(observable)
Converts an Observable into a Stream.
observable: any
The observable to be converted as a stream.periodic(period)
Creates a stream that periodically emits incremental numbers, every period
milliseconds.
Marble diagram:
periodic(1000)
---0---1---2---3---4---...
period: number
The interval in milliseconds to use as a rate of emission.merge(stream1, stream2)
Blends multiple streams together, emitting events from all of them concurrently.
merge takes multiple streams as arguments, and creates a stream that behaves like each of the argument streams, in parallel.
Marble diagram:
--1----2-----3--------4---
----a-----b----c---d------
merge
--1-a--2--b--3-c---d--4---
stream1: Stream
A stream to merge together with other streams.stream2: Stream
A stream to merge together with other streams. Two or more streams may be given as arguments.combine(stream1, stream2)
Combines multiple input streams together to return a stream whose events are arrays that collect the latest events from each input stream.
combine internally remembers the most recent event from each of the input streams. When any of the input streams emits an event, that event together with all the other saved events are combined into an array. That array will be emitted on the output stream. It's essentially a way of joining together the events from multiple streams.
Marble diagram:
--1----2-----3--------4---
----a-----b-----c--d------
combine
----1a-2a-2b-3b-3c-3d-4d--
stream1: Stream
A stream to combine together with other streams.stream2: Stream
A stream to combine together with other streams. Multiple streams, not just two, may be given as arguments.Methods and Operators
Methods are functions attached to a Stream instance, like stream.addListener()
. Operators are also methods, but return a new Stream, leaving the existing Stream unmodified, except for the fact that it has a child Stream attached as Listener.
addListener(listener)
Adds a Listener to the Stream.
listener: Listener
removeListener(listener)
Removes a Listener from the Stream, assuming the Listener was added to it.
listener: Listener\<T>
subscribe(listener)
Adds a Listener to the Stream returning a Subscription to remove that listener.
listener: Listener
map(project)
Transforms each event from the input Stream through a project
function, to get a Stream that emits those transformed events.
Marble diagram:
--1---3--5-----7------
map(i => i * 10)
--10--30-50----70-----
project: Function
A function of type (t: T) => U
that takes event t
of type T
from the input Stream and produces an event of type U
, to be emitted on the output Stream.mapTo(projectedValue)
It's like map
, but transforms each input event to always the same constant value on the output Stream.
Marble diagram:
--1---3--5-----7-----
mapTo(10)
--10--10-10----10----
projectedValue
A value to emit on the output Stream whenever the input Stream emits any value.filter(passes)
Only allows events that pass the test given by the passes
argument.
Each event from the input stream is given to the passes
function. If the function returns true
, the event is forwarded to the output stream, otherwise it is ignored and not forwarded.
Marble diagram:
--1---2--3-----4-----5---6--7-8--
filter(i => i % 2 === 0)
------2--------4---------6----8--
passes: Function
A function of type (t: T) => boolean
that takes an event from the input stream and checks if it passes, by returning a boolean.take(amount)
Lets the first amount
many events from the input stream pass to the output stream, then makes the output stream complete.
Marble diagram:
--a---b--c----d---e--
take(3)
--a---b--c|
amount: number
How many events to allow from the input stream before completing the output stream.drop(amount)
Ignores the first amount
many events from the input stream, and then after that starts forwarding events from the input stream to the output stream.
Marble diagram:
--a---b--c----d---e--
drop(3)
--------------d---e--
amount: number
How many events to ignore from the input stream before forwarding all events from the input stream to the output stream.last()
When the input stream completes, the output stream will emit the last event emitted by the input stream, and then will also complete.
Marble diagram:
--a---b--c--d----|
last()
-----------------d|
startWith(initial)
Prepends the given initial
value to the sequence of events emitted by the input stream. The returned stream is a MemoryStream, which means it is already remember()
'd.
Marble diagram:
---1---2-----3---
startWith(0)
0--1---2-----3---
initial
The value or event to prepend.endWhen(other)
Uses another stream to determine when to complete the current stream.
When the given other
stream emits an event or completes, the output stream will complete. Before that happens, the output stream will behaves like the input stream.
Marble diagram:
---1---2-----3--4----5----6---
endWhen( --------a--b--| )
---1---2-----3--4--|
other
Some other stream that is used to know when should the output stream of this operator complete.fold(accumulate, seed)
"Folds" the stream onto itself.
Combines events from the past throughout the entire execution of the input stream, allowing you to accumulate them together. It's essentially like Array.prototype.reduce
. The returned stream is a MemoryStream, which means it is already remember()
'd.
The output stream starts by emitting the seed
which you give as argument. Then, when an event happens on the input stream, it is combined with that seed value through the accumulate
function, and the output value is emitted on the output stream. fold
remembers that output value as acc
("accumulator"), and then when a new input event t
happens, acc
will be combined with that to produce the new acc
and so forth.
Marble diagram:
------1-----1--2----1----1------
fold((acc, x) => acc + x, 3)
3-----4-----5--7----8----9------
accumulate: Function
A function of type (acc: R, t: T) => R
that takes the previous accumulated value acc
and the incoming event from the input stream and produces the new accumulated value.seed
The initial accumulated value, of type R
.replaceError(replace)
Replaces an error with another stream.
When (and if) an error happens on the input stream, instead of forwarding that error to the output stream, replaceError will call the replace
function which returns the stream that the output stream will replicate. And, in case that new stream also emits an error, replace
will be called again to get another stream to start replicating.
Marble diagram:
--1---2-----3--4-----X
replaceError( () => --10--| )
--1---2-----3--4--------10--|
replace: Function
A function of type (err) => Stream
that takes the error that occurred on the input stream or on the previous replacement stream and returns a new stream. The output stream will behave like the stream that this function returns.flatten()
Flattens a "stream of streams", handling only one nested stream at a time (no concurrency).
If the input stream is a stream that emits streams, then this operator will return an output stream which is a flat stream: emits regular events. The flattening happens without concurrency. It works like this: when the input stream emits a nested stream, flatten will start imitating that nested one. However, as soon as the next nested stream is emitted on the input stream, flatten will forget the previous nested one it was imitating, and will start imitating the new nested one.
Marble diagram:
--+--------+---------------
\ \
\ ----1----2---3--
--a--b----c----d--------
flatten
-----a--b------1----2---3--
compose(operator)
Passes the input stream to a custom operator, to produce an output stream.
compose is a handy way of using an existing function in a chained style. Instead of writing outStream = f(inStream)
you can write outStream = inStream.compose(f)
.
operator: function
A function that takes a stream as input and returns a stream as well.remember()
Returns an output stream that behaves like the input stream, but also remembers the most recent event that happens on the input stream, so that a newly added listener will immediately receive that memorised event.
debug(labelOrSpy)
Returns an output stream that identically behaves like the input stream, but also runs a spy
function for each event, to help you debug your app.
debug takes a spy
function as argument, and runs that for each event happening on the input stream. If you don't provide the spy
argument, then debug will just console.log
each event. This helps you to understand the flow of events through some operator chain.
Please note that if the output stream has no listeners, then it will not start, which means spy
will never run because no actual event happens in that case.
Marble diagram:
--1----2-----3-----4--
debug
--1----2-----3-----4--
labelOrSpy: function
A string to use as the label when printing debug information on the console, or a 'spy' function that takes an event as argument, and does not need to return anything.imitate(target)
imitate changes this current Stream to emit the same events that the other
given Stream does. This method returns nothing.
This method exists to allow one thing: circular dependency of streams. For instance, let's imagine that for some reason you need to create a circular dependency where stream first$
depends on stream second$
which in turn depends on first$
:
import delay from 'xstream/extra/delay'
var first$ = second$.map(x => x * 10).take(3);
var second$ = first$.map(x => x + 1).startWith(1).compose(delay(100));
However, that is invalid JavaScript, because second$
is undefined on the first line. This is how imitate can help solve it:
import delay from 'xstream/extra/delay'
var secondProxy$ = xs.create();
var first$ = secondProxy$.map(x => x * 10).take(3);
var second$ = first$.map(x => x + 1).startWith(1).compose(delay(100));
secondProxy$.imitate(second$);
We create secondProxy$
before the others, so it can be used in the declaration of first$
. Then, after both first$
and second$
are defined, we hook secondProxy$
with second$
with imitate()
to tell that they are "the same". imitate
will not trigger the start of any stream, it just binds secondProxy$
and second$
together.
The following is an example where imitate()
is important in Cycle.js applications. A parent component contains some child components. A child has an action stream which is given to the parent to define its state:
const childActionProxy$ = xs.create();
const parent = Parent({...sources, childAction$: childActionProxy$});
const childAction$ = parent.state$.map(s => s.child.action$).flatten();
childActionProxy$.imitate(childAction$);
Note, though, that imitate()
does not support MemoryStreams. If we would attempt to imitate a MemoryStream in a circular dependency, we would either get a race condition (where the symptom would be "nothing happens") or an infinite cyclic emission of values. It's useful to think about MemoryStreams as cells in a spreadsheet. It doesn't make any sense to define a spreadsheet cell A1
with a formula that depends on B1
and cell B1
defined with a formula that depends on A1
.
If you find yourself wanting to use imitate()
with a MemoryStream, you should rework your code around imitate()
to use a Stream instead. Look for the stream in the circular dependency that represents an event stream, and that would be a candidate for creating a proxy Stream which then imitates the target Stream.
target: Stream
The other stream to imitate on the current one. Must not be a MemoryStream.shamefullySendNext(value)
Forces the Stream to emit the given value to its listeners.
As the name indicates, if you use this, you are most likely doing something The Wrong Way. Please try to understand the reactive way before using this method. Use it only when you know what you are doing.
value
The "next" value you want to broadcast to all listeners of this Stream.shamefullySendError(error)
Forces the Stream to emit the given error to its listeners.
As the name indicates, if you use this, you are most likely doing something The Wrong Way. Please try to understand the reactive way before using this method. Use it only when you know what you are doing.
error: any
The error you want to broadcast to all the listeners of this Stream.shamefullySendComplete()
Forces the Stream to emit the "completed" event to its listeners.
As the name indicates, if you use this, you are most likely doing something The Wrong Way. Please try to understand the reactive way before using this method. Use it only when you know what you are doing.
setDebugListener(listener)
Adds a "debug" listener to the stream. There can only be one debug listener, that's why this is 'setDebugListener'. To remove the debug listener, just call setDebugListener(null).
A debug listener is like any other listener. The only difference is that a debug listener is "stealthy": its presence/absence does not trigger the start/stop of the stream (or the producer inside the stream). This is useful so you can inspect what is going on without changing the behavior of the program. If you have an idle stream and you add a normal listener to it, the stream will start executing. But if you set a debug listener on an idle stream, it won't start executing (not until the first normal listener is added).
As the name indicates, we don't recommend using this method to build app logic. In fact, in most cases the debug operator works just fine. Only use this one if you know what you're doing.
listener: Listener\<T>
FAQ
Q: Why does imitate()
support a Stream but not a MemoryStream?
A: MemoryStreams are meant for representing "values over time" (your age), while Streams represent simply events (your birthdays). MemoryStreams are usually initialized with a value, and imitate()
is meant for creating circular dependencies of streams. If we would attempt to imitate a MemoryStream in a circular dependency, we would either get a race condition (where the symptom would be "nothing happens") or an infinite cyclic emission of values.
If you find yourself wanting to use imitate()
with a MemoryStream, you should rework your code around imitate()
to use a Stream instead. Look for the stream in the circular dependency that represents an event stream, and that would be a candidate for creating a MimicStream which then imitates the real event stream.
Q: What's the difference between xstream and RxJS?
A: Read this blog post on the topic. In short:
Q: What is the equivalent of withLatestFrom
in xstream?
A: withLatestFrom
is implemented as an extra named sampleCombine
.
Misc.
xstream is built by staltz and TylorS.
Read the CHANGELOG for release notes of all versions of xstream.
Author: Staltz
Source Code: https://github.com/staltz/xstream
License: MIT License
#typescript #javascript #stream
1661592007
⚠️ This list is no longer being updated. For my latest Swift tips, checkout the "Tips" section on Swift by Sundell.
One of the things I really love about Swift is how I keep finding interesting ways to use it in various situations, and when I do - I usually share them on Twitter. Here's a collection of all the tips & tricks that I've shared so far. Each entry has a link to the original tweet, if you want to respond with some feedback or question, which is always super welcome! 🚀
Also make sure to check out all of my other Swift content:
🚀 Here are some quick tips to make async tests faster & more stable:
// BEFORE:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
detector.detectMentions(in: string) { mentions in
XCTAssertEqual(mentions, ["johnsundell"])
}
sleep(2)
}
}
// AFTER:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
var mentions: [String]?
let expectation = self.expectation(description: #function)
detector.detectMentions(in: string) {
mentions = $0
expectation.fulfill()
}
waitForExpectations(timeout: 10)
XCTAssertEqual(mentions, ["johnsundell"])
}
}
For more on async testing, check out "Unit testing asynchronous Swift code".
✍️ Adding support for the new Apple Pencil double-tap feature is super easy! All you have to do is to create a UIPencilInteraction
, add it to a view, and implement one delegate method. Hopefully all pencil-compatible apps will soon adopt this.
let interaction = UIPencilInteraction()
interaction.delegate = self
view.addInteraction(interaction)
extension ViewController: UIPencilInteractionDelegate {
func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
// Handle pencil double-tap
}
}
For more on using this and other iPad Pro features, check out "Building iPad Pro features in Swift".
😎 Here's a cool function that combines a value with a function to return a closure that captures that value, so that it can be called without any arguments. Super useful when working with closure-based APIs and we want to use some of our properties without having to capture self
.
func combine<A, B>(_ value: A, with closure: @escaping (A) -> B) -> () -> B {
return { closure(value) }
}
// BEFORE:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = { [weak self] in
guard let self = self else {
return
}
self.productManager.startCheckout(for: self.product)
}
}
}
// AFTER:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = combine(product, with: productManager.startCheckout)
}
}
💉 When I'm only using a single function from a dependency, I love to inject that function as a closure, instead of having to create a protocol and inject the whole object. Makes dependency injection & testing super simple.
final class ArticleLoader {
typealias Networking = (Endpoint) -> Future<Data>
private let networking: Networking
init(networking: @escaping Networking = URLSession.shared.load) {
self.networking = networking
}
func loadLatest() -> Future<[Article]> {
return networking(.latestArticles).decode()
}
}
For more on this technique, check out "Simple Swift dependency injection with functions".
💥 It's cool that you can easily assign a closure as a custom NSException
handler. This is super useful when building things in Playgrounds - since you can't use breakpoints - so instead of just signal SIGABRT
, you'll get the full exception description if something goes wrong.
NSSetUncaughtExceptionHandler { exception in
print(exception)
}
❤️ I love that in Swift, we can use the type system to make our code so much more self-documenting - one way of doing so is to use type aliases to give the primitive types that we use a more semantic meaning.
extension List.Item {
// Using type aliases, we can give semantic meaning to the
// primitive types that we use, without having to introduce
// wrapper types.
typealias Index = Int
}
extension List {
enum Mutation {
// Our enum cases now become a lot more self-documenting,
// without having to add additional parameter labels to
// explain them.
case add(Item, Item.Index)
case update(Item, Item.Index)
case remove(Item.Index)
}
}
For more on self-documenting code, check out "Writing self-documenting Swift code".
🤯 A little late night prototyping session reveals that protocol constraints can not only be applied to extensions - they can also be added to protocol definitions!
This is awesome, since it lets us easily define specialized protocols based on more generic ones.
protocol Component {
associatedtype Container
func add(to container: Container)
}
// Protocols that inherit from other protocols can include
// constraints to further specialize them.
protocol ViewComponent: Component where Container == UIView {
associatedtype View: UIView
var view: View { get }
}
extension ViewComponent {
func add(to container: UIView) {
container.addSubview(view)
}
}
For more on specializing protocols, check out "Specializing protocols in Swift".
📦 Here's a super handy extension on Swift's Optional
type, which gives us a really nice API for easily unwrapping an optional, or throwing an error in case the value turned out to be nil
:
extension Optional {
func orThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
switch self {
case .some(let value):
return value
case .none:
throw errorExpression()
}
}
}
let file = try loadFile(at: path).orThrow(MissingFileError())
For more ways that optionals can be extended, check out "Extending optionals in Swift".
👩🔬 Testing code that uses static APIs can be really tricky, but there's a way that it can often be done - using Swift's first class function capabilities!
Instead of accessing that static API directly, we can inject the function we want to use, which enables us to mock it!
// BEFORE
class FriendsLoader {
func loadFriends(then handler: @escaping (Result<[Friend]>) -> Void) {
Networking.loadData(from: .friends) { result in
...
}
}
}
// AFTER
class FriendsLoader {
typealias Handler<T> = (Result<T>) -> Void
typealias DataLoadingFunction = (Endpoint, @escaping Handler<Data>) -> Void
func loadFriends(using dataLoading: DataLoadingFunction = Networking.loadData,
then handler: @escaping Handler<[Friend]>) {
dataLoading(.friends) { result in
...
}
}
}
// MOCKING IN TESTS
let dataLoading: FriendsLoader.DataLoadingFunction = { _, handler in
handler(.success(mockData))
}
friendsLoader.loadFriends(using: dataLoading) { result in
...
}
🐾 Swift's pattern matching capabilities are so powerful! Two enum cases with associated values can even be matched and handled by the same switch case - which is super useful when handling state changes with similar data.
enum DownloadState {
case inProgress(progress: Double)
case paused(progress: Double)
case cancelled
case finished(Data)
}
func downloadStateDidChange(to state: DownloadState) {
switch state {
case .inProgress(let progress), .paused(let progress):
updateProgressView(with: progress)
case .cancelled:
showCancelledMessage()
case .finished(let data):
process(data)
}
}
🅰 One really nice benefit of Swift multiline string literals - even for single lines of text - is that they don't require quotes to be escaped. Perfect when working with things like HTML, or creating a custom description for an object.
let html = highlighter.highlight("Array<String>")
XCTAssertEqual(html, """
<span class="type">Array</span><<span class="type">String</span>>
""")
💎 While it's very common in functional programming, the reduce
function might be a bit of a hidden gem in Swift. It provides a super useful way to transform a sequence into a single value.
extension Sequence where Element: Equatable {
func numberOfOccurrences(of target: Element) -> Int {
return reduce(0) { result, element in
guard element == target else {
return result
}
return result + 1
}
}
}
You can read more about transforming collections in "Transforming collections in Swift".
📦 When I use Codable in Swift, I want to avoid manual implementations as much as possible, even when there's a mismatch between my code structure and the JSON I'm decoding.
One way that can often be achieved is to use private data containers combined with computed properties.
struct User: Codable {
let name: String
let age: Int
var homeTown: String { return originPlace.name }
private let originPlace: Place
}
private extension User {
struct Place: Codable {
let name: String
}
}
extension User {
struct Container: Codable {
let user: User
}
}
🚢 Instead of using feature branches, I merge almost all of my code directly into master - and then I use feature flags to conditionally enable features when they're ready. That way I can avoid merge conflicts and keep shipping!
extension ListViewController {
func addSearchIfNeeded() {
// Rather than having to keep maintaining a separate
// feature branch for a new feature, we can use a flag
// to conditionally turn it on.
guard FeatureFlags.searchEnabled else {
return
}
let resultsVC = SearchResultsViewController()
let searchVC = UISearchController(
searchResultsController: resultsVC
)
searchVC.searchResultsUpdater = resultsVC
navigationItem.searchController = searchVC
}
}
You can read more about feature flags in "Feature flags in Swift".
💾 Here I'm using tuples to create a lightweight hierarchy for my data, giving me a nice structure without having to introduce any additional types.
struct CodeSegment {
var tokens: (
previous: String?,
current: String
)
var delimiters: (
previous: Character?
next: Character?
)
}
handle(segment.tokens.current)
You can read more about tuples in "Using tuples as lightweight types in Swift"
3️⃣ Whenever I have 3 properties or local variables that share the same prefix, I usually try to extract them into their own method or type. That way I can avoid massive types & methods, and also increase readability, without falling into a "premature optimization" trap.
Before
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articleFolder = try contentFolder.subfolder(named: "posts")
let articleProcessor = ContentProcessor(folder: articleFolder)
let articles = try articleProcessor.process()
...
}
After
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articles = try processArticles(in: contentFolder)
...
}
private func processArticles(in folder: Folder) throws -> [ContentItem] {
let folder = try folder.subfolder(named: "posts")
let processor = ContentProcessor(folder: folder)
return try processor.process()
}
👨🔧 Here's two extensions that I always add to the Encodable
& Decodable
protocols, which for me really make the Codable API nicer to use. By using type inference for decoding, a lot of boilerplate can be removed when the compiler is already able to infer the resulting type.
extension Encodable {
func encoded() throws -> Data {
return try JSONEncoder().encode(self)
}
}
extension Data {
func decoded<T: Decodable>() throws -> T {
return try JSONDecoder().decode(T.self, from: self)
}
}
let data = try user.encoded()
// By using a generic type in the decoded() method, the
// compiler can often infer the type we want to decode
// from the current context.
try userDidLogin(data.decoded())
// And if not, we can always supply the type, still making
// the call site read very nicely.
let otherUser = try data.decoded() as User
📦 UserDefaults
is a lot more powerful than what it first might seem like. Not only can it store more complex values (like dates & dictionaries) and parse command line arguments - it also enables easy sharing of settings & lightweight data between apps in the same App Group.
let sharedDefaults = UserDefaults(suiteName: "my-app-group")!
let useDarkMode = sharedDefaults.bool(forKey: "dark-mode")
// This value is put into the shared suite.
sharedDefaults.set(true, forKey: "dark-mode")
// If you want to treat the shared settings as read-only (and add
// local overrides on top of them), you can simply add the shared
// suite to the standard UserDefaults.
let combinedDefaults = UserDefaults.standard
combinedDefaults.addSuite(named: "my-app-group")
// This value is a local override, not added to the shared suite.
combinedDefaults.set(true, forKey: "app-specific-override")
🎨 By overriding layerClass
you can tell UIKit what CALayer
class to use for a UIView
's backing layer. That way you can reduce the amount of layers, and don't have to do any manual layout.
final class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var colors: (start: UIColor, end: UIColor)? {
didSet { updateLayer() }
}
private func updateLayer() {
let layer = self.layer as! CAGradientLayer
layer.colors = colors.map { [$0.start.cgColor, $0.end.cgColor] }
}
}
✅ That the compiler now automatically synthesizes Equatable conformances is such a huge upgrade for Swift! And the cool thing is that it works for all kinds of types - even for enums with associated values! Especially useful when using enums for verification in unit tests.
struct Article: Equatable {
let title: String
let text: String
}
struct User: Equatable {
let name: String
let age: Int
}
extension Navigator {
enum Destination: Equatable {
case profile(User)
case article(Article)
}
}
func testNavigatingToArticle() {
let article = Article(title: "Title", text: "Text")
controller.select(article)
XCTAssertEqual(navigator.destinations, [.article(article)])
}
🤝 Associated types can have defaults in Swift - which is super useful for types that are not easily inferred (for example when they're not used for a specific instance method or property).
protocol Identifiable {
associatedtype RawIdentifier: Codable = String
var id: Identifier<Self> { get }
}
struct User: Identifiable {
let id: Identifier<User>
let name: String
}
struct Group: Identifiable {
typealias RawIdentifier = Int
let id: Identifier<Group>
let name: String
}
🆔 If you want to avoid using plain strings as identifiers (which can increase both type safety & readability), it's really easy to create a custom Identifier type that feels just like a native Swift type, thanks to protocols!
More on this topic in "Type-safe identifiers in Swift".
struct Identifier: Hashable {
let string: String
}
extension Identifier: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
string = value
}
}
extension Identifier: CustomStringConvertible {
var description: String {
return string
}
}
extension Identifier: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
string = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
}
}
struct Article: Codable {
let id: Identifier
let title: String
}
let article = Article(id: "my-article", title: "Hello world!")
🙌 A really cool thing about using tuples to model the internal state of a Swift type, is that you can unwrap an optional tuple's members directly into local variables.
Very useful in order to group multiple optional values together for easy unwrapping & handling.
class ImageTransformer {
private var queue = [(image: UIImage, transform: Transform)]()
private func processNext() {
// When unwrapping an optional tuple, you can assign the members
// directly to local variables.
guard let (image, transform) = queue.first else {
return
}
let context = Context()
context.draw(image)
context.apply(transform)
...
}
}
❤️ I love to structure my code using extensions in Swift. One big benefit of doing so when it comes to struct initializers, is that defining a convenience initializer doesn't remove the default one the compiler generates - best of both worlds!
struct Article {
let date: Date
var title: String
var text: String
var comments: [Comment]
}
extension Article {
init(title: String, text: String) {
self.init(date: Date(), title: title, text: text, comments: [])
}
}
let articleA = Article(title: "Best Cupcake Recipe", text: "...")
let articleB = Article(
date: Date(),
title: "Best Cupcake Recipe",
text: "...",
comments: [
Comment(user: currentUser, text: "Yep, can confirm!")
]
)
🏈 A big benefit of using throwing functions for synchronous Swift APIs is that the caller can decide whether they want to treat the return value as optional (try?
) or required (try
).
func loadFile(named name: String) throws -> File {
guard let url = urlForFile(named: name) else {
throw File.Error.missing
}
do {
let data = try Data(contentsOf: url)
return File(url: url, data: data)
} catch {
throw File.Error.invalidData(error)
}
}
let requiredFile = try loadFile(named: "AppConfig.json")
let optionalFile = try? loadFile(named: "UserSettings.json")
🐝 Types that are nested in generics automatically inherit their parent's generic types - which is super useful when defining accessory types (for things like states or outcomes).
struct Task<Input, Output> {
typealias Closure = (Input) throws -> Output
let closure: Closure
}
extension Task {
enum Result {
case success(Output)
case failure(Error)
}
}
🤖 Now that the Swift compiler automatically synthesizes Equatable & Hashable conformances for value types, it's easier than ever to setup model structures with nested types that are all Equatable
/Hashable
!
typealias Value = Hashable & Codable
struct User: Value {
var name: String
var age: Int
var lastLoginDate: Date?
var settings: Settings
}
extension User {
struct Settings: Value {
var itemsPerPage: Int
var theme: Theme
}
}
extension User.Settings {
enum Theme: String, Value {
case light
case dark
}
}
You can read more about using nested types in Swift here.
🎉 Swift 4.1 is here! One of the key features it brings is conditional conformances, which lets you have a type only conform to a protocol under certain constraints.
protocol UnboxTransformable {
associatedtype RawValue
static func transform(_ value: RawValue) throws -> Self?
}
extension Array: UnboxTransformable where Element: UnboxTransformable {
typealias RawValue = [Element.RawValue]
static func transform(_ value: RawValue) throws -> [Element]? {
return try value.compactMap(Element.transform)
}
}
I also have an article with lots of more info on conditional conformances here. Paul Hudson also has a great overview of all Swift 4.1 features here.
🕵️♀️ A cool thing about Swift type aliases is that they can be generic! Combine that with tuples and you can easily define simple generic types.
typealias Pair<T> = (T, T)
extension Game {
func calculateScore(for players: Pair<Player>) -> Int {
...
}
}
You can read more about using tuples as lightweight types here.
☑️ A really cool "hidden" feature of UserDefaults is that it contains any arguments that were passed to the app at launch!
Super useful both in Swift command line tools & scripts, but also to temporarily override a value when debugging iOS apps.
let defaults = UserDefaults.standard
let query = defaults.string(forKey: "query")
let resultCount = defaults.integer(forKey: "results")
👏 Swift's &
operator is awesome! Not only can you use it to compose protocols, you can compose other types too! Very useful if you want to hide concrete types & implementation details.
protocol LoadableFromURL {
func load(from url: URL)
}
class ContentViewController: UIViewController, LoadableFromURL {
func load(from url: URL) {
...
}
}
class ViewControllerFactory {
func makeContentViewController() -> UIViewController & LoadableFromURL {
return ContentViewController()
}
}
🤗 When capturing values in mocks, using an array (instead of just a single value) makes it easy to verify that only a certain number of values were passed.
Perfect for protecting against "over-calling" something.
class UserManagerTests: XCTestCase {
func testObserversCalledWhenUserFirstLogsIn() {
let manager = UserManager()
let observer = ObserverMock()
manager.addObserver(observer)
// First login, observers should be notified
let user = User(id: 123, name: "John")
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
// If the same user logs in again, observers shouldn't be notified
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
}
}
private extension UserManagerTests {
class ObserverMock: UserManagerObserver {
private(set) var users = [User]()
func userDidChange(to user: User) {
users.append(user)
}
}
}
👋 When writing tests, you don't always need to create mocks - you can create stubs using real instances of things like errors, URLs & UserDefaults.
Here's how to do that for some common tasks/object types in Swift:
// Create errors using NSError (#function can be used to reference the name of the test)
let error = NSError(domain: #function, code: 1, userInfo: nil)
// Create non-optional URLs using file paths
let url = URL(fileURLWithPath: "Some/URL")
// Reference the test bundle using Bundle(for:)
let bundle = Bundle(for: type(of: self))
// Create an explicit UserDefaults object (instead of having to use a mock)
let userDefaults = UserDefaults(suiteName: #function)
// Create queues to control/await concurrent operations
let queue = DispatchQueue(label: #function)
For when you actually do need mocking, check out "Mocking in Swift".
⏱ I've started using "then" as an external parameter label for completion handlers. Makes the call site read really nicely (Because I do ❤️ conversational API design) regardless of whether trailing closure syntax is used or not.
protocol DataLoader {
// Adding type aliases to protocols can be a great way to
// reduce verbosity for parameter types.
typealias Handler = (Result<Data>) -> Void
associatedtype Endpoint
func loadData(from endpoint: Endpoint, then handler: @escaping Handler)
}
loader.loadData(from: .messages) { result in
...
}
loader.loadData(from: .messages, then: { result in
...
})
😴 Combining lazily evaluated sequences with builder pattern-like properties can lead to some pretty sweet APIs for configurable sequences in Swift.
Also useful for queries & other things you "build up" and then execute.
// Extension adding builder pattern-like properties that return
// a new sequence value with the given configuration applied
extension FileSequence {
var recursive: FileSequence {
var sequence = self
sequence.isRecursive = true
return sequence
}
var includingHidden: FileSequence {
var sequence = self
sequence.includeHidden = true
return sequence
}
}
// BEFORE
let files = folder.makeFileSequence(recursive: true, includeHidden: true)
// AFTER
let files = folder.files.recursive.includingHidden
Want an intro to lazy sequences? Check out "Swift sequences: The art of being lazy".
My top 3 tips for faster & more stable UI tests:
📱 Reset the app's state at the beginning of every test.
🆔 Use accessibility identifiers instead of UI strings.
⏱ Use expectations instead of waiting time.
func testOpeningArticle() {
// Launch the app with an argument that tells it to reset its state
let app = XCUIApplication()
app.launchArguments.append("--uitesting")
app.launch()
// Check that the app is displaying an activity indicator
let activityIndicator = app.activityIndicator.element
XCTAssertTrue(activityIndicator.exists)
// Wait for the loading indicator to disappear = content is ready
expectation(for: NSPredicate(format: "exists == 0"),
evaluatedWith: activityIndicator)
// Use a generous timeout in case the network is slow
waitForExpectations(timeout: 10)
// Tap the cell for the first article
app.tables.cells["Article.0"].tap()
// Assert that a label with the accessibility identifier "Article.Title" exists
let label = app.staticTexts["Article.Title"]
XCTAssertTrue(label.exists)
}
📋 It's super easy to access the contents of the clipboard from a Swift script. A big benefit of Swift scripting is being able to use Cocoa's powerful APIs for Mac apps.
import Cocoa
let clipboard = NSPasteboard.general.string(forType: .string)
🎯 Using Swift tuples for view state can be a super nice way to group multiple properties together and render them reactively using the layout system.
By using a tuple we don't have to either introduce a new type or make our view model-aware.
class TextView: UIView {
var state: (title: String?, text: String?) {
// By telling UIKit that our view needs layout and binding our
// state in layoutSubviews, we can react to state changes without
// doing unnecessary layout work.
didSet { setNeedsLayout() }
}
private let titleLabel = UILabel()
private let textLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.text = state.title
textLabel.text = state.text
...
}
}
⚾️ Swift tests can throw, which is super useful in order to avoid complicated logic or force unwrapping. By making errors conform to LocalizedError
, you can also get a nice error message in Xcode if there's a failure.
class ImageCacheTests: XCTestCase {
func testCachingAndLoadingImage() throws {
let bundle = Bundle(for: type(of: self))
let cache = ImageCache(bundle: bundle)
// Bonus tip: You can easily load images from your test
// bundle using this UIImage initializer
let image = try require(UIImage(named: "sample", in: bundle, compatibleWith: nil))
try cache.cache(image, forKey: "key")
let cachedImage = try cache.image(forKey: "key")
XCTAssertEqual(image, cachedImage)
}
}
enum ImageCacheError {
case emptyKey
case dataConversionFailed
}
// When using throwing tests, making your errors conform to
// LocalizedError will render a much nicer error message in
// Xcode (per default only the error code is shown).
extension ImageCacheError: LocalizedError {
var errorDescription: String? {
switch self {
case .emptyKey:
return "An empty key was given"
case .dataConversionFailed:
return "Failed to convert the given image to Data"
}
}
}
For more information, and the implementation of the require
method used above, check out "Avoiding force unwrapping in Swift unit tests".
✍️ Unlike static
properties, class
properties can be overridden by subclasses (however, they can't be stored, only computed).
class TableViewCell: UITableViewCell {
class var preferredHeight: CGFloat { return 60 }
}
class TallTableViewCell: TableViewCell {
override class var preferredHeight: CGFloat { return 100 }
}
👨🎨 Creating extensions with static factory methods can be a great alternative to subclassing in Swift, especially for things like setting up UIViews, CALayers or other kinds of styling.
It also lets you remove a lot of styling & setup from your view controllers.
extension UILabel {
static func makeForTitle() -> UILabel {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 24)
label.textColor = .darkGray
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.75
return label
}
static func makeForText() -> UILabel {
let label = UILabel()
label.font = .systemFont(ofSize: 16)
label.textColor = .black
label.numberOfLines = 0
return label
}
}
class ArticleViewController: UIViewController {
lazy var titleLabel = UILabel.makeForTitle()
lazy var textLabel = UILabel.makeForText()
}
🧒 An awesome thing about child view controllers is that they're automatically resized to match their parent, making them a super nice solution for things like loading & error views.
class ListViewController: UIViewController {
func loadItems() {
let loadingViewController = LoadingViewController()
add(loadingViewController)
dataLoader.loadItems { [weak self] result in
loadingViewController.remove()
self?.handle(result)
}
}
}
For more about child view controller (including the add
and remove
methods used above), check out "Using child view controllers as plugins in Swift".
🤐 Using the zip function in Swift you can easily combine two sequences. Super useful when using two sequences to do some work, since zip takes care of all the bounds-checking.
func render(titles: [String]) {
for (label, text) in zip(titleLabels, titles) {
print(text)
label.text = text
}
}
🎛 The awesome thing about option sets in Swift is that they can automatically either be passed as a single member or as a set. Even cooler is that you can easily define your own option sets as well, perfect for options and other non-exclusive values.
// Option sets are awesome, because you can easily pass them
// both using dot syntax and array literal syntax, like when
// using the UIView animation API:
UIView.animate(withDuration: 0.3,
delay: 0,
options: .allowUserInteraction,
animations: animations)
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.allowUserInteraction, .layoutSubviews],
animations: animations)
// The cool thing is that you can easily define your own option
// sets as well, by defining a struct that has an Int rawValue,
// that will be used as a bit mask.
extension Cache {
struct Options: OptionSet {
static let saveToDisk = Options(rawValue: 1)
static let clearOnMemoryWarning = Options(rawValue: 1 << 1)
static let clearDaily = Options(rawValue: 1 << 2)
let rawValue: Int
}
}
// We can now use Cache.Options just like UIViewAnimationOptions:
Cache(options: .saveToDisk)
Cache(options: [.saveToDisk, .clearDaily])
🙌 Using the where
clause when designing protocol-oriented APIs in Swift can let your implementations (or others' if it's open source) have a lot more freedom, especially when it comes to collections.
See "Using generic type constraints in Swift 4" for more info.
public protocol PathFinderMap {
associatedtype Node
// Using the 'where' clause for associated types, we can
// ensure that a type meets certain requirements (in this
// case that it's a sequence with Node elements).
associatedtype NodeSequence: Sequence where NodeSequence.Element == Node
// Instead of using a concrete type (like [Node]) here, we
// give implementors of this protocol more freedom while
// still meeting our requirements. For example, one
// implementation might use Set<Node>.
func neighbors(of node: Node) -> NodeSequence
}
👨🍳 Combine first class functions in Swift with the fact that Dictionary elements are (Key, Value) tuples and you can build yourself some pretty awesome functional chains when iterating over a Dictionary.
func makeActor(at coordinate: Coordinate, for building: Building) -> Actor {
let actor = Actor()
actor.position = coordinate.point
actor.animation = building.animation
return actor
}
func render(_ buildings: [Coordinate : Building]) {
buildings.map(makeActor).forEach(add)
}
😎 In Swift, you can call any instance method as a static function and it will return a closure representing that method. This is how running tests using SPM on Linux works.
More about this topic in my blog post "First class functions in Swift".
// This produces a '() -> Void' closure which is a reference to the
// given view's 'removeFromSuperview' method.
let closure = UIView.removeFromSuperview(view)
// We can now call it just like we would any other closure, and it
// will run 'view.removeFromSuperview()'
closure()
// This is how running tests using the Swift Package Manager on Linux
// works, you return your test functions as closures:
extension UserManagerTests {
static var allTests = [
("testLoggingIn", testLoggingIn),
("testLoggingOut", testLoggingOut),
("testUserPermissions", testUserPermissions)
]
}
👏 One really nice benefit of dropping suffixes from method names (and just using verbs, when possible) is that it becomes super easy to support both single and multiple arguments, and it works really well semantically.
extension UIView {
func add(_ subviews: UIView...) {
subviews.forEach(addSubview)
}
}
view.add(button)
view.add(label)
// By dropping the "Subview" suffix from the method name, both
// single and multiple arguments work really well semantically.
view.add(button, label)
👽 Using the AnyObject
(or class
) constraint on protocols is not only useful when defining delegates (or other weak references), but also when you always want instances to be mutable without copying.
// By constraining a protocol with 'AnyObject' it can only be adopted
// by classes, which means all instances will always be mutable, and
// that it's the original instance (not a copy) that will be mutated.
protocol DataContainer: AnyObject {
var data: Data? { get set }
}
class UserSettingsManager {
private var settings: Settings
private let dataContainer: DataContainer
// Since DataContainer is a protocol, we an easily mock it in
// tests if we use dependency injection
init(settings: Settings, dataContainer: DataContainer) {
self.settings = settings
self.dataContainer = dataContainer
}
func saveSettings() throws {
let data = try settings.serialize()
// We can now assign properties on an instance of our protocol
// because the compiler knows it's always going to be a class
dataContainer.data = data
}
}
🍣 Even if you define a custom raw value for a string-based enum in Swift, the full case name will be used in string interpolation.
Super useful when using separate raw values for JSON, while still wanting to use the full case name in other contexts.
extension Building {
// This enum has custom raw values that are used when decoding
// a value, for example from JSON.
enum Kind: String {
case castle = "C"
case town = "T"
case barracks = "B"
case goldMine = "G"
case camp = "CA"
case blacksmith = "BL"
}
var animation: Animation {
return Animation(
// When used in string interpolation, the full case name is still used.
// For 'castle' this will be 'buildings/castle'.
name: "buildings/\(kind)",
frameCount: frameCount,
frameDuration: frameDuration
)
}
}
👨🔬 Continuing to experiment with expressive ways of comparing a value with a list of candidates in Swift. Adding an extension on Equatable is probably my favorite approach so far.
extension Equatable {
func isAny(of candidates: Self...) -> Bool {
return candidates.contains(self)
}
}
let isHorizontal = direction.isAny(of: .left, .right)
See tip #35 for my previous experiment.
📐 A really interesting side-effect of a UIView
's bounds
being its rect within its own coordinate system is that transforms don't affect it at all. That's why it's usually a better fit than frame
when doing layout calculations of subviews.
let view = UIView()
view.frame.size = CGSize(width: 100, height: 100)
view.transform = CGAffineTransform(scaleX: 2, y: 2)
print(view.frame) // (-50.0, -50.0, 200.0, 200.0)
print(view.bounds) // (0.0, 0.0, 100.0, 100.0)
👏 It's awesome that many UIKit APIs with completion handlers and other optional parameters import into Swift with default arguments (even though they are written in Objective-C). Getting rid of all those nil arguments is so nice!
// BEFORE: All parameters are specified, just like in Objective-C
viewController.present(modalViewController, animated: true, completion: nil)
modalViewController.dismiss(animated: true, completion: nil)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
options: [],
animations: animations,
completion: nil)
// AFTER: Since many UIKit APIs with completion handlers and other
// optional parameters import into Swift with default arguments,
// we can make our calls shorter
viewController.present(modalViewController, animated: true)
modalViewController.dismiss(animated: true)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
animations: animations)
✂️ Avoiding Massive View Controllers is all about finding the right levels of abstraction and splitting things up.
My personal rule of thumb is that as soon as I have 3 methods or properties that have the same prefix, I break them out into their own type.
// BEFORE
class LoginViewController: UIViewController {
private lazy var signUpLabel = UILabel()
private lazy var signUpImageView = UIImageView()
private lazy var signUpButton = UIButton()
}
// AFTER
class LoginViewController: UIViewController {
private lazy var signUpView = SignUpView()
}
class SignUpView: UIView {
private lazy var label = UILabel()
private lazy var imageView = UIImageView()
private lazy var button = UIButton()
}
❤️ I love the fact that optionals are enums in Swift - it makes it so easy to extend them with convenience APIs for certain types. Especially useful when doing things like data validation on optional values.
func validateTextFields() -> Bool {
guard !usernameTextField.text.isNilOrEmpty else {
return false
}
...
return true
}
// Since all optionals are actual enum values in Swift, we can easily
// extend them for certain types, to add our own convenience APIs
extension Optional where Wrapped == String {
var isNilOrEmpty: Bool {
switch self {
case let string?:
return string.isEmpty
case nil:
return true
}
}
}
// Since strings are now Collections in Swift 4, you can even
// add this property to all optional collections:
extension Optional where Wrapped: Collection {
var isNilOrEmpty: Bool {
switch self {
case let collection?:
return collection.isEmpty
case nil:
return true
}
}
}
🗺 Using the where
keyword can be a super nice way to quickly apply a filter in a for
-loop in Swift. You can of course use map
, filter
and forEach
, or guard
, but for simple loops I think this is very expressive and nice.
func archiveMarkedPosts() {
for post in posts where post.isMarked {
archive(post)
}
}
func healAllies() {
for player in players where player.isAllied(to: currentPlayer) {
player.heal()
}
}
👻 Variable shadowing can be super useful in Swift, especially when you want to create a local copy of a parameter value in order to use it as state within a closure.
init(repeatMode: RepeatMode, closure: @escaping () -> UpdateOutcome) {
// Shadow the argument with a local, mutable copy
var repeatMode = repeatMode
self.closure = {
// With shadowing, there's no risk of accidentially
// referring to the immutable version
switch repeatMode {
case .forever:
break
case .times(let count):
guard count > 0 else {
return .finished
}
// We can now capture the mutable version and use
// it for state in a closure
repeatMode = .times(count - 1)
}
return closure()
}
}
✒️ Dot syntax is one of my favorite features of Swift. What's really cool is that it's not only for enums, any static method or property can be used with dot syntax - even initializers! Perfect for convenience APIs and default parameters.
public enum RepeatMode {
case times(Int)
case forever
}
public extension RepeatMode {
static var never: RepeatMode {
return .times(0)
}
static var once: RepeatMode {
return .times(1)
}
}
view.perform(animation, repeated: .once)
// To make default parameters more compact, you can even use init with dot syntax
class ImageLoader {
init(cache: Cache = .init(), decoder: ImageDecoder = .init()) {
...
}
}
🚀 One really cool aspect of Swift having first class functions is that you can pass any function (or even initializer) as a closure, and even call it with a tuple containing its parameters!
// This function lets us treat any "normal" function or method as
// a closure and run it with a tuple that contains its parameters
func call<Input, Output>(_ function: (Input) -> Output, with input: Input) -> Output {
return function(input)
}
class ViewFactory {
func makeHeaderView() -> HeaderView {
// We can now pass an initializer as a closure, and a tuple
// containing its parameters
return call(HeaderView.init, with: loadTextStyles())
}
private func loadTextStyles() -> (font: UIFont, color: UIColor) {
return (theme.font, theme.textColor)
}
}
class HeaderView {
init(font: UIFont, textColor: UIColor) {
...
}
}
💉 If you've been struggling to test code that uses static APIs, here's a technique you can use to enable static dependency injection without having to modify any call sites:
// Before: Almost impossible to test due to the use of singletons
class Analytics {
static func log(_ event: Event) {
Database.shared.save(event)
let dictionary = event.serialize()
NetworkManager.shared.post(dictionary, to: eventURL)
}
}
// After: Much easier to test, since we can inject mocks as arguments
class Analytics {
static func log(_ event: Event,
database: Database = .shared,
networkManager: NetworkManager = .shared) {
database.save(event)
let dictionary = event.serialize()
networkManager.post(dictionary, to: eventURL)
}
}
🎉 In Swift 4, type inference works for lazy properties and you don't need to explicitly refer to self
!
// Swift 3
class PurchaseView: UIView {
private lazy var buyButton: UIButton = self.makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
// Swift 4
class PurchaseView: UIView {
private lazy var buyButton = makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
😎 You can turn any Swift Error
into an NSError
, which is super useful when pattern matching with a code 👍. Also, switching on optionals is pretty cool!
let task = urlSession.dataTask(with: url) { data, _, error in
switch error {
case .some(let error as NSError) where error.code == NSURLErrorNotConnectedToInternet:
presenter.showOfflineView()
case .some(let error):
presenter.showGenericErrorView()
case .none:
presenter.renderContent(from: data)
}
}
task.resume()
Also make sure to check out Kostas Kremizas' tip about how you can pattern match directly against a member of URLError
.
🖥 Here's an easy way to make iOS model code that uses UIImage
macOS compatible - like me and Gui Rambo discussed on the Swift by Sundell Podcast.
// Either put this in a separate file that you only include in your macOS target or wrap the code in #if os(macOS) / #endif
import Cocoa
// Step 1: Typealias UIImage to NSImage
typealias UIImage = NSImage
// Step 2: You might want to add these APIs that UIImage has but NSImage doesn't.
extension NSImage {
var cgImage: CGImage? {
var proposedRect = CGRect(origin: .zero, size: size)
return cgImage(forProposedRect: &proposedRect,
context: nil,
hints: nil)
}
convenience init?(named name: String) {
self.init(named: Name(name))
}
}
// Step 3: Profit - you can now make your model code that uses UIImage cross-platform!
struct User {
let name: String
let profileImage: UIImage
}
🤖 You can easily define a protocol-oriented API that can only be mutated internally, by using an internal protocol that extends a public one.
// Declare a public protocol that acts as your immutable API
public protocol ModelHolder {
associatedtype Model
var model: Model { get }
}
// Declare an extended, internal protocol that provides a mutable API
internal protocol MutableModelHolder: ModelHolder {
var model: Model { get set }
}
// You can now implement the requirements using 'public internal(set)'
public class UserHolder: MutableModelHolder {
public internal(set) var model: User
internal init(model: User) {
self.model = model
}
}
🎛 You can switch on a set using array literals as cases in Swift! Can be really useful to avoid many if
/else if
statements.
class RoadTile: Tile {
var connectedDirections = Set<Direction>()
func render() {
switch connectedDirections {
case [.up, .down]:
image = UIImage(named: "road-vertical")
case [.left, .right]:
image = UIImage(named: "road-horizontal")
default:
image = UIImage(named: "road")
}
}
}
🌍 When caching localized content in an app, it's a good idea to add the current locale to all keys, to prevent bugs when switching languages.
func cache(_ content: Content, forKey key: String) throws {
let data = try wrap(content) as Data
let key = localize(key: key)
try storage.store(data, forKey: key)
}
func loadCachedContent(forKey key: String) -> Content? {
let key = localize(key: key)
let data = storage.loadData(forKey: key)
return data.flatMap { try? unbox(data: $0) }
}
private func localize(key: String) -> String {
return key + "-" + Bundle.main.preferredLocalizations[0]
}
🚳 Here's an easy way to setup a test to avoid accidental retain cycles with object relationships (like weak delegates & observers) in Swift:
func testDelegateNotRetained() {
// Assign the delegate (weak) and also retain it using a local var
var delegate: Delegate? = DelegateMock()
controller.delegate = delegate
XCTAssertNotNil(controller.delegate)
// Release the local var, which should also release the weak reference
delegate = nil
XCTAssertNil(controller.delegate)
}
👨🔬 Playing around with an expressive way to check if a value matches any of a list of candidates in Swift:
// Instead of multiple conditions like this:
if string == "One" || string == "Two" || string == "Three" {
}
// You can now do:
if string == any(of: "One", "Two", "Three") {
}
You can find a gist with the implementation here.
👪 APIs in a Swift extension automatically inherit its access control level, making it a neat way to organize public, internal & private APIs.
public extension Animation {
init(textureNamed textureName: String) {
frames = [Texture(name: textureName)]
}
init(texturesNamed textureNames: [String], frameDuration: TimeInterval = 1) {
frames = textureNames.map(Texture.init)
self.frameDuration = frameDuration
}
init(image: Image) {
frames = [Texture(image: image)]
}
}
internal extension Animation {
func loadFrameImages() -> [Image] {
return frames.map { $0.loadImageIfNeeded() }
}
}
🗺 Using map
you can transform an optional value into an optional Result
type by simply passing in the enum case.
enum Result<Value> {
case value(Value)
case error(Error)
}
class Promise<Value> {
private var result: Result<Value>?
init(value: Value? = nil) {
result = value.map(Result.value)
}
}
👌 It's so nice that you can assign directly to self
in struct
initializers in Swift. Very useful when adding conformance to protocols.
extension Bool: AnswerConvertible {
public init(input: String) throws {
switch input.lowercased() {
case "y", "yes", "👍":
self = true
default:
self = false
}
}
}
☎️ Defining Swift closures as inline functions enables you to recursively call them, which is super useful in things like custom sequences.
class Database {
func records(matching query: Query) -> AnySequence<Record> {
var recordIterator = loadRecords().makeIterator()
func iterate() -> Record? {
guard let nextRecord = recordIterator.next() else {
return nil
}
guard nextRecord.matches(query) else {
// Since the closure is an inline function, it can be recursively called,
// in this case in order to advance to the next item.
return iterate()
}
return nextRecord
}
// AnySequence/AnyIterator are part of the standard library and provide an easy way
// to define custom sequences using closures.
return AnySequence { AnyIterator(iterate) }
}
}
Rob Napier points out that using the above might cause crashes if used on a large databaset, since Swift has no guaranteed Tail Call Optimization (TCO).
Slava Pestov also points out that another benefit of inline functions vs closures is that they can have their own generic parameter list.
🏖 Using lazy properties in Swift, you can pass self
to required Objective-C dependencies without having to use force-unwrapped optionals.
class DataLoader: NSObject {
lazy var urlSession: URLSession = self.makeURLSession()
private func makeURLSession() -> URLSession {
return URLSession(configuration: .default, delegate: self, delegateQueue: .main)
}
}
class Renderer {
lazy var displayLink: CADisplayLink = self.makeDisplayLink()
private func makeDisplayLink() -> CADisplayLink {
return CADisplayLink(target: self, selector: #selector(screenDidRefresh))
}
}
👓 If you have a property in Swift that needs to be weak
or lazy
, you can still make it readonly by using private(set)
.
class Node {
private(set) weak var parent: Node?
private(set) lazy var children = [Node]()
func add(child: Node) {
children.append(child)
child.parent = self
}
}
🌏 Tired of using URL(string: "url")!
for static URLs? Make URL
conform to ExpressibleByStringLiteral
and you can now simply use "url"
instead.
extension URL: ExpressibleByStringLiteral {
// By using 'StaticString' we disable string interpolation, for safety
public init(stringLiteral value: StaticString) {
self = URL(string: "\(value)").require(hint: "Invalid URL string literal: \(value)")
}
}
// We can now define URLs using static string literals 🎉
let url: URL = "https://www.swiftbysundell.com"
let task = URLSession.shared.dataTask(with: "https://www.swiftbysundell.com")
// In Swift 3 or earlier, you also have to implement 2 additional initializers
extension URL {
public init(extendedGraphemeClusterLiteral value: StaticString) {
self.init(stringLiteral: value)
}
public init(unicodeScalarLiteral value: StaticString) {
self.init(stringLiteral: value)
}
}
To find the extension that adds the require()
method on Optional
that I use above, check out Require.
✚ I'm always careful with operator overloading, but for manipulating things like sizes, points & frames I find them super useful.
extension CGSize {
static func *(lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}
}
button.frame.size = image.size * 2
If you like the above idea, check out CGOperators, which contains math operator overloads for all Core Graphics' vector types.
🔗 You can use closure types in generic constraints in Swift. Enables nice APIs for handling sequences of closures.
extension Sequence where Element == () -> Void {
func callAll() {
forEach { $0() }
}
}
extension Sequence where Element == () -> String {
func joinedResults(separator: String) -> String {
return map { $0() }.joined(separator: separator)
}
}
callbacks.callAll()
let names = nameProviders.joinedResults(separator: ", ")
(If you're using Swift 3, you have to change Element
to Iterator.Element
)
🎉 Using associated enum values is a super nice way to encapsulate mutually exclusive state info (and avoiding state-specific optionals).
// BEFORE: Lots of state-specific, optional properties
class Player {
var isWaitingForMatchMaking: Bool
var invitingUser: User?
var numberOfLives: Int
var playerDefeatedBy: Player?
var roundDefeatedIn: Int?
}
// AFTER: All state-specific information is encapsulated in enum cases
class Player {
enum State {
case waitingForMatchMaking
case waitingForInviteResponse(from: User)
case active(numberOfLives: Int)
case defeated(by: Player, roundNumber: Int)
}
var state: State
}
👍 I really like using enums for all async result types, even boolean ones. Self-documenting, and makes the call site a lot nicer to read too!
protocol PushNotificationService {
// Before
func enablePushNotifications(completionHandler: @escaping (Bool) -> Void)
// After
func enablePushNotifications(completionHandler: @escaping (PushNotificationStatus) -> Void)
}
enum PushNotificationStatus {
case enabled
case disabled
}
service.enablePushNotifications { status in
if status == .enabled {
enableNotificationsButton.removeFromSuperview()
}
}
🏃 Want to work on your async code in a Swift Playground? Just set needsIndefiniteExecution
to true to keep it running:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let greeting = "Hello after 3 seconds"
print(greeting)
}
To stop the playground from executing, simply call PlaygroundPage.current.finishExecution()
.
💦 Avoid memory leaks when accidentially refering to self
in closures by overriding it locally with a weak reference:
Swift >= 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let self = self else {
return
}
self.cache(result)
...
Swift < 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let `self` = self else {
return
}
self.cache(result)
...
Note that the reason the above currently works is because of a compiler bug (which I hope gets turned into a properly supported feature soon).
🕓 Using dispatch work items you can easily cancel a delayed asynchronous GCD task if you no longer need it:
let workItem = DispatchWorkItem {
// Your async code goes in here
}
// Execute the work item after 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem)
// You can cancel the work item if you no longer need it
workItem.cancel()
➕ While working on a new Swift developer tool (to be open sourced soon 😉), I came up with a pretty neat way of organizing its sequence of operations, by combining their functions into a closure:
internal func +<A, B, C>(lhs: @escaping (A) throws -> B,
rhs: @escaping (B) throws -> C) -> (A) throws -> C {
return { try rhs(lhs($0)) }
}
public func run() throws {
try (determineTarget + build + analyze + output)()
}
If you're familiar with the functional programming world, you might know the above technique as the pipe operator (thanks to Alexey Demedreckiy for pointing this out!)
🗺 Using map()
and flatMap()
on optionals you can chain multiple operations without having to use lengthy if lets
or guards
:
// BEFORE
guard let string = argument(at: 1) else {
return
}
guard let url = URL(string: string) else {
return
}
handle(url)
// AFTER
argument(at: 1).flatMap(URL.init).map(handle)
🚀 Using self-executing closures is a great way to encapsulate lazy property initialization:
class StoreViewController: UIViewController {
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let view = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
view.delegate = self
view.dataSource = self
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
}
}
⚡️ You can speed up your Swift package tests using the --parallel
flag. For Marathon, the tests execute 3 times faster that way!
swift test --parallel
🛠 Struggling with mocking UserDefaults
in a test? The good news is: you don't need mocking - just create a real instance:
class LoginTests: XCTestCase {
private var userDefaults: UserDefaults!
private var manager: LoginManager!
override func setUp() {
super.setup()
userDefaults = UserDefaults(suiteName: #file)
userDefaults.removePersistentDomain(forName: #file)
manager = LoginManager(userDefaults: userDefaults)
}
}
👍 Using variadic parameters in Swift, you can create some really nice APIs that take a list of objects without having to use an array:
extension Canvas {
func add(_ shapes: Shape...) {
shapes.forEach(add)
}
}
let circle = Circle(center: CGPoint(x: 5, y: 5), radius: 5)
let lineA = Line(start: .zero, end: CGPoint(x: 10, y: 10))
let lineB = Line(start: CGPoint(x: 0, y: 10), end: CGPoint(x: 10, y: 0))
let canvas = Canvas()
canvas.add(circle, lineA, lineB)
canvas.render()
😮 Just like you can refer to a Swift function as a closure, you can do the same thing with enum cases with associated values:
enum UnboxPath {
case key(String)
case keyPath(String)
}
struct UserSchema {
static let name = key("name")
static let age = key("age")
static let posts = key("posts")
private static let key = UnboxPath.key
}
📈 The ===
operator lets you check if two objects are the same instance. Very useful when verifying that an array contains an instance in a test:
protocol InstanceEquatable: class, Equatable {}
extension InstanceEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
extension Enemy: InstanceEquatable {}
func testDestroyingEnemy() {
player.attack(enemy)
XCTAssertTrue(player.destroyedEnemies.contains(enemy))
}
😎 Cool thing about Swift initializers: you can call them using dot syntax and pass them as closures! Perfect for mocking dates in tests.
class Logger {
private let storage: LogStorage
private let dateProvider: () -> Date
init(storage: LogStorage = .init(), dateProvider: @escaping () -> Date = Date.init) {
self.storage = storage
self.dateProvider = dateProvider
}
func log(event: Event) {
storage.store(event: event, date: dateProvider())
}
}
📱 Most of my UI testing logic is now categories on XCUIApplication
. Makes the test cases really easy to read:
func testLoggingInAndOut() {
XCTAssertFalse(app.userIsLoggedIn)
app.launch()
app.login()
XCTAssertTrue(app.userIsLoggedIn)
app.logout()
XCTAssertFalse(app.userIsLoggedIn)
}
func testDisplayingCategories() {
XCTAssertFalse(app.isDisplayingCategories)
app.launch()
app.login()
app.goToCategories()
XCTAssertTrue(app.isDisplayingCategories)
}
🙂 It’s a good idea to avoid “default” cases when switching on Swift enums - it’ll “force you” to update your logic when a new case is added:
enum State {
case loggedIn
case loggedOut
case onboarding
}
func handle(_ state: State) {
switch state {
case .loggedIn:
showMainUI()
case .loggedOut:
showLoginUI()
// Compiler error: Switch must be exhaustive
}
}
💂 It's really cool that you can use Swift's 'guard' statement to exit out of pretty much any scope, not only return from functions:
// You can use the 'guard' statement to...
for string in strings {
// ...continue an iteration
guard shouldProcess(string) else {
continue
}
// ...or break it
guard !shouldBreak(for: string) else {
break
}
// ...or return
guard !shouldReturn(for: string) else {
return
}
// ..or throw an error
guard string.isValid else {
throw StringError.invalid(string)
}
// ...or exit the program
guard !shouldExit(for: string) else {
exit(1)
}
}
❤️ Love how you can pass functions & operators as closures in Swift. For example, it makes the syntax for sorting arrays really nice!
let array = [3, 9, 1, 4, 6, 2]
let sorted = array.sorted(by: <)
🗝 Here's a neat little trick I use to get UserDefault key consistency in Swift (#function expands to the property name in getters/setters). Just remember to write a good suite of tests that'll guard you against bugs when changing property names.
extension UserDefaults {
var onboardingCompleted: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
}
📛 Want to use a name already taken by the standard library for a nested type? No problem - just use Swift.
to disambiguate:
extension Command {
enum Error: Swift.Error {
case missing
case invalid(String)
}
}
📦 Playing around with using Wrap to implement Equatable
for any type, primarily for testing:
protocol AutoEquatable: Equatable {}
extension AutoEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
let lhsData = try! wrap(lhs) as Data
let rhsData = try! wrap(rhs) as Data
return lhsData == rhsData
}
}
📏 One thing that I find really useful in Swift is to use typealiases to reduce the length of method signatures in generic types:
public class PathFinder<Object: PathFinderObject> {
public typealias Map = Object.Map
public typealias Node = Map.Node
public typealias Path = PathFinderPath<Object>
public static func possiblePaths(for object: Object, at rootNode: Node, on map: Map) -> Path.Sequence {
return .init(object: object, rootNode: rootNode, map: map)
}
}
📖 You can reference either the external or internal parameter label when writing Swift docs - and they get parsed the same:
// EITHER:
class Foo {
/**
* - parameter string: A string
*/
func bar(with string: String) {}
}
// OR:
class Foo {
/**
* - parameter with: A string
*/
func bar(with string: String) {}
}
👍 Finding more and more uses for auto closures in Swift. Can enable some pretty nice APIs:
extension Dictionary {
mutating func value(for key: Key, orAdd valueClosure: @autoclosure () -> Value) -> Value {
if let value = self[key] {
return value
}
let value = valueClosure()
self[key] = value
return value
}
}
🚀 I’ve started to become a really big fan of nested types in Swift. Love the additional namespacing it gives you!
public struct Map {
public struct Model {
public let size: Size
public let theme: Theme
public var terrain: [Position : Terrain.Model]
public var units: [Position : Unit.Model]
public var buildings: [Position : Building.Model]
}
public enum Direction {
case up
case right
case down
case left
}
public struct Position {
public var x: Int
public var y: Int
}
public enum Size: String {
case small = "S"
case medium = "M"
case large = "L"
case extraLarge = "XL"
}
}
Author: JohnSundell
Source code: https://github.com/JohnSundell/SwiftTips
License: MIT license
#swift
1666245660
One of the things I really love about Swift is how I keep finding interesting ways to use it in various situations, and when I do - I usually share them on Twitter. Here's a collection of all the tips & tricks that I've shared so far. Each entry has a link to the original tweet, if you want to respond with some feedback or question, which is always super welcome! 🚀
⚠️ This list is no longer being updated. For my latest Swift tips, checkout the "Tips" section on Swift by Sundell.
Also make sure to check out all of my other Swift content:
🚀 Here are some quick tips to make async tests faster & more stable:
// BEFORE:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
detector.detectMentions(in: string) { mentions in
XCTAssertEqual(mentions, ["johnsundell"])
}
sleep(2)
}
}
// AFTER:
class MentionDetectorTests: XCTestCase {
func testDetectingMention() {
let detector = MentionDetector()
let string = "This test was written by @johnsundell."
var mentions: [String]?
let expectation = self.expectation(description: #function)
detector.detectMentions(in: string) {
mentions = $0
expectation.fulfill()
}
waitForExpectations(timeout: 10)
XCTAssertEqual(mentions, ["johnsundell"])
}
}
For more on async testing, check out "Unit testing asynchronous Swift code".
✍️ Adding support for the new Apple Pencil double-tap feature is super easy! All you have to do is to create a UIPencilInteraction
, add it to a view, and implement one delegate method. Hopefully all pencil-compatible apps will soon adopt this.
let interaction = UIPencilInteraction()
interaction.delegate = self
view.addInteraction(interaction)
extension ViewController: UIPencilInteractionDelegate {
func pencilInteractionDidTap(_ interaction: UIPencilInteraction) {
// Handle pencil double-tap
}
}
For more on using this and other iPad Pro features, check out "Building iPad Pro features in Swift".
😎 Here's a cool function that combines a value with a function to return a closure that captures that value, so that it can be called without any arguments. Super useful when working with closure-based APIs and we want to use some of our properties without having to capture self
.
func combine<A, B>(_ value: A, with closure: @escaping (A) -> B) -> () -> B {
return { closure(value) }
}
// BEFORE:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = { [weak self] in
guard let self = self else {
return
}
self.productManager.startCheckout(for: self.product)
}
}
}
// AFTER:
class ProductViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
buyButton.handler = combine(product, with: productManager.startCheckout)
}
}
💉 When I'm only using a single function from a dependency, I love to inject that function as a closure, instead of having to create a protocol and inject the whole object. Makes dependency injection & testing super simple.
final class ArticleLoader {
typealias Networking = (Endpoint) -> Future<Data>
private let networking: Networking
init(networking: @escaping Networking = URLSession.shared.load) {
self.networking = networking
}
func loadLatest() -> Future<[Article]> {
return networking(.latestArticles).decode()
}
}
For more on this technique, check out "Simple Swift dependency injection with functions".
💥 It's cool that you can easily assign a closure as a custom NSException
handler. This is super useful when building things in Playgrounds - since you can't use breakpoints - so instead of just signal SIGABRT
, you'll get the full exception description if something goes wrong.
NSSetUncaughtExceptionHandler { exception in
print(exception)
}
❤️ I love that in Swift, we can use the type system to make our code so much more self-documenting - one way of doing so is to use type aliases to give the primitive types that we use a more semantic meaning.
extension List.Item {
// Using type aliases, we can give semantic meaning to the
// primitive types that we use, without having to introduce
// wrapper types.
typealias Index = Int
}
extension List {
enum Mutation {
// Our enum cases now become a lot more self-documenting,
// without having to add additional parameter labels to
// explain them.
case add(Item, Item.Index)
case update(Item, Item.Index)
case remove(Item.Index)
}
}
For more on self-documenting code, check out "Writing self-documenting Swift code".
🤯 A little late night prototyping session reveals that protocol constraints can not only be applied to extensions - they can also be added to protocol definitions!
This is awesome, since it lets us easily define specialized protocols based on more generic ones.
protocol Component {
associatedtype Container
func add(to container: Container)
}
// Protocols that inherit from other protocols can include
// constraints to further specialize them.
protocol ViewComponent: Component where Container == UIView {
associatedtype View: UIView
var view: View { get }
}
extension ViewComponent {
func add(to container: UIView) {
container.addSubview(view)
}
}
For more on specializing protocols, check out "Specializing protocols in Swift".
📦 Here's a super handy extension on Swift's Optional
type, which gives us a really nice API for easily unwrapping an optional, or throwing an error in case the value turned out to be nil
:
extension Optional {
func orThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
switch self {
case .some(let value):
return value
case .none:
throw errorExpression()
}
}
}
let file = try loadFile(at: path).orThrow(MissingFileError())
For more ways that optionals can be extended, check out "Extending optionals in Swift".
👩🔬 Testing code that uses static APIs can be really tricky, but there's a way that it can often be done - using Swift's first class function capabilities!
Instead of accessing that static API directly, we can inject the function we want to use, which enables us to mock it!
// BEFORE
class FriendsLoader {
func loadFriends(then handler: @escaping (Result<[Friend]>) -> Void) {
Networking.loadData(from: .friends) { result in
...
}
}
}
// AFTER
class FriendsLoader {
typealias Handler<T> = (Result<T>) -> Void
typealias DataLoadingFunction = (Endpoint, @escaping Handler<Data>) -> Void
func loadFriends(using dataLoading: DataLoadingFunction = Networking.loadData,
then handler: @escaping Handler<[Friend]>) {
dataLoading(.friends) { result in
...
}
}
}
// MOCKING IN TESTS
let dataLoading: FriendsLoader.DataLoadingFunction = { _, handler in
handler(.success(mockData))
}
friendsLoader.loadFriends(using: dataLoading) { result in
...
}
🐾 Swift's pattern matching capabilities are so powerful! Two enum cases with associated values can even be matched and handled by the same switch case - which is super useful when handling state changes with similar data.
enum DownloadState {
case inProgress(progress: Double)
case paused(progress: Double)
case cancelled
case finished(Data)
}
func downloadStateDidChange(to state: DownloadState) {
switch state {
case .inProgress(let progress), .paused(let progress):
updateProgressView(with: progress)
case .cancelled:
showCancelledMessage()
case .finished(let data):
process(data)
}
}
🅰 One really nice benefit of Swift multiline string literals - even for single lines of text - is that they don't require quotes to be escaped. Perfect when working with things like HTML, or creating a custom description for an object.
let html = highlighter.highlight("Array<String>")
XCTAssertEqual(html, """
<span class="type">Array</span><<span class="type">String</span>>
""")
💎 While it's very common in functional programming, the reduce
function might be a bit of a hidden gem in Swift. It provides a super useful way to transform a sequence into a single value.
extension Sequence where Element: Equatable {
func numberOfOccurrences(of target: Element) -> Int {
return reduce(0) { result, element in
guard element == target else {
return result
}
return result + 1
}
}
}
You can read more about transforming collections in "Transforming collections in Swift".
📦 When I use Codable in Swift, I want to avoid manual implementations as much as possible, even when there's a mismatch between my code structure and the JSON I'm decoding.
One way that can often be achieved is to use private data containers combined with computed properties.
struct User: Codable {
let name: String
let age: Int
var homeTown: String { return originPlace.name }
private let originPlace: Place
}
private extension User {
struct Place: Codable {
let name: String
}
}
extension User {
struct Container: Codable {
let user: User
}
}
🚢 Instead of using feature branches, I merge almost all of my code directly into master - and then I use feature flags to conditionally enable features when they're ready. That way I can avoid merge conflicts and keep shipping!
extension ListViewController {
func addSearchIfNeeded() {
// Rather than having to keep maintaining a separate
// feature branch for a new feature, we can use a flag
// to conditionally turn it on.
guard FeatureFlags.searchEnabled else {
return
}
let resultsVC = SearchResultsViewController()
let searchVC = UISearchController(
searchResultsController: resultsVC
)
searchVC.searchResultsUpdater = resultsVC
navigationItem.searchController = searchVC
}
}
You can read more about feature flags in "Feature flags in Swift".
💾 Here I'm using tuples to create a lightweight hierarchy for my data, giving me a nice structure without having to introduce any additional types.
struct CodeSegment {
var tokens: (
previous: String?,
current: String
)
var delimiters: (
previous: Character?
next: Character?
)
}
handle(segment.tokens.current)
You can read more about tuples in "Using tuples as lightweight types in Swift"
3️⃣ Whenever I have 3 properties or local variables that share the same prefix, I usually try to extract them into their own method or type. That way I can avoid massive types & methods, and also increase readability, without falling into a "premature optimization" trap.
Before
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articleFolder = try contentFolder.subfolder(named: "posts")
let articleProcessor = ContentProcessor(folder: articleFolder)
let articles = try articleProcessor.process()
...
}
After
public func generate() throws {
let contentFolder = try folder.subfolder(named: "content")
let articles = try processArticles(in: contentFolder)
...
}
private func processArticles(in folder: Folder) throws -> [ContentItem] {
let folder = try folder.subfolder(named: "posts")
let processor = ContentProcessor(folder: folder)
return try processor.process()
}
👨🔧 Here's two extensions that I always add to the Encodable
& Decodable
protocols, which for me really make the Codable API nicer to use. By using type inference for decoding, a lot of boilerplate can be removed when the compiler is already able to infer the resulting type.
extension Encodable {
func encoded() throws -> Data {
return try JSONEncoder().encode(self)
}
}
extension Data {
func decoded<T: Decodable>() throws -> T {
return try JSONDecoder().decode(T.self, from: self)
}
}
let data = try user.encoded()
// By using a generic type in the decoded() method, the
// compiler can often infer the type we want to decode
// from the current context.
try userDidLogin(data.decoded())
// And if not, we can always supply the type, still making
// the call site read very nicely.
let otherUser = try data.decoded() as User
📦 UserDefaults
is a lot more powerful than what it first might seem like. Not only can it store more complex values (like dates & dictionaries) and parse command line arguments - it also enables easy sharing of settings & lightweight data between apps in the same App Group.
let sharedDefaults = UserDefaults(suiteName: "my-app-group")!
let useDarkMode = sharedDefaults.bool(forKey: "dark-mode")
// This value is put into the shared suite.
sharedDefaults.set(true, forKey: "dark-mode")
// If you want to treat the shared settings as read-only (and add
// local overrides on top of them), you can simply add the shared
// suite to the standard UserDefaults.
let combinedDefaults = UserDefaults.standard
combinedDefaults.addSuite(named: "my-app-group")
// This value is a local override, not added to the shared suite.
combinedDefaults.set(true, forKey: "app-specific-override")
🎨 By overriding layerClass
you can tell UIKit what CALayer
class to use for a UIView
's backing layer. That way you can reduce the amount of layers, and don't have to do any manual layout.
final class GradientView: UIView {
override class var layerClass: AnyClass { return CAGradientLayer.self }
var colors: (start: UIColor, end: UIColor)? {
didSet { updateLayer() }
}
private func updateLayer() {
let layer = self.layer as! CAGradientLayer
layer.colors = colors.map { [$0.start.cgColor, $0.end.cgColor] }
}
}
✅ That the compiler now automatically synthesizes Equatable conformances is such a huge upgrade for Swift! And the cool thing is that it works for all kinds of types - even for enums with associated values! Especially useful when using enums for verification in unit tests.
struct Article: Equatable {
let title: String
let text: String
}
struct User: Equatable {
let name: String
let age: Int
}
extension Navigator {
enum Destination: Equatable {
case profile(User)
case article(Article)
}
}
func testNavigatingToArticle() {
let article = Article(title: "Title", text: "Text")
controller.select(article)
XCTAssertEqual(navigator.destinations, [.article(article)])
}
🤝 Associated types can have defaults in Swift - which is super useful for types that are not easily inferred (for example when they're not used for a specific instance method or property).
protocol Identifiable {
associatedtype RawIdentifier: Codable = String
var id: Identifier<Self> { get }
}
struct User: Identifiable {
let id: Identifier<User>
let name: String
}
struct Group: Identifiable {
typealias RawIdentifier = Int
let id: Identifier<Group>
let name: String
}
🆔 If you want to avoid using plain strings as identifiers (which can increase both type safety & readability), it's really easy to create a custom Identifier type that feels just like a native Swift type, thanks to protocols!
More on this topic in "Type-safe identifiers in Swift".
struct Identifier: Hashable {
let string: String
}
extension Identifier: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
string = value
}
}
extension Identifier: CustomStringConvertible {
var description: String {
return string
}
}
extension Identifier: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
string = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(string)
}
}
struct Article: Codable {
let id: Identifier
let title: String
}
let article = Article(id: "my-article", title: "Hello world!")
🙌 A really cool thing about using tuples to model the internal state of a Swift type, is that you can unwrap an optional tuple's members directly into local variables.
Very useful in order to group multiple optional values together for easy unwrapping & handling.
class ImageTransformer {
private var queue = [(image: UIImage, transform: Transform)]()
private func processNext() {
// When unwrapping an optional tuple, you can assign the members
// directly to local variables.
guard let (image, transform) = queue.first else {
return
}
let context = Context()
context.draw(image)
context.apply(transform)
...
}
}
❤️ I love to structure my code using extensions in Swift. One big benefit of doing so when it comes to struct initializers, is that defining a convenience initializer doesn't remove the default one the compiler generates - best of both worlds!
struct Article {
let date: Date
var title: String
var text: String
var comments: [Comment]
}
extension Article {
init(title: String, text: String) {
self.init(date: Date(), title: title, text: text, comments: [])
}
}
let articleA = Article(title: "Best Cupcake Recipe", text: "...")
let articleB = Article(
date: Date(),
title: "Best Cupcake Recipe",
text: "...",
comments: [
Comment(user: currentUser, text: "Yep, can confirm!")
]
)
🏈 A big benefit of using throwing functions for synchronous Swift APIs is that the caller can decide whether they want to treat the return value as optional (try?
) or required (try
).
func loadFile(named name: String) throws -> File {
guard let url = urlForFile(named: name) else {
throw File.Error.missing
}
do {
let data = try Data(contentsOf: url)
return File(url: url, data: data)
} catch {
throw File.Error.invalidData(error)
}
}
let requiredFile = try loadFile(named: "AppConfig.json")
let optionalFile = try? loadFile(named: "UserSettings.json")
🐝 Types that are nested in generics automatically inherit their parent's generic types - which is super useful when defining accessory types (for things like states or outcomes).
struct Task<Input, Output> {
typealias Closure = (Input) throws -> Output
let closure: Closure
}
extension Task {
enum Result {
case success(Output)
case failure(Error)
}
}
🤖 Now that the Swift compiler automatically synthesizes Equatable & Hashable conformances for value types, it's easier than ever to setup model structures with nested types that are all Equatable
/Hashable
!
typealias Value = Hashable & Codable
struct User: Value {
var name: String
var age: Int
var lastLoginDate: Date?
var settings: Settings
}
extension User {
struct Settings: Value {
var itemsPerPage: Int
var theme: Theme
}
}
extension User.Settings {
enum Theme: String, Value {
case light
case dark
}
}
You can read more about using nested types in Swift here.
🎉 Swift 4.1 is here! One of the key features it brings is conditional conformances, which lets you have a type only conform to a protocol under certain constraints.
protocol UnboxTransformable {
associatedtype RawValue
static func transform(_ value: RawValue) throws -> Self?
}
extension Array: UnboxTransformable where Element: UnboxTransformable {
typealias RawValue = [Element.RawValue]
static func transform(_ value: RawValue) throws -> [Element]? {
return try value.compactMap(Element.transform)
}
}
I also have an article with lots of more info on conditional conformances here. Paul Hudson also has a great overview of all Swift 4.1 features here.
🕵️♀️ A cool thing about Swift type aliases is that they can be generic! Combine that with tuples and you can easily define simple generic types.
typealias Pair<T> = (T, T)
extension Game {
func calculateScore(for players: Pair<Player>) -> Int {
...
}
}
You can read more about using tuples as lightweight types here.
☑️ A really cool "hidden" feature of UserDefaults is that it contains any arguments that were passed to the app at launch!
Super useful both in Swift command line tools & scripts, but also to temporarily override a value when debugging iOS apps.
let defaults = UserDefaults.standard
let query = defaults.string(forKey: "query")
let resultCount = defaults.integer(forKey: "results")
👏 Swift's &
operator is awesome! Not only can you use it to compose protocols, you can compose other types too! Very useful if you want to hide concrete types & implementation details.
protocol LoadableFromURL {
func load(from url: URL)
}
class ContentViewController: UIViewController, LoadableFromURL {
func load(from url: URL) {
...
}
}
class ViewControllerFactory {
func makeContentViewController() -> UIViewController & LoadableFromURL {
return ContentViewController()
}
}
🤗 When capturing values in mocks, using an array (instead of just a single value) makes it easy to verify that only a certain number of values were passed.
Perfect for protecting against "over-calling" something.
class UserManagerTests: XCTestCase {
func testObserversCalledWhenUserFirstLogsIn() {
let manager = UserManager()
let observer = ObserverMock()
manager.addObserver(observer)
// First login, observers should be notified
let user = User(id: 123, name: "John")
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
// If the same user logs in again, observers shouldn't be notified
manager.userDidLogin(user)
XCTAssertEqual(observer.users, [user])
}
}
private extension UserManagerTests {
class ObserverMock: UserManagerObserver {
private(set) var users = [User]()
func userDidChange(to user: User) {
users.append(user)
}
}
}
👋 When writing tests, you don't always need to create mocks - you can create stubs using real instances of things like errors, URLs & UserDefaults.
Here's how to do that for some common tasks/object types in Swift:
// Create errors using NSError (#function can be used to reference the name of the test)
let error = NSError(domain: #function, code: 1, userInfo: nil)
// Create non-optional URLs using file paths
let url = URL(fileURLWithPath: "Some/URL")
// Reference the test bundle using Bundle(for:)
let bundle = Bundle(for: type(of: self))
// Create an explicit UserDefaults object (instead of having to use a mock)
let userDefaults = UserDefaults(suiteName: #function)
// Create queues to control/await concurrent operations
let queue = DispatchQueue(label: #function)
For when you actually do need mocking, check out "Mocking in Swift".
⏱ I've started using "then" as an external parameter label for completion handlers. Makes the call site read really nicely (Because I do ❤️ conversational API design) regardless of whether trailing closure syntax is used or not.
protocol DataLoader {
// Adding type aliases to protocols can be a great way to
// reduce verbosity for parameter types.
typealias Handler = (Result<Data>) -> Void
associatedtype Endpoint
func loadData(from endpoint: Endpoint, then handler: @escaping Handler)
}
loader.loadData(from: .messages) { result in
...
}
loader.loadData(from: .messages, then: { result in
...
})
😴 Combining lazily evaluated sequences with builder pattern-like properties can lead to some pretty sweet APIs for configurable sequences in Swift.
Also useful for queries & other things you "build up" and then execute.
// Extension adding builder pattern-like properties that return
// a new sequence value with the given configuration applied
extension FileSequence {
var recursive: FileSequence {
var sequence = self
sequence.isRecursive = true
return sequence
}
var includingHidden: FileSequence {
var sequence = self
sequence.includeHidden = true
return sequence
}
}
// BEFORE
let files = folder.makeFileSequence(recursive: true, includeHidden: true)
// AFTER
let files = folder.files.recursive.includingHidden
Want an intro to lazy sequences? Check out "Swift sequences: The art of being lazy".
My top 3 tips for faster & more stable UI tests:
📱 Reset the app's state at the beginning of every test.
🆔 Use accessibility identifiers instead of UI strings.
⏱ Use expectations instead of waiting time.
func testOpeningArticle() {
// Launch the app with an argument that tells it to reset its state
let app = XCUIApplication()
app.launchArguments.append("--uitesting")
app.launch()
// Check that the app is displaying an activity indicator
let activityIndicator = app.activityIndicator.element
XCTAssertTrue(activityIndicator.exists)
// Wait for the loading indicator to disappear = content is ready
expectation(for: NSPredicate(format: "exists == 0"),
evaluatedWith: activityIndicator)
// Use a generous timeout in case the network is slow
waitForExpectations(timeout: 10)
// Tap the cell for the first article
app.tables.cells["Article.0"].tap()
// Assert that a label with the accessibility identifier "Article.Title" exists
let label = app.staticTexts["Article.Title"]
XCTAssertTrue(label.exists)
}
📋 It's super easy to access the contents of the clipboard from a Swift script. A big benefit of Swift scripting is being able to use Cocoa's powerful APIs for Mac apps.
import Cocoa
let clipboard = NSPasteboard.general.string(forType: .string)
🎯 Using Swift tuples for view state can be a super nice way to group multiple properties together and render them reactively using the layout system.
By using a tuple we don't have to either introduce a new type or make our view model-aware.
class TextView: UIView {
var state: (title: String?, text: String?) {
// By telling UIKit that our view needs layout and binding our
// state in layoutSubviews, we can react to state changes without
// doing unnecessary layout work.
didSet { setNeedsLayout() }
}
private let titleLabel = UILabel()
private let textLabel = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.text = state.title
textLabel.text = state.text
...
}
}
⚾️ Swift tests can throw, which is super useful in order to avoid complicated logic or force unwrapping. By making errors conform to LocalizedError
, you can also get a nice error message in Xcode if there's a failure.
class ImageCacheTests: XCTestCase {
func testCachingAndLoadingImage() throws {
let bundle = Bundle(for: type(of: self))
let cache = ImageCache(bundle: bundle)
// Bonus tip: You can easily load images from your test
// bundle using this UIImage initializer
let image = try require(UIImage(named: "sample", in: bundle, compatibleWith: nil))
try cache.cache(image, forKey: "key")
let cachedImage = try cache.image(forKey: "key")
XCTAssertEqual(image, cachedImage)
}
}
enum ImageCacheError {
case emptyKey
case dataConversionFailed
}
// When using throwing tests, making your errors conform to
// LocalizedError will render a much nicer error message in
// Xcode (per default only the error code is shown).
extension ImageCacheError: LocalizedError {
var errorDescription: String? {
switch self {
case .emptyKey:
return "An empty key was given"
case .dataConversionFailed:
return "Failed to convert the given image to Data"
}
}
}
For more information, and the implementation of the require
method used above, check out "Avoiding force unwrapping in Swift unit tests".
✍️ Unlike static
properties, class
properties can be overridden by subclasses (however, they can't be stored, only computed).
class TableViewCell: UITableViewCell {
class var preferredHeight: CGFloat { return 60 }
}
class TallTableViewCell: TableViewCell {
override class var preferredHeight: CGFloat { return 100 }
}
👨🎨 Creating extensions with static factory methods can be a great alternative to subclassing in Swift, especially for things like setting up UIViews, CALayers or other kinds of styling.
It also lets you remove a lot of styling & setup from your view controllers.
extension UILabel {
static func makeForTitle() -> UILabel {
let label = UILabel()
label.font = .boldSystemFont(ofSize: 24)
label.textColor = .darkGray
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.75
return label
}
static func makeForText() -> UILabel {
let label = UILabel()
label.font = .systemFont(ofSize: 16)
label.textColor = .black
label.numberOfLines = 0
return label
}
}
class ArticleViewController: UIViewController {
lazy var titleLabel = UILabel.makeForTitle()
lazy var textLabel = UILabel.makeForText()
}
🧒 An awesome thing about child view controllers is that they're automatically resized to match their parent, making them a super nice solution for things like loading & error views.
class ListViewController: UIViewController {
func loadItems() {
let loadingViewController = LoadingViewController()
add(loadingViewController)
dataLoader.loadItems { [weak self] result in
loadingViewController.remove()
self?.handle(result)
}
}
}
For more about child view controller (including the add
and remove
methods used above), check out "Using child view controllers as plugins in Swift".
🤐 Using the zip function in Swift you can easily combine two sequences. Super useful when using two sequences to do some work, since zip takes care of all the bounds-checking.
func render(titles: [String]) {
for (label, text) in zip(titleLabels, titles) {
print(text)
label.text = text
}
}
🎛 The awesome thing about option sets in Swift is that they can automatically either be passed as a single member or as a set. Even cooler is that you can easily define your own option sets as well, perfect for options and other non-exclusive values.
// Option sets are awesome, because you can easily pass them
// both using dot syntax and array literal syntax, like when
// using the UIView animation API:
UIView.animate(withDuration: 0.3,
delay: 0,
options: .allowUserInteraction,
animations: animations)
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.allowUserInteraction, .layoutSubviews],
animations: animations)
// The cool thing is that you can easily define your own option
// sets as well, by defining a struct that has an Int rawValue,
// that will be used as a bit mask.
extension Cache {
struct Options: OptionSet {
static let saveToDisk = Options(rawValue: 1)
static let clearOnMemoryWarning = Options(rawValue: 1 << 1)
static let clearDaily = Options(rawValue: 1 << 2)
let rawValue: Int
}
}
// We can now use Cache.Options just like UIViewAnimationOptions:
Cache(options: .saveToDisk)
Cache(options: [.saveToDisk, .clearDaily])
🙌 Using the where
clause when designing protocol-oriented APIs in Swift can let your implementations (or others' if it's open source) have a lot more freedom, especially when it comes to collections.
See "Using generic type constraints in Swift 4" for more info.
public protocol PathFinderMap {
associatedtype Node
// Using the 'where' clause for associated types, we can
// ensure that a type meets certain requirements (in this
// case that it's a sequence with Node elements).
associatedtype NodeSequence: Sequence where NodeSequence.Element == Node
// Instead of using a concrete type (like [Node]) here, we
// give implementors of this protocol more freedom while
// still meeting our requirements. For example, one
// implementation might use Set<Node>.
func neighbors(of node: Node) -> NodeSequence
}
👨🍳 Combine first class functions in Swift with the fact that Dictionary elements are (Key, Value) tuples and you can build yourself some pretty awesome functional chains when iterating over a Dictionary.
func makeActor(at coordinate: Coordinate, for building: Building) -> Actor {
let actor = Actor()
actor.position = coordinate.point
actor.animation = building.animation
return actor
}
func render(_ buildings: [Coordinate : Building]) {
buildings.map(makeActor).forEach(add)
}
😎 In Swift, you can call any instance method as a static function and it will return a closure representing that method. This is how running tests using SPM on Linux works.
More about this topic in my blog post "First class functions in Swift".
// This produces a '() -> Void' closure which is a reference to the
// given view's 'removeFromSuperview' method.
let closure = UIView.removeFromSuperview(view)
// We can now call it just like we would any other closure, and it
// will run 'view.removeFromSuperview()'
closure()
// This is how running tests using the Swift Package Manager on Linux
// works, you return your test functions as closures:
extension UserManagerTests {
static var allTests = [
("testLoggingIn", testLoggingIn),
("testLoggingOut", testLoggingOut),
("testUserPermissions", testUserPermissions)
]
}
👏 One really nice benefit of dropping suffixes from method names (and just using verbs, when possible) is that it becomes super easy to support both single and multiple arguments, and it works really well semantically.
extension UIView {
func add(_ subviews: UIView...) {
subviews.forEach(addSubview)
}
}
view.add(button)
view.add(label)
// By dropping the "Subview" suffix from the method name, both
// single and multiple arguments work really well semantically.
view.add(button, label)
👽 Using the AnyObject
(or class
) constraint on protocols is not only useful when defining delegates (or other weak references), but also when you always want instances to be mutable without copying.
// By constraining a protocol with 'AnyObject' it can only be adopted
// by classes, which means all instances will always be mutable, and
// that it's the original instance (not a copy) that will be mutated.
protocol DataContainer: AnyObject {
var data: Data? { get set }
}
class UserSettingsManager {
private var settings: Settings
private let dataContainer: DataContainer
// Since DataContainer is a protocol, we an easily mock it in
// tests if we use dependency injection
init(settings: Settings, dataContainer: DataContainer) {
self.settings = settings
self.dataContainer = dataContainer
}
func saveSettings() throws {
let data = try settings.serialize()
// We can now assign properties on an instance of our protocol
// because the compiler knows it's always going to be a class
dataContainer.data = data
}
}
🍣 Even if you define a custom raw value for a string-based enum in Swift, the full case name will be used in string interpolation.
Super useful when using separate raw values for JSON, while still wanting to use the full case name in other contexts.
extension Building {
// This enum has custom raw values that are used when decoding
// a value, for example from JSON.
enum Kind: String {
case castle = "C"
case town = "T"
case barracks = "B"
case goldMine = "G"
case camp = "CA"
case blacksmith = "BL"
}
var animation: Animation {
return Animation(
// When used in string interpolation, the full case name is still used.
// For 'castle' this will be 'buildings/castle'.
name: "buildings/\(kind)",
frameCount: frameCount,
frameDuration: frameDuration
)
}
}
👨🔬 Continuing to experiment with expressive ways of comparing a value with a list of candidates in Swift. Adding an extension on Equatable is probably my favorite approach so far.
extension Equatable {
func isAny(of candidates: Self...) -> Bool {
return candidates.contains(self)
}
}
let isHorizontal = direction.isAny(of: .left, .right)
See tip 35 for my previous experiment.
📐 A really interesting side-effect of a UIView
's bounds
being its rect within its own coordinate system is that transforms don't affect it at all. That's why it's usually a better fit than frame
when doing layout calculations of subviews.
let view = UIView()
view.frame.size = CGSize(width: 100, height: 100)
view.transform = CGAffineTransform(scaleX: 2, y: 2)
print(view.frame) // (-50.0, -50.0, 200.0, 200.0)
print(view.bounds) // (0.0, 0.0, 100.0, 100.0)
👏 It's awesome that many UIKit APIs with completion handlers and other optional parameters import into Swift with default arguments (even though they are written in Objective-C). Getting rid of all those nil arguments is so nice!
// BEFORE: All parameters are specified, just like in Objective-C
viewController.present(modalViewController, animated: true, completion: nil)
modalViewController.dismiss(animated: true, completion: nil)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
options: [],
animations: animations,
completion: nil)
// AFTER: Since many UIKit APIs with completion handlers and other
// optional parameters import into Swift with default arguments,
// we can make our calls shorter
viewController.present(modalViewController, animated: true)
modalViewController.dismiss(animated: true)
viewController.transition(from: loadingViewController,
to: contentViewController,
duration: 0.3,
animations: animations)
✂️ Avoiding Massive View Controllers is all about finding the right levels of abstraction and splitting things up.
My personal rule of thumb is that as soon as I have 3 methods or properties that have the same prefix, I break them out into their own type.
// BEFORE
class LoginViewController: UIViewController {
private lazy var signUpLabel = UILabel()
private lazy var signUpImageView = UIImageView()
private lazy var signUpButton = UIButton()
}
// AFTER
class LoginViewController: UIViewController {
private lazy var signUpView = SignUpView()
}
class SignUpView: UIView {
private lazy var label = UILabel()
private lazy var imageView = UIImageView()
private lazy var button = UIButton()
}
❤️ I love the fact that optionals are enums in Swift - it makes it so easy to extend them with convenience APIs for certain types. Especially useful when doing things like data validation on optional values.
func validateTextFields() -> Bool {
guard !usernameTextField.text.isNilOrEmpty else {
return false
}
...
return true
}
// Since all optionals are actual enum values in Swift, we can easily
// extend them for certain types, to add our own convenience APIs
extension Optional where Wrapped == String {
var isNilOrEmpty: Bool {
switch self {
case let string?:
return string.isEmpty
case nil:
return true
}
}
}
// Since strings are now Collections in Swift 4, you can even
// add this property to all optional collections:
extension Optional where Wrapped: Collection {
var isNilOrEmpty: Bool {
switch self {
case let collection?:
return collection.isEmpty
case nil:
return true
}
}
}
🗺 Using the where
keyword can be a super nice way to quickly apply a filter in a for
-loop in Swift. You can of course use map
, filter
and forEach
, or guard
, but for simple loops I think this is very expressive and nice.
func archiveMarkedPosts() {
for post in posts where post.isMarked {
archive(post)
}
}
func healAllies() {
for player in players where player.isAllied(to: currentPlayer) {
player.heal()
}
}
👻 Variable shadowing can be super useful in Swift, especially when you want to create a local copy of a parameter value in order to use it as state within a closure.
init(repeatMode: RepeatMode, closure: @escaping () -> UpdateOutcome) {
// Shadow the argument with a local, mutable copy
var repeatMode = repeatMode
self.closure = {
// With shadowing, there's no risk of accidentially
// referring to the immutable version
switch repeatMode {
case .forever:
break
case .times(let count):
guard count > 0 else {
return .finished
}
// We can now capture the mutable version and use
// it for state in a closure
repeatMode = .times(count - 1)
}
return closure()
}
}
✒️ Dot syntax is one of my favorite features of Swift. What's really cool is that it's not only for enums, any static method or property can be used with dot syntax - even initializers! Perfect for convenience APIs and default parameters.
public enum RepeatMode {
case times(Int)
case forever
}
public extension RepeatMode {
static var never: RepeatMode {
return .times(0)
}
static var once: RepeatMode {
return .times(1)
}
}
view.perform(animation, repeated: .once)
// To make default parameters more compact, you can even use init with dot syntax
class ImageLoader {
init(cache: Cache = .init(), decoder: ImageDecoder = .init()) {
...
}
}
🚀 One really cool aspect of Swift having first class functions is that you can pass any function (or even initializer) as a closure, and even call it with a tuple containing its parameters!
// This function lets us treat any "normal" function or method as
// a closure and run it with a tuple that contains its parameters
func call<Input, Output>(_ function: (Input) -> Output, with input: Input) -> Output {
return function(input)
}
class ViewFactory {
func makeHeaderView() -> HeaderView {
// We can now pass an initializer as a closure, and a tuple
// containing its parameters
return call(HeaderView.init, with: loadTextStyles())
}
private func loadTextStyles() -> (font: UIFont, color: UIColor) {
return (theme.font, theme.textColor)
}
}
class HeaderView {
init(font: UIFont, textColor: UIColor) {
...
}
}
💉 If you've been struggling to test code that uses static APIs, here's a technique you can use to enable static dependency injection without having to modify any call sites:
// Before: Almost impossible to test due to the use of singletons
class Analytics {
static func log(_ event: Event) {
Database.shared.save(event)
let dictionary = event.serialize()
NetworkManager.shared.post(dictionary, to: eventURL)
}
}
// After: Much easier to test, since we can inject mocks as arguments
class Analytics {
static func log(_ event: Event,
database: Database = .shared,
networkManager: NetworkManager = .shared) {
database.save(event)
let dictionary = event.serialize()
networkManager.post(dictionary, to: eventURL)
}
}
🎉 In Swift 4, type inference works for lazy properties and you don't need to explicitly refer to self
!
// Swift 3
class PurchaseView: UIView {
private lazy var buyButton: UIButton = self.makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
// Swift 4
class PurchaseView: UIView {
private lazy var buyButton = makeBuyButton()
private func makeBuyButton() -> UIButton {
let button = UIButton()
button.setTitle("Buy", for: .normal)
button.setTitleColor(.blue, for: .normal)
return button
}
}
😎 You can turn any Swift Error
into an NSError
, which is super useful when pattern matching with a code 👍. Also, switching on optionals is pretty cool!
let task = urlSession.dataTask(with: url) { data, _, error in
switch error {
case .some(let error as NSError) where error.code == NSURLErrorNotConnectedToInternet:
presenter.showOfflineView()
case .some(let error):
presenter.showGenericErrorView()
case .none:
presenter.renderContent(from: data)
}
}
task.resume()
Also make sure to check out Kostas Kremizas' tip about how you can pattern match directly against a member of URLError
.
🖥 Here's an easy way to make iOS model code that uses UIImage
macOS compatible - like me and Gui Rambo discussed on the Swift by Sundell Podcast.
// Either put this in a separate file that you only include in your macOS target or wrap the code in #if os(macOS) / #endif
import Cocoa
// Step 1: Typealias UIImage to NSImage
typealias UIImage = NSImage
// Step 2: You might want to add these APIs that UIImage has but NSImage doesn't.
extension NSImage {
var cgImage: CGImage? {
var proposedRect = CGRect(origin: .zero, size: size)
return cgImage(forProposedRect: &proposedRect,
context: nil,
hints: nil)
}
convenience init?(named name: String) {
self.init(named: Name(name))
}
}
// Step 3: Profit - you can now make your model code that uses UIImage cross-platform!
struct User {
let name: String
let profileImage: UIImage
}
🤖 You can easily define a protocol-oriented API that can only be mutated internally, by using an internal protocol that extends a public one.
// Declare a public protocol that acts as your immutable API
public protocol ModelHolder {
associatedtype Model
var model: Model { get }
}
// Declare an extended, internal protocol that provides a mutable API
internal protocol MutableModelHolder: ModelHolder {
var model: Model { get set }
}
// You can now implement the requirements using 'public internal(set)'
public class UserHolder: MutableModelHolder {
public internal(set) var model: User
internal init(model: User) {
self.model = model
}
}
🎛 You can switch on a set using array literals as cases in Swift! Can be really useful to avoid many if
/else if
statements.
class RoadTile: Tile {
var connectedDirections = Set<Direction>()
func render() {
switch connectedDirections {
case [.up, .down]:
image = UIImage(named: "road-vertical")
case [.left, .right]:
image = UIImage(named: "road-horizontal")
default:
image = UIImage(named: "road")
}
}
}
🌍 When caching localized content in an app, it's a good idea to add the current locale to all keys, to prevent bugs when switching languages.
func cache(_ content: Content, forKey key: String) throws {
let data = try wrap(content) as Data
let key = localize(key: key)
try storage.store(data, forKey: key)
}
func loadCachedContent(forKey key: String) -> Content? {
let key = localize(key: key)
let data = storage.loadData(forKey: key)
return data.flatMap { try? unbox(data: $0) }
}
private func localize(key: String) -> String {
return key + "-" + Bundle.main.preferredLocalizations[0]
}
🚳 Here's an easy way to setup a test to avoid accidental retain cycles with object relationships (like weak delegates & observers) in Swift:
func testDelegateNotRetained() {
// Assign the delegate (weak) and also retain it using a local var
var delegate: Delegate? = DelegateMock()
controller.delegate = delegate
XCTAssertNotNil(controller.delegate)
// Release the local var, which should also release the weak reference
delegate = nil
XCTAssertNil(controller.delegate)
}
👨🔬 Playing around with an expressive way to check if a value matches any of a list of candidates in Swift:
// Instead of multiple conditions like this:
if string == "One" || string == "Two" || string == "Three" {
}
// You can now do:
if string == any(of: "One", "Two", "Three") {
}
You can find a gist with the implementation here.
👪 APIs in a Swift extension automatically inherit its access control level, making it a neat way to organize public, internal & private APIs.
public extension Animation {
init(textureNamed textureName: String) {
frames = [Texture(name: textureName)]
}
init(texturesNamed textureNames: [String], frameDuration: TimeInterval = 1) {
frames = textureNames.map(Texture.init)
self.frameDuration = frameDuration
}
init(image: Image) {
frames = [Texture(image: image)]
}
}
internal extension Animation {
func loadFrameImages() -> [Image] {
return frames.map { $0.loadImageIfNeeded() }
}
}
🗺 Using map
you can transform an optional value into an optional Result
type by simply passing in the enum case.
enum Result<Value> {
case value(Value)
case error(Error)
}
class Promise<Value> {
private var result: Result<Value>?
init(value: Value? = nil) {
result = value.map(Result.value)
}
}
👌 It's so nice that you can assign directly to self
in struct
initializers in Swift. Very useful when adding conformance to protocols.
extension Bool: AnswerConvertible {
public init(input: String) throws {
switch input.lowercased() {
case "y", "yes", "👍":
self = true
default:
self = false
}
}
}
☎️ Defining Swift closures as inline functions enables you to recursively call them, which is super useful in things like custom sequences.
class Database {
func records(matching query: Query) -> AnySequence<Record> {
var recordIterator = loadRecords().makeIterator()
func iterate() -> Record? {
guard let nextRecord = recordIterator.next() else {
return nil
}
guard nextRecord.matches(query) else {
// Since the closure is an inline function, it can be recursively called,
// in this case in order to advance to the next item.
return iterate()
}
return nextRecord
}
// AnySequence/AnyIterator are part of the standard library and provide an easy way
// to define custom sequences using closures.
return AnySequence { AnyIterator(iterate) }
}
}
Rob Napier points out that using the above might cause crashes if used on a large databaset, since Swift has no guaranteed Tail Call Optimization (TCO).
Slava Pestov also points out that another benefit of inline functions vs closures is that they can have their own generic parameter list.
🏖 Using lazy properties in Swift, you can pass self
to required Objective-C dependencies without having to use force-unwrapped optionals.
class DataLoader: NSObject {
lazy var urlSession: URLSession = self.makeURLSession()
private func makeURLSession() -> URLSession {
return URLSession(configuration: .default, delegate: self, delegateQueue: .main)
}
}
class Renderer {
lazy var displayLink: CADisplayLink = self.makeDisplayLink()
private func makeDisplayLink() -> CADisplayLink {
return CADisplayLink(target: self, selector: #selector(screenDidRefresh))
}
}
👓 If you have a property in Swift that needs to be weak
or lazy
, you can still make it readonly by using private(set)
.
class Node {
private(set) weak var parent: Node?
private(set) lazy var children = [Node]()
func add(child: Node) {
children.append(child)
child.parent = self
}
}
🌏 Tired of using URL(string: "url")!
for static URLs? Make URL
conform to ExpressibleByStringLiteral
and you can now simply use "url"
instead.
extension URL: ExpressibleByStringLiteral {
// By using 'StaticString' we disable string interpolation, for safety
public init(stringLiteral value: StaticString) {
self = URL(string: "\(value)").require(hint: "Invalid URL string literal: \(value)")
}
}
// We can now define URLs using static string literals 🎉
let url: URL = "https://www.swiftbysundell.com"
let task = URLSession.shared.dataTask(with: "https://www.swiftbysundell.com")
// In Swift 3 or earlier, you also have to implement 2 additional initializers
extension URL {
public init(extendedGraphemeClusterLiteral value: StaticString) {
self.init(stringLiteral: value)
}
public init(unicodeScalarLiteral value: StaticString) {
self.init(stringLiteral: value)
}
}
To find the extension that adds the require()
method on Optional
that I use above, check out Require.
✚ I'm always careful with operator overloading, but for manipulating things like sizes, points & frames I find them super useful.
extension CGSize {
static func *(lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}
}
button.frame.size = image.size * 2
If you like the above idea, check out CGOperators, which contains math operator overloads for all Core Graphics' vector types.
🔗 You can use closure types in generic constraints in Swift. Enables nice APIs for handling sequences of closures.
extension Sequence where Element == () -> Void {
func callAll() {
forEach { $0() }
}
}
extension Sequence where Element == () -> String {
func joinedResults(separator: String) -> String {
return map { $0() }.joined(separator: separator)
}
}
callbacks.callAll()
let names = nameProviders.joinedResults(separator: ", ")
(If you're using Swift 3, you have to change Element
to Iterator.Element
)
🎉 Using associated enum values is a super nice way to encapsulate mutually exclusive state info (and avoiding state-specific optionals).
// BEFORE: Lots of state-specific, optional properties
class Player {
var isWaitingForMatchMaking: Bool
var invitingUser: User?
var numberOfLives: Int
var playerDefeatedBy: Player?
var roundDefeatedIn: Int?
}
// AFTER: All state-specific information is encapsulated in enum cases
class Player {
enum State {
case waitingForMatchMaking
case waitingForInviteResponse(from: User)
case active(numberOfLives: Int)
case defeated(by: Player, roundNumber: Int)
}
var state: State
}
👍 I really like using enums for all async result types, even boolean ones. Self-documenting, and makes the call site a lot nicer to read too!
protocol PushNotificationService {
// Before
func enablePushNotifications(completionHandler: @escaping (Bool) -> Void)
// After
func enablePushNotifications(completionHandler: @escaping (PushNotificationStatus) -> Void)
}
enum PushNotificationStatus {
case enabled
case disabled
}
service.enablePushNotifications { status in
if status == .enabled {
enableNotificationsButton.removeFromSuperview()
}
}
🏃 Want to work on your async code in a Swift Playground? Just set needsIndefiniteExecution
to true to keep it running:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
let greeting = "Hello after 3 seconds"
print(greeting)
}
To stop the playground from executing, simply call PlaygroundPage.current.finishExecution()
.
💦 Avoid memory leaks when accidentially refering to self
in closures by overriding it locally with a weak reference:
Swift >= 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let self = self else {
return
}
self.cache(result)
...
Swift < 4.2
dataLoader.loadData(from: url) { [weak self] result in
guard let `self` = self else {
return
}
self.cache(result)
...
Note that the reason the above currently works is because of a compiler bug (which I hope gets turned into a properly supported feature soon).
🕓 Using dispatch work items you can easily cancel a delayed asynchronous GCD task if you no longer need it:
let workItem = DispatchWorkItem {
// Your async code goes in here
}
// Execute the work item after 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem)
// You can cancel the work item if you no longer need it
workItem.cancel()
➕ While working on a new Swift developer tool (to be open sourced soon 😉), I came up with a pretty neat way of organizing its sequence of operations, by combining their functions into a closure:
internal func +<A, B, C>(lhs: @escaping (A) throws -> B,
rhs: @escaping (B) throws -> C) -> (A) throws -> C {
return { try rhs(lhs($0)) }
}
public func run() throws {
try (determineTarget + build + analyze + output)()
}
If you're familiar with the functional programming world, you might know the above technique as the pipe operator (thanks to Alexey Demedreckiy for pointing this out!)
🗺 Using map()
and flatMap()
on optionals you can chain multiple operations without having to use lengthy if lets
or guards
:
// BEFORE
guard let string = argument(at: 1) else {
return
}
guard let url = URL(string: string) else {
return
}
handle(url)
// AFTER
argument(at: 1).flatMap(URL.init).map(handle)
🚀 Using self-executing closures is a great way to encapsulate lazy property initialization:
class StoreViewController: UIViewController {
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let view = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
view.delegate = self
view.dataSource = self
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
}
}
⚡️ You can speed up your Swift package tests using the --parallel
flag. For Marathon, the tests execute 3 times faster that way!
swift test --parallel
🛠 Struggling with mocking UserDefaults
in a test? The good news is: you don't need mocking - just create a real instance:
class LoginTests: XCTestCase {
private var userDefaults: UserDefaults!
private var manager: LoginManager!
override func setUp() {
super.setup()
userDefaults = UserDefaults(suiteName: #file)
userDefaults.removePersistentDomain(forName: #file)
manager = LoginManager(userDefaults: userDefaults)
}
}
👍 Using variadic parameters in Swift, you can create some really nice APIs that take a list of objects without having to use an array:
extension Canvas {
func add(_ shapes: Shape...) {
shapes.forEach(add)
}
}
let circle = Circle(center: CGPoint(x: 5, y: 5), radius: 5)
let lineA = Line(start: .zero, end: CGPoint(x: 10, y: 10))
let lineB = Line(start: CGPoint(x: 0, y: 10), end: CGPoint(x: 10, y: 0))
let canvas = Canvas()
canvas.add(circle, lineA, lineB)
canvas.render()
😮 Just like you can refer to a Swift function as a closure, you can do the same thing with enum cases with associated values:
enum UnboxPath {
case key(String)
case keyPath(String)
}
struct UserSchema {
static let name = key("name")
static let age = key("age")
static let posts = key("posts")
private static let key = UnboxPath.key
}
📈 The ===
operator lets you check if two objects are the same instance. Very useful when verifying that an array contains an instance in a test:
protocol InstanceEquatable: class, Equatable {}
extension InstanceEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
extension Enemy: InstanceEquatable {}
func testDestroyingEnemy() {
player.attack(enemy)
XCTAssertTrue(player.destroyedEnemies.contains(enemy))
}
😎 Cool thing about Swift initializers: you can call them using dot syntax and pass them as closures! Perfect for mocking dates in tests.
class Logger {
private let storage: LogStorage
private let dateProvider: () -> Date
init(storage: LogStorage = .init(), dateProvider: @escaping () -> Date = Date.init) {
self.storage = storage
self.dateProvider = dateProvider
}
func log(event: Event) {
storage.store(event: event, date: dateProvider())
}
}
📱 Most of my UI testing logic is now categories on XCUIApplication
. Makes the test cases really easy to read:
func testLoggingInAndOut() {
XCTAssertFalse(app.userIsLoggedIn)
app.launch()
app.login()
XCTAssertTrue(app.userIsLoggedIn)
app.logout()
XCTAssertFalse(app.userIsLoggedIn)
}
func testDisplayingCategories() {
XCTAssertFalse(app.isDisplayingCategories)
app.launch()
app.login()
app.goToCategories()
XCTAssertTrue(app.isDisplayingCategories)
}
🙂 It’s a good idea to avoid “default” cases when switching on Swift enums - it’ll “force you” to update your logic when a new case is added:
enum State {
case loggedIn
case loggedOut
case onboarding
}
func handle(_ state: State) {
switch state {
case .loggedIn:
showMainUI()
case .loggedOut:
showLoginUI()
// Compiler error: Switch must be exhaustive
}
}
💂 It's really cool that you can use Swift's 'guard' statement to exit out of pretty much any scope, not only return from functions:
// You can use the 'guard' statement to...
for string in strings {
// ...continue an iteration
guard shouldProcess(string) else {
continue
}
// ...or break it
guard !shouldBreak(for: string) else {
break
}
// ...or return
guard !shouldReturn(for: string) else {
return
}
// ..or throw an error
guard string.isValid else {
throw StringError.invalid(string)
}
// ...or exit the program
guard !shouldExit(for: string) else {
exit(1)
}
}
❤️ Love how you can pass functions & operators as closures in Swift. For example, it makes the syntax for sorting arrays really nice!
let array = [3, 9, 1, 4, 6, 2]
let sorted = array.sorted(by: <)
🗝 Here's a neat little trick I use to get UserDefault key consistency in Swift (#function expands to the property name in getters/setters). Just remember to write a good suite of tests that'll guard you against bugs when changing property names.
extension UserDefaults {
var onboardingCompleted: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
}
📛 Want to use a name already taken by the standard library for a nested type? No problem - just use Swift.
to disambiguate:
extension Command {
enum Error: Swift.Error {
case missing
case invalid(String)
}
}
📦 Playing around with using Wrap to implement Equatable
for any type, primarily for testing:
protocol AutoEquatable: Equatable {}
extension AutoEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
let lhsData = try! wrap(lhs) as Data
let rhsData = try! wrap(rhs) as Data
return lhsData == rhsData
}
}
📏 One thing that I find really useful in Swift is to use typealiases to reduce the length of method signatures in generic types:
public class PathFinder<Object: PathFinderObject> {
public typealias Map = Object.Map
public typealias Node = Map.Node
public typealias Path = PathFinderPath<Object>
public static func possiblePaths(for object: Object, at rootNode: Node, on map: Map) -> Path.Sequence {
return .init(object: object, rootNode: rootNode, map: map)
}
}
📖 You can reference either the external or internal parameter label when writing Swift docs - and they get parsed the same:
// EITHER:
class Foo {
/**
* - parameter string: A string
*/
func bar(with string: String) {}
}
// OR:
class Foo {
/**
* - parameter with: A string
*/
func bar(with string: String) {}
}
👍 Finding more and more uses for auto closures in Swift. Can enable some pretty nice APIs:
extension Dictionary {
mutating func value(for key: Key, orAdd valueClosure: @autoclosure () -> Value) -> Value {
if let value = self[key] {
return value
}
let value = valueClosure()
self[key] = value
return value
}
}
🚀 I’ve started to become a really big fan of nested types in Swift. Love the additional namespacing it gives you!
public struct Map {
public struct Model {
public let size: Size
public let theme: Theme
public var terrain: [Position : Terrain.Model]
public var units: [Position : Unit.Model]
public var buildings: [Position : Building.Model]
}
public enum Direction {
case up
case right
case down
case left
}
public struct Position {
public var x: Int
public var y: Int
}
public enum Size: String {
case small = "S"
case medium = "M"
case large = "L"
case extraLarge = "XL"
}
}
Author: JohnSundell
Source Code: https://github.com/JohnSundell/SwiftTips
License: MIT license
1595344320
Corona Virus Pandemic has brought the world to a standstill.
Countries are on a major lockdown. Schools, colleges, theatres, gym, clubs, and all other public places are shut down, the country’s economy is suffering, human health is on stake, people are losing their jobs and nobody knows how worse it can get.
Since most of the places are on lockdown, and you are working from home or have enough time to nourish your skills, then you should use this time wisely! We always complain that we want some ‘time’ to learn and upgrade our knowledge but don’t get it due to our ‘busy schedules’. So, now is the time to make a ‘list of skills’ and learn and upgrade your skills at home!
And for the technology-loving people like us, Knoldus Techhub has already helped us a lot in doing it in a short span of time!
If you are still not aware of it, don’t worry as Georgia Byng has well said,
“No time is better than the present”
– Georgia Byng, a British children’s writer, illustrator, actress and film producer.
No matter if you are a developer (be it front-end or back-end) or a data scientist, tester, or a DevOps person, or, a learner who has a keen interest in technology, Knoldus Techhub has brought it all for you under one common roof.
From technologies like Scala, spark, elastic-search to angular, go, machine learning, it has a total of 20 technologies with some recently added ones i.e. DAML, test automation, snowflake, and ionic.
Every technology in Tech-hub has n number of templates. Once you click on any specific technology you’ll be able to see all the templates of that technology. Since these templates are downloadable, you need to provide your email to get the template downloadable link in your mail.
These templates helps you learn the practical implementation of a topic with so much of ease. Using these templates you can learn and kick-start your development in no time.
Apart from your learning, there are some out of the box templates, that can help provide the solution to your business problem that has all the basic dependencies/ implementations already plugged in. Tech hub names these templates as xlr8rs (pronounced as accelerators).
xlr8rs make your development real fast by just adding your core business logic to the template.
If you are looking for a template that’s not available, you can also request a template may be for learning or requesting for a solution to your business problem and tech-hub will connect with you to provide you the solution. Isn’t this helpful 🙂
To keep you updated, the Knoldus tech hub provides you with the information on the most trending technology and the most downloaded templates at present. This you’ll be informed and learn the one that’s most trending.
Since we believe:
“There’s always a scope of improvement“
If you still feel like it isn’t helping you in learning and development, you can provide your feedback in the feedback section in the bottom right corner of the website.
#ai #akka #akka-http #akka-streams #amazon ec2 #angular 6 #angular 9 #angular material #apache flink #apache kafka #apache spark #api testing #artificial intelligence #aws #aws services #big data and fast data #blockchain #css #daml #devops #elasticsearch #flink #functional programming #future #grpc #html #hybrid application development #ionic framework #java #java11 #kubernetes #lagom #microservices #ml # ai and data engineering #mlflow #mlops #mobile development #mongodb #non-blocking #nosql #play #play 2.4.x #play framework #python #react #reactive application #reactive architecture #reactive programming #rust #scala #scalatest #slick #software #spark #spring boot #sql #streaming #tech blogs #testing #user interface (ui) #web #web application #web designing #angular #coronavirus #daml #development #devops #elasticsearch #golang #ionic #java #kafka #knoldus #lagom #learn #machine learning #ml #pandemic #play framework #scala #skills #snowflake #spark streaming #techhub #technology #test automation #time management #upgrade
1622108520
Apache Flink, a 4th generation Big Data processing framework provides robust **stateful stream processing capabilitie**s. So, in a few parts of the blogs, we will learn what is Stateful stream processing. And how we can use Flink to write a stateful streaming application.
In general, stateful stream processing is an application design pattern for processing an unbounded stream of events. Stateful stream processing means a** “State”** is shared between events(stream entities). And therefore past events can influence the way the current events are processed.
Let’s try to understand it with a real-world scenario. Suppose we have a system that is responsible for generating a report. It comprising the total number of vehicles passed from a toll Plaza per hour/day. To achieve it, we will save the count of the vehicles passed from the toll plaza within one hour. That count will be used to accumulate it with the further next hour’s count to find the total number of vehicles passed from toll Plaza within 24 hours. Here we are saving or storing a count and it is nothing but the “State” of the application.
Might be it seems very simple, but in a distributed system it is very hard to achieve stateful stream processing. Stateful stream processing is much more difficult to scale up because we need different workers to share the state. Flink does provide ease of use, high efficiency, and high reliability for the**_ state management_** in a distributed environment.
#apache flink #big data and fast data #flink #streaming #streaming solutions ##apache flink #big data analytics #fast data analytics #flink streaming #stateful streaming #streaming analytics
1648944600
subscrib
_
__ _____| |_ _ __ ___ __ _ _ __ ___
\ \/ / __| __| '__/ _ \/ _` | '_ ` _ \
> <\__ \ |_| | | __/ (_| | | | | | |
/_/\_\___/\__|_| \___|\__,_|_| |_| |_|
Example
import xs from 'xstream'
// Tick every second incremental numbers,
// only pass even numbers, then map them to their square,
// and stop after 5 seconds has passed
var stream = xs.periodic(1000)
.filter(i => i % 2 === 0)
.map(i => i * i)
.endWhen(xs.periodic(5000).take(1))
// So far, the stream is idle.
// As soon as it gets its first listener, it starts executing.
stream.addListener({
next: i => console.log(i),
error: err => console.error(err),
complete: () => console.log('completed'),
})
Installation
npm install xstream
Usage
import xs from 'xstream'
var xs = require('xstream').default
API
addListener
removeListener
subscribe
map
mapTo
filter
take
drop
last
startWith
endWhen
fold
replaceError
flatten
compose
remember
debug
imitate
shamefullySendNext
shamefullySendError
shamefullySendComplete
setDebugListener
To keep the core of xstream small and simple, less frequently-used methods are available under the xstream/extra
directory, and must be imported separately. See EXTRA_DOCS for documentation.
Overview
XStream has four fundamental types: Stream, Listener, Producer, and MemoryStream.
A Stream is an event emitter with multiple Listeners. When an event happens on the Stream, it is broadcast to all its Listeners at the same time.
Streams have methods attached to them called operators, such as map
, filter
, fold
, take
, etc. When called, an operator creates and returns another Stream. Once the first Stream broadcasts an event, the event will pass through the operator logic and the output Stream may perhaps broadcast its own event based on the source one.
You can also trigger an event to happen on a Stream with the shamefullySend*
methods. But you don't want to do that. Really, avoid doing that because it's not the reactive way and you'll be missing the point of this library. Ok?
A Listener is an object with one to three functions attached to it: next
, error
, and complete
. There is usually one function for each type of event a Stream may emit but only next
is always required.
next
events are the typical type, they deliver a value.error
events abort (stop) the execution of the Stream, and happen when something goes wrong in the Stream (or upstream somewhere in the chain of operators)complete
events signal the peaceful stop of the execution of the Stream.This is an example of a typical listener:
var listener = {
next: (value) => {
console.log('The Stream gave me a value: ', value);
},
error: (err) => {
console.error('The Stream gave me an error: ', err);
},
complete: () => {
console.log('The Stream told me it is done.');
},
}
And this is how you would attach that Listener to a Stream:
stream.addListener(listener)
And when you think the Listener is done, you can remove it from the Stream:
stream.removeListener(listener)
A Producer is like a machine that produces events to be broadcast on a Stream.
Events from a Stream must come from somewhere, right? That's why we need Producers. They are objects with two functions attached: start(listener)
and stop()
. Once you call start
with a listener
, the Producer will start generating events and it will send those to the listener. When you call stop()
, the Producer should quit doing its own thing.
Streams are also Listeners (actually they are "InternalListeners", not Listeners, but that's a detail you can ignore), so you can theoretically give a Stream as the listener in producer.start(streamAsListener)
. Then, essentially the Producer is now generating events that will be broadcast on the Stream. Nice, huh? Now a bunch of listeners can be attached to the Stream and they can all get those events originally coming from the Producer. That's why xs.create(producer)
receives a Producer to be the heart of a new Stream. Check this out:
var producer = {
start: function (listener) {
this.id = setInterval(() => listener.next('yo'), 1000)
},
stop: function () {
clearInterval(this.id)
},
id: 0,
}
// This fellow delivers a 'yo' next event every 1 second
var stream = xs.create(producer)
But remember, a Producer has only one listener, but a Stream may have many listeners.
You may wonder "when is start
and stop
called", and that's actually a fairly tricky topic, so let's get back to that soon. First let me tell you about MemoryStreams.
A MemoryStream is just like a Stream: it has operators, it can have listeners attached, you can shamefully send events to it, blabla. But it has one special property: it has memory. It remembers the most recent (but just one) next
event that it sent to its listeners.
Why is that useful? If a new Listener is added after that next
event was sent, the MemoryStream will get its value stored in memory and will send it to the newly attached Listener.
This is important so MemoryStreams can represent values or pieces of state which are relevant even after some time has passed. You don't want to lose those, you want to keep them and send them to Listeners that arrive late, after the event was originally created.
A Stream controls its Producer according to its number of Listeners, using reference counting with a synchronous start
and a cancelable asynchronous stop
. That's how a Stream starts and stops, basically. Usually this part of XStream is not so relevant to remember when building applications, but if you want to understand it for debugging or curiosity, it's explained in plain English below.
When you create a Stream with xs.create(producer)
, the start()
function of the Producer is not yet called. The Stream is still "idle". It has the Producer, but the Producer was not turned on.
Once the first Listener is added to the Stream, the number of Listeners attached suddenly changed from 0
to 1
. That's when the Stream calls start
, because after all there is at least one Listener interested in this Stream.
More Listeners may be added in the future, but they don't affect whether the Producer will continue working or stop. Just the first Listener dictates when the Stream starts.
What matters for stopping the Producer is stream.removeListener
. When the last Listener leaves (or in other words, when the number of Listeners suddenly changes from 1
to 0
), the Stream schedules producer.stop()
to happen on the next event loop. That is, asynchronously. If, however, a new Listener is added (number goes from 0
to 1
) before that scheduled moment, the producer.stop()
will be cancelled, and the Producer will continue generating events for its Stream normally.
The reason the Producer is not suddenly (synchronously) stopped, is that it is often necessary to swap the single listener of a Stream, but still keep its ongoing execution. For instance:
var listenerA = {/* ... */}
var listenerB = {/* ... */}
// number goes from 0 to 1, so the Stream's Producer starts
stream.addListener(listenerA)
// ...
// number goes from 1 to 0, but then immediately goes back
// to 1, because listenerB was added
stream.removeListener(listenerA)
stream.addListener(listenerB)
// Stream's Producer does not stop, everything continues as before
It's still useful to eventually (asynchronously) stop a Stream's internal Producer, because you don't want useless computation lying around producing gibberish. At least I don't.
Factories
Factories are functions that create Streams, such as xs.create()
, xs.periodic()
, etc.
create(producer)
Creates a new Stream given a Producer.
producer: Producer
An optional Producer that dictates how to start, generate events, and stop the Stream.createWithMemory(producer)
Creates a new MemoryStream given a Producer.
producer: Producer
An optional Producer that dictates how to start, generate events, and stop the Stream.never()
Creates a Stream that does nothing when started. It never emits any event.
Marble diagram:
never
-----------------------
empty()
Creates a Stream that immediately emits the "complete" notification when started, and that's it.
Marble diagram:
empty
-|
throw(error)
Creates a Stream that immediately emits an "error" notification with the value you passed as the error
argument when the stream starts, and that's it.
Marble diagram:
throw(X)
-X
error
The error event to emit on the created stream.from(input)
Creates a stream from an Array, Promise, or an Observable.
input: Array|PromiseLike|Observable
The input to make a stream from.of(a, b)
Creates a Stream that immediately emits the arguments that you give to of, then completes.
Marble diagram:
of(1,2,3)
123|
a
The first value you want to emit as an event on the stream.b
The second value you want to emit as an event on the stream. One or more of these values may be given as arguments.fromArray(array)
Converts an array to a stream. The returned stream will emit synchronously all the items in the array, and then complete.
Marble diagram:
fromArray([1,2,3])
123|
array: Array
The array to be converted as a stream.fromPromise(promise)
Converts a promise to a stream. The returned stream will emit the resolved value of the promise, and then complete. However, if the promise is rejected, the stream will emit the corresponding error.
Marble diagram:
fromPromise( ----42 )
-----------------42|
promise: PromiseLike
The promise to be converted as a stream.fromObservable(observable)
Converts an Observable into a Stream.
observable: any
The observable to be converted as a stream.periodic(period)
Creates a stream that periodically emits incremental numbers, every period
milliseconds.
Marble diagram:
periodic(1000)
---0---1---2---3---4---...
period: number
The interval in milliseconds to use as a rate of emission.merge(stream1, stream2)
Blends multiple streams together, emitting events from all of them concurrently.
merge takes multiple streams as arguments, and creates a stream that behaves like each of the argument streams, in parallel.
Marble diagram:
--1----2-----3--------4---
----a-----b----c---d------
merge
--1-a--2--b--3-c---d--4---
stream1: Stream
A stream to merge together with other streams.stream2: Stream
A stream to merge together with other streams. Two or more streams may be given as arguments.combine(stream1, stream2)
Combines multiple input streams together to return a stream whose events are arrays that collect the latest events from each input stream.
combine internally remembers the most recent event from each of the input streams. When any of the input streams emits an event, that event together with all the other saved events are combined into an array. That array will be emitted on the output stream. It's essentially a way of joining together the events from multiple streams.
Marble diagram:
--1----2-----3--------4---
----a-----b-----c--d------
combine
----1a-2a-2b-3b-3c-3d-4d--
stream1: Stream
A stream to combine together with other streams.stream2: Stream
A stream to combine together with other streams. Multiple streams, not just two, may be given as arguments.Methods and Operators
Methods are functions attached to a Stream instance, like stream.addListener()
. Operators are also methods, but return a new Stream, leaving the existing Stream unmodified, except for the fact that it has a child Stream attached as Listener.
addListener(listener)
Adds a Listener to the Stream.
listener: Listener
removeListener(listener)
Removes a Listener from the Stream, assuming the Listener was added to it.
listener: Listener\<T>
subscribe(listener)
Adds a Listener to the Stream returning a Subscription to remove that listener.
listener: Listener
map(project)
Transforms each event from the input Stream through a project
function, to get a Stream that emits those transformed events.
Marble diagram:
--1---3--5-----7------
map(i => i * 10)
--10--30-50----70-----
project: Function
A function of type (t: T) => U
that takes event t
of type T
from the input Stream and produces an event of type U
, to be emitted on the output Stream.mapTo(projectedValue)
It's like map
, but transforms each input event to always the same constant value on the output Stream.
Marble diagram:
--1---3--5-----7-----
mapTo(10)
--10--10-10----10----
projectedValue
A value to emit on the output Stream whenever the input Stream emits any value.filter(passes)
Only allows events that pass the test given by the passes
argument.
Each event from the input stream is given to the passes
function. If the function returns true
, the event is forwarded to the output stream, otherwise it is ignored and not forwarded.
Marble diagram:
--1---2--3-----4-----5---6--7-8--
filter(i => i % 2 === 0)
------2--------4---------6----8--
passes: Function
A function of type (t: T) => boolean
that takes an event from the input stream and checks if it passes, by returning a boolean.take(amount)
Lets the first amount
many events from the input stream pass to the output stream, then makes the output stream complete.
Marble diagram:
--a---b--c----d---e--
take(3)
--a---b--c|
amount: number
How many events to allow from the input stream before completing the output stream.drop(amount)
Ignores the first amount
many events from the input stream, and then after that starts forwarding events from the input stream to the output stream.
Marble diagram:
--a---b--c----d---e--
drop(3)
--------------d---e--
amount: number
How many events to ignore from the input stream before forwarding all events from the input stream to the output stream.last()
When the input stream completes, the output stream will emit the last event emitted by the input stream, and then will also complete.
Marble diagram:
--a---b--c--d----|
last()
-----------------d|
startWith(initial)
Prepends the given initial
value to the sequence of events emitted by the input stream. The returned stream is a MemoryStream, which means it is already remember()
'd.
Marble diagram:
---1---2-----3---
startWith(0)
0--1---2-----3---
initial
The value or event to prepend.endWhen(other)
Uses another stream to determine when to complete the current stream.
When the given other
stream emits an event or completes, the output stream will complete. Before that happens, the output stream will behaves like the input stream.
Marble diagram:
---1---2-----3--4----5----6---
endWhen( --------a--b--| )
---1---2-----3--4--|
other
Some other stream that is used to know when should the output stream of this operator complete.fold(accumulate, seed)
"Folds" the stream onto itself.
Combines events from the past throughout the entire execution of the input stream, allowing you to accumulate them together. It's essentially like Array.prototype.reduce
. The returned stream is a MemoryStream, which means it is already remember()
'd.
The output stream starts by emitting the seed
which you give as argument. Then, when an event happens on the input stream, it is combined with that seed value through the accumulate
function, and the output value is emitted on the output stream. fold
remembers that output value as acc
("accumulator"), and then when a new input event t
happens, acc
will be combined with that to produce the new acc
and so forth.
Marble diagram:
------1-----1--2----1----1------
fold((acc, x) => acc + x, 3)
3-----4-----5--7----8----9------
accumulate: Function
A function of type (acc: R, t: T) => R
that takes the previous accumulated value acc
and the incoming event from the input stream and produces the new accumulated value.seed
The initial accumulated value, of type R
.replaceError(replace)
Replaces an error with another stream.
When (and if) an error happens on the input stream, instead of forwarding that error to the output stream, replaceError will call the replace
function which returns the stream that the output stream will replicate. And, in case that new stream also emits an error, replace
will be called again to get another stream to start replicating.
Marble diagram:
--1---2-----3--4-----X
replaceError( () => --10--| )
--1---2-----3--4--------10--|
replace: Function
A function of type (err) => Stream
that takes the error that occurred on the input stream or on the previous replacement stream and returns a new stream. The output stream will behave like the stream that this function returns.flatten()
Flattens a "stream of streams", handling only one nested stream at a time (no concurrency).
If the input stream is a stream that emits streams, then this operator will return an output stream which is a flat stream: emits regular events. The flattening happens without concurrency. It works like this: when the input stream emits a nested stream, flatten will start imitating that nested one. However, as soon as the next nested stream is emitted on the input stream, flatten will forget the previous nested one it was imitating, and will start imitating the new nested one.
Marble diagram:
--+--------+---------------
\ \
\ ----1----2---3--
--a--b----c----d--------
flatten
-----a--b------1----2---3--
compose(operator)
Passes the input stream to a custom operator, to produce an output stream.
compose is a handy way of using an existing function in a chained style. Instead of writing outStream = f(inStream)
you can write outStream = inStream.compose(f)
.
operator: function
A function that takes a stream as input and returns a stream as well.remember()
Returns an output stream that behaves like the input stream, but also remembers the most recent event that happens on the input stream, so that a newly added listener will immediately receive that memorised event.
debug(labelOrSpy)
Returns an output stream that identically behaves like the input stream, but also runs a spy
function for each event, to help you debug your app.
debug takes a spy
function as argument, and runs that for each event happening on the input stream. If you don't provide the spy
argument, then debug will just console.log
each event. This helps you to understand the flow of events through some operator chain.
Please note that if the output stream has no listeners, then it will not start, which means spy
will never run because no actual event happens in that case.
Marble diagram:
--1----2-----3-----4--
debug
--1----2-----3-----4--
labelOrSpy: function
A string to use as the label when printing debug information on the console, or a 'spy' function that takes an event as argument, and does not need to return anything.imitate(target)
imitate changes this current Stream to emit the same events that the other
given Stream does. This method returns nothing.
This method exists to allow one thing: circular dependency of streams. For instance, let's imagine that for some reason you need to create a circular dependency where stream first$
depends on stream second$
which in turn depends on first$
:
import delay from 'xstream/extra/delay'
var first$ = second$.map(x => x * 10).take(3);
var second$ = first$.map(x => x + 1).startWith(1).compose(delay(100));
However, that is invalid JavaScript, because second$
is undefined on the first line. This is how imitate can help solve it:
import delay from 'xstream/extra/delay'
var secondProxy$ = xs.create();
var first$ = secondProxy$.map(x => x * 10).take(3);
var second$ = first$.map(x => x + 1).startWith(1).compose(delay(100));
secondProxy$.imitate(second$);
We create secondProxy$
before the others, so it can be used in the declaration of first$
. Then, after both first$
and second$
are defined, we hook secondProxy$
with second$
with imitate()
to tell that they are "the same". imitate
will not trigger the start of any stream, it just binds secondProxy$
and second$
together.
The following is an example where imitate()
is important in Cycle.js applications. A parent component contains some child components. A child has an action stream which is given to the parent to define its state:
const childActionProxy$ = xs.create();
const parent = Parent({...sources, childAction$: childActionProxy$});
const childAction$ = parent.state$.map(s => s.child.action$).flatten();
childActionProxy$.imitate(childAction$);
Note, though, that imitate()
does not support MemoryStreams. If we would attempt to imitate a MemoryStream in a circular dependency, we would either get a race condition (where the symptom would be "nothing happens") or an infinite cyclic emission of values. It's useful to think about MemoryStreams as cells in a spreadsheet. It doesn't make any sense to define a spreadsheet cell A1
with a formula that depends on B1
and cell B1
defined with a formula that depends on A1
.
If you find yourself wanting to use imitate()
with a MemoryStream, you should rework your code around imitate()
to use a Stream instead. Look for the stream in the circular dependency that represents an event stream, and that would be a candidate for creating a proxy Stream which then imitates the target Stream.
target: Stream
The other stream to imitate on the current one. Must not be a MemoryStream.shamefullySendNext(value)
Forces the Stream to emit the given value to its listeners.
As the name indicates, if you use this, you are most likely doing something The Wrong Way. Please try to understand the reactive way before using this method. Use it only when you know what you are doing.
value
The "next" value you want to broadcast to all listeners of this Stream.shamefullySendError(error)
Forces the Stream to emit the given error to its listeners.
As the name indicates, if you use this, you are most likely doing something The Wrong Way. Please try to understand the reactive way before using this method. Use it only when you know what you are doing.
error: any
The error you want to broadcast to all the listeners of this Stream.shamefullySendComplete()
Forces the Stream to emit the "completed" event to its listeners.
As the name indicates, if you use this, you are most likely doing something The Wrong Way. Please try to understand the reactive way before using this method. Use it only when you know what you are doing.
setDebugListener(listener)
Adds a "debug" listener to the stream. There can only be one debug listener, that's why this is 'setDebugListener'. To remove the debug listener, just call setDebugListener(null).
A debug listener is like any other listener. The only difference is that a debug listener is "stealthy": its presence/absence does not trigger the start/stop of the stream (or the producer inside the stream). This is useful so you can inspect what is going on without changing the behavior of the program. If you have an idle stream and you add a normal listener to it, the stream will start executing. But if you set a debug listener on an idle stream, it won't start executing (not until the first normal listener is added).
As the name indicates, we don't recommend using this method to build app logic. In fact, in most cases the debug operator works just fine. Only use this one if you know what you're doing.
listener: Listener\<T>
FAQ
Q: Why does imitate()
support a Stream but not a MemoryStream?
A: MemoryStreams are meant for representing "values over time" (your age), while Streams represent simply events (your birthdays). MemoryStreams are usually initialized with a value, and imitate()
is meant for creating circular dependencies of streams. If we would attempt to imitate a MemoryStream in a circular dependency, we would either get a race condition (where the symptom would be "nothing happens") or an infinite cyclic emission of values.
If you find yourself wanting to use imitate()
with a MemoryStream, you should rework your code around imitate()
to use a Stream instead. Look for the stream in the circular dependency that represents an event stream, and that would be a candidate for creating a MimicStream which then imitates the real event stream.
Q: What's the difference between xstream and RxJS?
A: Read this blog post on the topic. In short:
Q: What is the equivalent of withLatestFrom
in xstream?
A: withLatestFrom
is implemented as an extra named sampleCombine
.
Misc.
xstream is built by staltz and TylorS.
Read the CHANGELOG for release notes of all versions of xstream.
Author: Staltz
Source Code: https://github.com/staltz/xstream
License: MIT License