When you think of apps developed in Swift using Xcode, do you think of Linux? Do you think of web apps? Do you think of Microsoft Exchange Servers? 😱 Probably not.
At Jeff Bergier’s work, he runs an app that uses all of these. Plus, it’s all Swift! While moving from the safe world of Darwin and Foundation to the wild west of Linux, you may run into quite a few speedbumps. In this talk from Swift Language User Group, Jeff shares some of these with you so that your journey down this exciting new road will be easy!
Introduction (0:36)
I am a UX designer at a company called Riverbed. We do enterprise, networking hardware stuff that has nothing to do with mobile or iOS or Apple. I learned Objective-C before Swift, and have been teaching myself iOS for about three years.
The App and How it Started (1:20)
My production server Swift app is a conference room scheduler for work. It started as a pure storyboard prototype. Storyboards are a great prototyping tool - you can throw together all your table views, click through them, and easily make videos that you can show so that people understand what you are trying to make without any code.
The storyboard turned into a Swift playground, because everyone at work wanted it to interface with Outlook. With Outlook, you need to use the Exchange API, which is in XML. During a work hackathon, I was able to develop a real version of this app written in Python on a framework called CherryPy.
Because I wasn’t happy with the Python, and because I wanted to learn server-side Swift, I rewrote it in my spare time. There are several server-side Swift frameworks, particularly one developed by IBM.
I did not end up using the IBM framework, even though they had a really nice GUI tool to help with changes to the Linux server. I went with Perfect because it seemed easiest to get up and running.
Why Server-Side Swift? (7:01)
I wanted to learn JavaScript and server-side Swift. Python works okay, but I think Xcode is great because of autocomplete and syntax checks. In server-side Swift, you don’t use interface builder, so that reduces most of your crashes to none.
Type safety is also great in Swift. It controls for the passing of unintended parameters into functions. Plus, Safari’s debugging tools are similar to Xcode’s.
How It’s Built (7:01)
This is a modern web app in that the Javascript loads HTML. The JavaScript sends a post request to the server. The server then sends back a JSON string, and then the JavaScript converts the JSON string into HTML elements and displays them. I used Bootstrap and JavaScript, then I used some cookies to store session information. I also stored some AES credentials in the cookies so that I can decrypt their password later when users return.
Like the backend with CherryPy, most web frameworks work in a similar way, where you have a router concept. I set up one that listens for all or POST requests on slash, and then it goes into my Swift code. Perfect does not have a session manager, so I derive my own that sets the cookies and restores them with the request. Each request that comes in has a JSON payload that the Swift takes apart, determines what step in the process they are in and what data they’ve selected, communicates with the exchange server, and then returns a new JSON string to them.
Forget About the .XcodeProj (11:02)
The framework does not listen to the Xcode project, which is just a nice-to-have so that you can edit the code in Xcode. I don’t even check it into the repo, as it is totally disposable. We use the Swift package manager instead.
For some reason it sets the OS build settings to 10.10, so if you use any of the newer code, you have to tell it to do 10.12. Also, because your web framework is going to need static files that go with it, you have to add a copy files phase, or else the web server can’t find them.
Foundation is Mostly Present (17:13)
I would still default to using Foundation types, even though the Xcode has no good way of telling you if it will work on Linux. Some of the bigger classes like NSURLSession
were not on Perfect when I started, but now they’re there.
This is a little bit of code I wrote to round the dates; you can select to the nearest 15 minutes. You can see that it looks like standard iOS Foundation code.
extension Date {
private static func roundedTimeInterval(from date: Date) -> TimeInterval {
let dc = Calendar.current.dateComponents([.minute, .second], from: date)
let originalMinute = Double(dc.minute ?? 0)
let originalSeconds = Double(dc.second ?? 0)
let roundTo = 15.0
let roundedMinute = round(originalMinute / roundTo) * roundTo
let interval = ((roundedMinute - originalMinute) * 60) - originalSeconds
return interval
}
mutating func roundMinutes() {
let timeInterval = type(of: self).roundedTimeInterval(from: self)
self += timeInterval
}
}
How can you tell if a Foundation type is going work when you are working with Linux? You can start by going to Apple’s GitHub pages on Foundation. If you see things that look like what you see there, that means it is probably working.
public struct DateComponents : ReferenceConvertible, Hashable, Equatable, _MutableBoxing {
public typealias ReferenceType = NSDateComponents
internal var _handle: _MutableHandle<NSDateComponents>
//// Initialize a 'DateComponents', optionally specifying values for its fields.
public init(calendar: Calendar? = nil,
timeZone: TimeZone? = nil,
era: Int? = nil,
...
)
_handle = MutableHandle(adoptingReference: NSDateComponents())
if let _calendar = calendar { self.calendar = _calendar }
if let _timezone = timeZone { self.timeZone = _timeZone }
if let _era = era { self.era = era }
...
}
If you see this, this is a NSURLAuthenticationChallenge
object, and it’s not implemented. That’s a bad sign. These crash at run time too.
open class URLAuthenticationChallenge: NSObject, NSSecureCoding {
static public var supportsSecureCoding: Bool {
return true
}
public required init?(coder aDecoder:NSCoder) {
NSUnimplemented()
}
open func encode(with aCoder: NSCoder) {
NSUnimplented()
}
}
Here is my first attempt at the same algorithm again which seemed to be the much more normal way to do it (I wanted to round something to 15 minutes).
import Foundation
// get date and components
var dc = Calendar.current.dateComponents(
[.year, .month, .day, .hour, .minute, .second, .calendar, .timeZone],
from: Date()
)
// get originals and do rounding
let originalMinute = Double(dc.minute ?? 0)
let roundTo = 15.0
let roundedMinute = Int(round(originalMinute / roundTo) * roundTo)
// modify components
dc.minute = roundedMinute
dc.second = 0
// generate new date
let roundedDate = dc.date! // crashes on linux
// fatal error: copy(with:) is not yet implemented: file Foundation/NSCalendar.swift, line 1434
I set minutes to roundedMinute
and the seconds to 0
, and then I ask them for a new date instead of that previous one where I got the time interval and then added and subtracted it from the original. It turns out that this part crashes on Linux because copyWithZone
is not implemented on NSDate
. You might run into little surprises like that.
Test on Linux Often (20:05)
You never know when you will get an NSUnimplemented
or copyWithZone
not implemented. If you are really concerned, I would suggest setting up some CI stuff for at least every commit. I’ve been using an app called Veertu; it’s sandboxed on the app store, it’s free, and it runs VMs in headlist modes.
You don’t have to see Linux’s horrible UI. It automatically downloads Linux and installs it for you.
Working with JSON is Much Easier (21:21)
You already know how to deal with NSJSONSerialization
. It still comes back as a data.jsonEncodedString
, so that will give you most collections you can call this on. Then every string and several other things have the opposite, so you can convert any string into objects, as long as it is real JSON. There are actually valid areas it throws in the try
statement if you want to deal with that.
import PerfectLib
let data: [String : Any] = [
"date" : "2016-01-01T12-12-00",
"name" : "Billy",
"age" : 22,
"emails" : [
"something@something.com",
"somethingelse@something.com"
]
]
let json = try data.jsonEncodedString()
Random is Hard (21:58)
arc4random_uniform()
is not part of Linux, but I saw some systems that you could install.
#if os(Linux)
import Glibc
#else
import Darwin
#endif
for i in 1 ... 5 {
#if os(Linux)
let randomNumber = random()
#else
let randomNumber = Int(arc4random_uniform(UInt32.max))
#endif
print("Round \(i): \(randomNumber)")
}
This loop generates five random numbers and prints them out. If it is on Linux, it uses Glibc’s random()
function. This random number generator is totally useless.
The easiest way I found to do random numbers is open up access to devrandom and read bytes off of it. It works on Mac and Linux, so you don’t have to do arc4random
separately. There are also a couple of libraries that exist such as Turnstile and Crypto.
Avoid #if os(Linux)
(24:25)
When you do this, you lose all help from Xcode. It can’t do syntax checking, it can’t do the most basic things, and it definitely can’t tell if it will work. Basically, that code in between that block doesn’t exist. I’d recommend avoiding it all costs and find a solution that works on both platforms.
The other thing is if Foundation is letting you down, which sometimes happens on Linux, you treat whatever framework you chose as the new Foundation. There are lots of things for Perfect, and it is very modular.
Threading (26:13)
Threading doesn’t work. You can set your timer and schedule it, and you can fire it manually, and it will fire and then never fire again. I think this just has to do with Perfect; they have their own runloop going that’s separate from what NSTimer is expecting.
Prefect has their own threading framework, and you can create threads yourself. If they are serial or concurrent, you can dispatch work onto them.
Forced Unwrapping is Bad (28:31)
Forced unwrapping is bad. When you force unwrap something on someone’s iOS app, it crashes for the user on the device. When you force unwrap something here on the server, it crashes for everybody.
Q&A (31:24)
Q: What were some of the other constraints when you were calling URIs?
Jeff: I prefer to write my code in a way that it is cleaner, slower, and easier to read by generating more objects instead of keeping them around for longer. I don’t know how good the performance is, and it hasn’t tested under load. In general, some of the Perfect guys did performance comparisons between the various Swift web frameworks and the traditional web frameworks, and the Swift ones seemed to be doing pretty well. But I honestly haven’t tested it with performance.
Q: How do you see server-side Swift in the next three to five years?
Jeff: I think right now it is way too early. I think we are at least a year away from even startup-y companies using it in production, and many years away for bigger companies.
Q: What are the trade-offs of using Swift instead of Python?
Jeff: I find that the autocompletes and stuff in Xcode are an immense help. I find the type checking to be immensely helpful. The way we wrote it in Python was not really object-oriented, just because everyone thought it would be. Python does better work with JSON type data structures.
Receive news and updates from Realm straight to your inbox