Robots in Swift
- 1. Thinking in Swift: Lessons
Learned from Converting a
Legacy Robotics
Project To Swift
Janie Clayton
- 2. About Me
• Software Developer at Black
Pixel
• Team Lead of the Swift Team
for RayWenderlich.com
• Coauthor of “iOS 8 SDK
Development” and “The Swift
Apprentice”
• @RedQueenCoder
• http://redqueencoder.com
- 9. Silent Failure: Feature or
Bug?
Dereferencing null pointers causes crashes in C and C++.
Null pointers also cause random behavior and memory
corruption.
Rather than crash if there is nothing at the pointer,
Objective-C sends messages to nil, which effectively does
nothing
Does not warn or alert you that the message isn’t being
received by anything.
- 13. Why Required Values are a
Good Thing
In Objective-C, optional values are the default rather
than something you choose to opt into.
Compiler enforced rules force you to ensure your
values are compliant or else it won’t let you build.
These compiler enforced rules catch bugs before they
are released out into the field.
Compile-time prevention > unit test catches > crashes
in QA testing > bugs in the field
- 14. Type Safety in Swift
Prevents “faith-based casting”
What does an NSArray take in and give out?
NSNotifications?
Compiler-defined interfaces
Like putting together pieces in a puzzle
Example: fixed-length arrays using tuples
- 16. Issues with NSCoding
Weak type safety, with potential security and stability
issues
Only works on descendants of NSObject, which does
not include Swift structs or enums
NSData is essentially “Grey Goo” in that it is a blob of
data that is not human readable.
- 18. struct Coordinate {
let x:Double
let y:Double
let z:Double
init(_ x: Double, _ y:Double, _ z:Double)
{
self.x = x
self.y = y
self.z = z
}
}
- 20. extension Coordinate:PropertyListReadable {
func propertyListRepresentation() -> NSDictionary
{
let representation:[String:AnyObject] =
[“x”:self.x, “y”:self.y, “z”:self.z]
return representation
}
init?(propertyListRepresentation:NSDictionary?) {
guard let values = propertyListRepresentation
else return {nil}
if let xCoordinate = values[“x”] as? Double,
yCoordinate = values[“y”] as? Double,
zCoordinate = values[“z”] as? Double {
self.x = xCoordinate
self.y = yCoordinate
self.z = zCoordinate
} else {
return nil
}
}
1.1
2.0
1.2
- 31. Why Reducing Mutability is
Good
If a lot of objects look at and can write to the same
object, one can change it underneath another one
Our robotics might expect a value to be one thing, but
another piece of code might change it and confuse the
robotics, which can cause physical damage that is $$$
Creates code coupling, aka Spaghetti Code. Huzzah!
- 33. Untestable Code
Code coupling made our code untestable because no
one piece could be isolated from any other piece
The code base became unstable and bugs took longer
and longer to find and to fix
It also became more difficult to add new features to the
code because the code would break
Taking the lessons learned from maintaining a 7-year-
old project allowed us to re-architect the software in a
more stable manner
- 35. Objects and Objective-C
Objects were the only way to encapsulate data and the
code that acted on the data in Objective-C
The Cocoa Frameworks were designed around the
idea that everything would be an object
All Objective-C objects were passed by reference
- 36. Swift Structs and Enums
Allowed other ways of encapsulating data besides just
classes.
Far more powerful and feature rich than C structs and
enums
Allows you to differentiate between passing values and
passing references.
- 37. When to use a class?
Use a class when you want shared references
Use a class if you need traditional inheritance
Use and enum if you need to represent distinct
options
Use a struct for all other structured values
- 40. Where is it most important to make
sure you have error handling?
Interacting with the file system
Interacting with hardware
Communication over a network
- 41. Why Error Handling is
Important
You do have complete control over your application.
You don’t have complete control over the outside
world.
You have to deal with error handling when you are
interacting with the outside world and it can fail in weird
ways.
- 42. Problems with Objective-C
Error Handling
A method / function can only return one value
✨ Magic constants ✨
Error scribbling and code crashes
http://edgecasesshow.com/007-if-you-look-at-the-
error-you-will-crash.html
Verbose and easy to forget to handle all the failure
states
- 43. - (Coordinate *)moveToCoordinate:(Coordinate *)targetCoordinate error:(NSError **)error
{
if (targetCoordinate == nil){
if (error != nil){
*error = [self errorForRoboticsErrorCode:BADCOORDINATE];
}
return nil;
}
if(![self moveUp error:error]){
return nil;
}
if(![self moveOverCoordinate:targetCoordinate error:error]){
return nil;
}
Coordinate *newPosition = [self readCurrentCoordinate:error];
if(newPosition == nil){
return nil;
}
if(!newPosition.isEqualTo(targetCoordinate)){
if (error != NULL){
*error = [self errorForRoboticsErrorCode:MISMATCHEDPOSITIONS]
}
return nil;
}
return newPosition;
}
- 44. Swift 1.0 and Result Type
Uses generics and enums with associated values to create a
reusable return type
Two cases: Success(value) and Failure(error)
Forces you to handle the errors if you want the success value
No need for magic constants or inputs as outputs
Allows us use error types other than NSError, like custom
Swift enums
- 45. public enum Result<T, U>{
case Success<T>
case Failure<U>
func then<V>(nextOperation:T -> Result<V, U>)
-> Result<V, U> {
switch self {
case let .Failure(error):
return .Failure(error)
case let .Success(value):
return nextOperation(value)
}
}
}
- 47. func moveToCoordinate(targetCoordinate:Coordinate) ->
Result<Coordinate, RoboticsError>
{
return self.moveUp()
.then{self.moveOverCoordinate(targetCoordinate)}
.then{self.moveDownCoordinate(targetCoordinate)}
.then{self.readCurrentCoordinate()}
.then{coordinate -> Result<Coordinate,
RoboticsError> in
if (coordinate != targetCoordinate) {
return .Failure(.MismatchedPosition)
} else {
return .Success(coordinate)
}
}
}
- 48. Advantages of Result Error
Handling
Compiler-enforced error handling for return values
Uses less code (go from 38 lines of code to 12)
Inputs are inputs, outputs are outputs
More human readable by removing boilerplate code
- 49. Issues with Result Error
Handling
Odd code flow with return statement at top
Really long chained statements can choke the compiler
Have to enclose non-Result-returning functions in
awkward closures
Easy to ignore errors from functions that don’t return
values
- 51. Swift 2.0 Error Handling
Introduced a try/catch error handling model
At a glance, very different from Result<T,U>, but in
practice very similar
Not at all NSException
Compiler forces you to handle or rethrow every error,
ensuring proper error handling
- 52. Do, Try, Catch, Throw, and
Throws
If you have a method that can provide an error, you
specify that it “throws” in the signature
To call an error throwing function, you must call it with
“try”
First “try” line that fails kicks out of the function / method
To catch the error, you use a “do/catch” block
To bubble the error up, “throw” the error
- 53. func moveToCoordinate(targetCoordinate: Coordinate)
throws -> Coordinate
{
try self.moveUp()
try self.moveOverCoordinate(targetCoordinate)
try self.moveDownToCoordinate(targetCoordinate)
let coordinate = try self.readCurrentCoordinate()
if(coordinate != targetCoordinate) {
throw .MismatchedPosition
}
return coordinate
}
- 54. Advantages over Result<T,U>
Even fewer lines of code (9 vs. 12)
More natural execution flow (return at end)
Fewer braces, no more artificial closures
Compiler-enforced handling of errors, even for
functions with no return values
- 55. Swift Wins
Our current code base is 75% smaller than the legacy
code base
Removed whole classes of bugs so that they can’t
even happen
Identified subtle bugs that would have taken weeks to
track down and have caused thousands of dollars in
damages
We can now implement new features that weren’t
possible before because the code was too fragile
- 56. Swift Wins
We now have unit tests that we could not implement in
the old code because of mutability and code coupling.
We are able to do sophisticated unit testing using fake
robots and serial ports in code that don’t require us to
connect to hardware.