SlideShare a Scribd company logo
Thinking in Swift: Lessons
Learned from Converting a
Legacy Robotics
Project To Swift
Janie Clayton
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
Robots in Swift
Microplotter II
Robots in Swift
-[NSAlert alertWithError:] called with nil NSError.
A generic error message will be displayed,
but the user deserves better.
Most Common Issues
Silent nil messaging failures
“Faith-based casting”
Unexpected object coupling
Error handling problems
Nil: The Silent Killer
The $10,000 Bug
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.
Nil Messages!!
Nil-Messaging Accessors
Inheriting from NSObject: nil
Int: 0
Float: 0.0
Double: 0.0
Robots in Swift
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
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
Property List Serialization
NSUserDefaults
Serialize data to make it easily storable
Cocoa way: Use NSCoder to convert types
to NSData to be stored and extracted later.
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.
Robots in Swift
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
}
}
protocol PropertyListReadable
{
func propertyListRepresentation() -> NSDictionary
init?(propertyListRepresentation:NSDictionary?)
}
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
func
saveValuesToDefault<T:PropertyListReadable>
(newValues:[T], key:String)
{
let encodedValues = newValues
.map{$0.propertyListRepresentation()}
NSUserDefaults.standardUserDefaults()
.setObject(encodedValues, forKey:key)
}
[“savedPositions”:
[[firstCoordinateNSDictionary],
[secondCoordinateNSDictionary],
[thirdCoordinateNSDictionary]]
]
func
extractValuesFromPropertyListArray<PropertyListReadable>
(propertyListArray:[AnyObject]?) -> [T]
{
guard let encodedArray = propertyListArray as?
[NSDictionary] else {return []}
return encodedArray
.map{T(propertyListRepresentation:$0)}
.filter{ $0 != nil }
.map{ $0! }
}
encodedArray
.map{T(propertyListRepresentation:$0)}
.filter{ $0 != nil }
.map{ $0! }
encodedArray
.flatMap{T(propertyListRepresentation:$0)}
guard let encodedArray = propertyListArray as?
[NSDictionary] else {return []}
guard let encodedArray = propertyListArray
else {return []}
func
extractValuesFromPropertyListArray<PropertyListReadable>
(propertyListArray:[AnyObject]?) -> [T]
{
guard let encodedArray = propertyListArray else
{return []}
return encodedArray.map{$0 as? NSDictionary}
.flatMap{T(propertyListRepresentation:$0)}
}
Reducing Mutability
Robots in Swift
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!
Robots in Swift
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
Values Types vs
Reference Types
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
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.
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
Error Handling
Robots in Swift
Where is it most important to make
sure you have error handling?
Interacting with the file system
Interacting with hardware
Communication over a network
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.
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
- (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;
}
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
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)
}
}
}
enum RoboticsError {
case MismatchedPosition
case HitObstruction
case NotConnected
}
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)
}
}
}
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
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
Robots in Swift
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
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
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
}
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
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
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.
Links and Questions
http://www.sunsetlakesoftware.com/2014/12/02/why-were-
rewriting-our-robotics-software-swift
http://redqueencoder.com/property-lists-and-user-defaults-in-swift/
http://www.sunsetlakesoftware.com/2015/06/12/swift-2-error-
handling-practice
https://github.com/LlamaKit/LlamaKit
http://edgecasesshow.com/007-if-you-look-at-the-error-you-will-
crash.html

More Related Content

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
  • 6. -[NSAlert alertWithError:] called with nil NSError. A generic error message will be displayed, but the user deserves better.
  • 7. Most Common Issues Silent nil messaging failures “Faith-based casting” Unexpected object coupling Error handling problems
  • 8. Nil: The Silent Killer The $10,000 Bug
  • 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.
  • 11. Nil-Messaging Accessors Inheriting from NSObject: nil Int: 0 Float: 0.0 Double: 0.0
  • 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
  • 15. Property List Serialization NSUserDefaults Serialize data to make it easily storable Cocoa way: Use NSCoder to convert types to NSData to be stored and extracted later.
  • 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 } }
  • 19. protocol PropertyListReadable { func propertyListRepresentation() -> NSDictionary init?(propertyListRepresentation:NSDictionary?) }
  • 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
  • 21. func saveValuesToDefault<T:PropertyListReadable> (newValues:[T], key:String) { let encodedValues = newValues .map{$0.propertyListRepresentation()} NSUserDefaults.standardUserDefaults() .setObject(encodedValues, forKey:key) }
  • 23. func extractValuesFromPropertyListArray<PropertyListReadable> (propertyListArray:[AnyObject]?) -> [T] { guard let encodedArray = propertyListArray as? [NSDictionary] else {return []} return encodedArray .map{T(propertyListRepresentation:$0)} .filter{ $0 != nil } .map{ $0! } }
  • 26. guard let encodedArray = propertyListArray as? [NSDictionary] else {return []}
  • 27. guard let encodedArray = propertyListArray else {return []}
  • 28. func extractValuesFromPropertyListArray<PropertyListReadable> (propertyListArray:[AnyObject]?) -> [T] { guard let encodedArray = propertyListArray else {return []} return encodedArray.map{$0 as? NSDictionary} .flatMap{T(propertyListRepresentation:$0)} }
  • 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) } } }
  • 46. enum RoboticsError { case MismatchedPosition case HitObstruction case NotConnected }
  • 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.