It is an exciting time to be a Swift enthusiast. The language is now open source and has been ported to Linux. And if that wasn’t enough, folks at Apple and elsewhere across the globe are also working to port over Foundation and Grand Central Dispatch.
If you’re coming to Swift from a mostly Linux background, you might not be aware of either Foundation or GCD, or why they are important. Simply put, Foundation is a
collection of utility classes designed to provide implementations for frequently used routines and tasks.
We’re not going to look at all of the functionality Foundation provides but will instead look at two classes: NSThread
and NSNotification
. Moreover, the idea behind this post is less about an exhaustive review of the functionality provided by the classes and more about beginning to get our new Swift on Linux users familiarity with Foundation.
NSThread
NSThread is Foundation’s portable thread class. Built upon POSIX threads, NSThread provides you with the ability to create a thread, start it, check its status, etc.
An NSThread
is easy to create, and notice that it takes a block as its init
argument.
1 2 3 4 5 6 7 8 9 10 11 12 |
import Foundation import Glibc let thread = NSThread(){ print("Entering thread") for i in (1...10).reverse() { print("\(i)...", terminator:"") fflush(stdout) sleep(1) } print("Exiting thread") } |
As it stands you can compile the above code, but it won’t do anything because we didn’t start()
our thread. Add thread.start()
at the end and run it. Here’s what we get:
swift threadexample.swift Entering thread 10...
That won’t do! Our application exits before the thread is complete. One way to solve this is to raise a “done” flag when the thread completes. We then go to sleep on the main process until the flag is set.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import Foundation import Glibc var done = false let thread = NSThread(){ print("Entering thread") for i in (1...10).reverse() { print("\(i)...", terminator:"") fflush(stdout) sleep(1) } print("\nExiting thread") done = true } thread.start() while !done { sleep(1) } print("Done") |
Note that the sleep(1)
in our main thread is simply yielding up the CPU so we aren’t creating a busy-wait, but it does have the annoying side effect of not ending execution until after the sleep
returns.
1 2 3 4 |
Entering thread 10...9...8...7...6...5...4...3...2...1... Exiting thread Done |
Even better is removing the done
flag and putting a “main event loop” in, like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import Foundation import Glibc let thread = NSThread(){ print("Entering thread") for i in (1...10).reverse() { print("\(i)...", terminator:"") fflush(stdout) sleep(1) } print("\nExiting thread") print("Done") exit(0) } thread.start() select(0, nil, nil, nil, nil) |
In this case our thread is now responsible for ending the application. Our select
statement is a fake event loop; it never returns but the application continues to run other threads driven by incoming events.
NSNotification
Anyone that has worked with Cocoa and OS X or iOS development has worked with NSNotification
s and NSNotificationCenter
. In many ways a NSNotificationCenter
is like a dbus for your application: objects may post notifications (that is, events), and objects that are interested can subscribe to receive them.
Let’s look at an example; but first, a brief disclaimer. NSNotification
s that are posted can be created with a userInfo
dictionary that contains additional information about the event. Unfortunately there is a bug in the Swift SILgen on Linux that is preventing the proper casts to extract the information, so we’re going to employ a global variable as a workaround. Normally we wouldn’t do that.
Here’s our code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import Foundation import Glibc let INPUT_NOTIFICATION = "InputNotification" let nc = NSNotificationCenter.defaultCenter() var availableData = "" let readThread = NSThread(){ print("Entering thread") let delim:Character = "\n" var input:String = "" while true { let c = Character(UnicodeScalar(UInt32(fgetc(stdin)))) if c == delim { availableData = input nc.postNotificationName(INPUT_NOTIFICATION, object:nil) input = "" } else { input.append(c) } } // Our read thread never exits } nc.addObserverForName(INPUT_NOTIFICATION, object:nil, queue:nil) { (_) in print("Data received: \(availableData)") } readThread.start() select(0, nil, nil, nil, nil) // Forever sleep |
It’s pretty simple.
1. Our nc
constant is the default NSNotificationCenter
for the application. You don’t have to create the default NSNotificationCenter
.
2. readThread
is an NSThread
instance that never exits but sits and reads stdin
. Whenever the newline character is encountered it saves the “buffered” characters (stored in input
) to availableData
and then posts an INPUT_NOTIFICATION
via the default NSNotificationCenter
.
3. Before starting our readThread
we added an observer for the INPUT_NOTIFICATION
. The addObserverForName
function takes a block which executes when the notification is broadcasted.
4. We start our thread with the NSThread
start
method.
5. Again, we employ a blocking select
to ensure the Linux process doesn’t end (which would unceremoniously kill our thread).
NSNotification on OS X
As a reference of how we should be using userInfo
with NSNotification
, here is some Swift code for OS X that eventually we’d like to see working on Linux:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import Foundation import Darwin let INPUT_NOTIFICATION = "InputNotification" let nc = NSNotificationCenter.defaultCenter() class Reader : NSObject { var readThread:NSThread? func start() { self.readThread = NSThread(target:self, selector:"readStdin", object:nil) self.readThread!.start() } func readStdin() { print("Entering thread") let delim:Character = "\n" var input:String = "" while true { let c = Character(UnicodeScalar(UInt32(fgetc(stdin)))) if c == delim { nc.postNotificationName(INPUT_NOTIFICATION, object:nil, userInfo:["availableData":input]) input = "" } else { input.append(c) } } // Our read thread never exits } } nc.addObserverForName(INPUT_NOTIFICATION, object:nil, queue:nil) { (notification) in guard let userInfo = notification.userInfo as? [String:String] else { return } guard let availableData = userInfo["availableData"] else { return } print("Data received: \(availableData)") } let reader = Reader() reader.start() select(0, nil, nil, nil, nil) // Forever sleep |
Closing Comments
Swift on Linux is very new; as of this writing, less than a month old. Yet with each passing day additional functionality is being added to the Foundation classes, folks are working to port Swift to ARM platforms such as Raspberry Pi and BeagleBone, and the language is evolving towards Swift 3. Our personal belief is that Swift has a great shot at being competitive with NodeJS, Python, Ruby, and Java and C++ for application development on Linux platforms. Hopefully this post and those to come will help application developers explore the possibilities Swift brings.
Have you tried this:
guard let availableData = notification.userInfo[“availableData”] as? String else {
return
}
(I have at the moment no Swift on Unix)