Swigo
Go concurrency primitives for Swift.
let msg = Chan<String>(buffer: 3)
let done = Chan<Bool>()
msg <- "Swift"
msg <- "❤️"
msg <- "Go"
msg.close()
go {
for message in msg {
print(message)
}
done <- true
}
<-done
|
msg := make(chan string, 3)
done := make(chan bool)
msg <- "Swift"
msg <- "❤️"
msg <- "Go"
close(msg)
go func() {
for message := range msg {
fmt.Println(message)
}
done <- true
}()
<-done
|
About
This repo is an experimental library to bring go style concurrency primitives to Swift. The objective is to bring nearly 1:1 API and behavior support to Swift. Do not expect comparabile performance or reliability. Swift does not have a runtime similar to go, and thus “goroutines” are just OS threads managed by GCD’s global queue.
Supported on:
- macOS
- iOS
- Linux
Usage
- Add
https://github.com/gh123man/Swigo
as a Swift package dependency to your project. import Swigo
and go!
Documentation & Examples
Range over chan
In Swift, Chan
implements the Sequence
and IteratorProtocol
protocols. So you can enumerate a channel until it’s closed.
let msg = Chan<String>()
let done = Chan<Bool>()
go {
for m in msg {
print(m)
}
print("closed")
done <- true
}
msg <- "hi"
msg.close()
<-done
|
msg := make(chan string)
done := make(chan bool)
go func() {
for m := range msg {
fmt.Println(m)
}
fmt.Println("closed")
done <- true
}()
msg <- "hi"
close(msg)
<-done
|
Buffered Channels
Channels in Swift can be buffered or unbuffered
let count = Chan<Int>(buffer: 100)
for i in (0..<100) {
count <- i
}
count.close()
let sum = count.reduce(0) { sum, next in
sum + next
}
print(sum)
|
count := make(chan int, 100)
for i := 0; i < 100; i++ {
count <- i
}
close(count)
sum := 0
for v := range count {
sum += v
}
fmt.Println(sum)
|
Also map
, reduce
, etc work on channels in Swift too thanks to Sequence
!
Select
Swift has reserve words for case
and default
and the operator support is not flexible enough to support inline channel operations in the select statement. So instead they are implemented as follows:
|
|
|
|
|
|
|
|
Gotcha: You cannot return
from none
to break an outer loop in Swift since it’s inside a closure. To break a loop surrounding a select
, you must explicitly set some control variable (ex: while !done
and done = true
)
Examples
Example | ||
---|---|---|
|
let a = Chan<String>(buffer: 1)
a <- "foo"
select {
rx(a) { av in
print(av!)
}
none {
print("Not called")
}
}
|
a := make(chan string, 1)
a <- "foo"
select {
case av := <-a:
fmt.Println(av)
default:
fmt.Println("Not called")
}
|
|
let a = Chan<String>(buffer: 1)
select {
tx(a, "foo")
none {
print("Not called")
}
}
print(<-a)
|
a := make(chan string, 1)
select {
case a <- "foo":
default:
fmt.Println("Not called")
}
fmt.Println(<-a)
|
|
let a = Chan<Bool>()
select {
rx(a)
none {
print("Default case!")
}
}
|
a := make(chan bool)
select {
case <-a:
default:
fmt.Println("Default case!")
}
|
Closing Channels
A Chan
can be closed. In Swift, the channel receive (<-
) operator returns T?
because a channel read will return nil
when the channel is closed. If you try to write to a closed channel, a fatalError
will be thrown.
It’s worth noting that this is somewhat different than how go deals with receiving on a closed channel. When a channel is closed you you need 1. a way to detect that the channel is closed and 2. something to return in place of the expected value. Swift does not have the notion of zero values so we need an alternate solution. Returning T?
allows us to do both.
Because of Swift’s optional semantics and strict type system, it is not always convenient to have to unwrap an optional every time you read a channel. To solve this you can use OpenChan
.
Alternatively you can simply unwrap the channel read:
let a = Chan<String>(buffer: 1)
a <- "hi"
if let val = <-a {
print(val)
}
OpenChan
Unlike Chan
, OpenChan
cannot be closed – it is always open. As a result <-
will return a non-optional T
. This has some other side effects however:
- If reading using the
Sequence
protocol,next() -> T?
will never returnnil
and thus your loop will never terminate (without an explicit break or return). - There is no way to break a blocking channel read without writing to the channel.
Usage
let c = OpenChan<String>(buffer: 1)
c <- "hi"
let result: String = <-c // Not an optional