ted.neward@newardassociates.com | Blog: http://blogs.newardassociates.com | Github: tedneward | LinkedIn: tedneward
Code: https://github.com/tedneward/Demo-Swift
Slides: http://www.newardassociates.com/presentations/BusyDevsGuide/Swift.html
This will not make you a Swift expert
... and this is not a comprehensive reference
In many ways, this is designed to "make you dangerous"
... and give you a place to start your own research/experiments
NOT an intro to iOS or OSX programming/APIs
Swift will NOT on its own make you a mobile developer, sorry!
A new language from Apple
1.0 shipped with XCode 6
2.0 shipped with XCode 7
2.1 shipped with XCode 7.1
2.2 shipped with XCode 7.2
2.3, 3.0 shipped with XCode 8
3.2, 4.0 shipped with XCode 9
... and so on
A language incorporating "modern" language features
An attempt to make iOS/OSX programming easier
and allow us/Apple to sunset Obj-C
Imperative syntax
Curly braces for blocks
(Optional) Semicolon statement terminators
Class-oriented in the spirit of C++, C#, Java, etc
Classes, methods, fields, properties, etc
Automatic reference counting (ARC) memory management
Encourages immutability in state
Functional primitives
Missing some core functional concepts, though
function currying
partial application
http://developer.apple.com
Language Guide-- https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html
Language Reference-- https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AboutTheLanguageReference.html
https://swift.org/documentation/
XCode is macOS-only (not built for any other platform)
Easiest way is through AppStore
This saves having to type in admin/sudo passwords over and over
Necessary for a lot of development-related tasks
iOS applications
macOS applications
swift file.swift: compile-on-demand/execute
swift repl: fire up a REPL (read-evaluate-print-loop)
swiftc file.swift: command-line compiler, produces file executable
sudo apt-get clang
However, this is just the language
no iOS build capability (or library reference)
no macOS build capability (or library reference)
... but it can reference local native libraries
Swift code starts at the top of the file
"main.swift" or "main.playground"
Playgrounds only work inside XCode
Essentially a Markdown file with special support in XCode
Hello, Swift
import Foundation
print("Hello, World!")
// single-line comments
/* */ multi-line comments
they nest appropriately, too
Entirely optional
and, for the most part, idiomatically excluded
Swift supports all the major types
Bool
Int(8, 16, 32, 64), UInt(8, 16, 32, 64)
Int, UInt: size inferred by platform
Float, Double
Character, String
... as well as user-defined types
classes, structs, enums, etc
+ - * / (but not %)
+= -= *= /= (but not %= ++ or --)
and so on
Swift has two kind of locals
Constants (values): let
Variables: var
Limited type inference
Pascal-style optional type syntax
also known as "type annotations"
Locals are never type-coerced
convert using "type(value)"
Use _ for name for anonymous local
usually to avoid over-zealous compiler warnings
Locals
let money = 42000
// let moneyD : Double = money // Illegal
let moneyD2 : Double = Double(money)
var moneyD = 42000
moneyD = 50000
Uses a () syntax
Any expression inside the interpolation
String interpolation
let age = 42 let message = "He is \(age) years old"
Tests a boolean condition, takes the branch if true
Can be followed by one or more else-if or else clauses
Pretty much identical to any other C-based language
If
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
}
else if temperatureInFahrenheit > 80 {
print("Holy crud! It's hot!")
}
else {
print("Quit yer whinin'.")
}
Tests a condition, takes the "else" branch if false
Must always have an "else" clause--- making it a stricter "if" conditional
Useful to "guard" against invalid conditions--- clearly documents preconditions
Guard
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)!")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
greet(person: ["name": "John"])
greet(person: ["name": "Jane", "location": "Cupertino"])
Multi-value branching
Mostly identical to other C-languages, but...
No default fallthrough
use the "fallthrough" keyword to make it fall through
Switch MUST be exhaustive (all cases covered)
Switch supports "range matching"
Switch can match tuples (more on that later)
Switch can bind data to local declarations
Switch can have "where" clauses
Switch: Simple case
let temp = 30
switch temp {
case 0:
print("Brr! Freezing!")
case 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18:
print("Still damn cold!")
default:
print("Meh")
}
Switch: Range cases
switch temp {
case 0:
print("Brr! Freezing!")
case 1...32:
print("Still damn cold!")
default:
print("Meh")
}
Switch: Local bindings
let aPoint = (2, 0)
switch aPoint {
case (let x, 0):
print("on the x-axis with an x value of \(x)")
case (0, let y):
print("on the y-axis with a y value of \(y)")
case let (x, y):
print("somewhere else at (\(x), \(y))")
}
Switch: Where clauses
let anotherPoint = (1, -1)
switch anotherPoint {
case let (x, y) where x == y:
print("on the identity slope")
case let (x, y) where x == -y:
print("on the negative identity slope")
case let (x, y):
print("somewhere else at (\(x), \(y))")
}
Two forms: while and repeat-while
Pretty much identical to any other C-based language
While
var index : Int = 0
while index < 5 {
print("This is the \(index)th time I've said this")
index += 1
}
Repeat-While
var anotherIndex : Int = 0
repeat {
print("This is the \(index)th time I've said this")
anotherIndex += 1
} while anotherIndex < 5
Iteration over loops/collections/etc
Several different flavors of for
for-in, using range operators
for-conditional-increment removed from Swift 3.0
For-In
for index in 1...5 {
print("\(index) times 5 equals \(index * 5)")
}
for _ in 1...5 {
print("I don't care about the index; just print 5 times")
}
common to use tuples to hand back (Bool, AnyObject) pairs
first is the success of the operation
second is the result (error or otherwise)
Swift also supports structured exception-handling
methods can throw exception objects
exceptions propagate up the stack until caught
define an enum type inheriting from ErrorType
remember, enums can carry additional data
suffix the func with the "throws" keyword
methods cannot throw without this
any methods that can throw must be invoked with "try"
use the throw keyword to throw the exception
Imagine we are building a vending machine...
struct Item {
var price: Int
var count: Int
}
class VendingMachine {
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func dispenseSnack(snack: String) {
print("Dispensing \(snack)")
}
Define an error type
enum VendingMachineError: Error {
case InvalidSelection(desired: String)
case InsufficientFunds(coinsNeeded: Int)
case OutOfStock
}
Declare throws
func vend(itemNamed name: String) throws -> String {
Throw the exception
guard var item = inventory[name] else {
throw VendingMachineError.InvalidSelection(desired: name)
}
guard item.count > 0 else {
throw VendingMachineError.OutOfStock
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
coinsDeposited -= item.price
item.count -= 1
inventory[name] = item
dispenseSnack(snack: name)
return name
"do" sets up a guarded block
throwing functions must be invoked using "try"
any exceptions thrown in the block will be evaluated
if no exceptions occur, "catch" blocks are bypassed
if an exception occurs, "catch" blocks are evaluated
first "catch" match wins
only the first matching block is executed
simplest match is a case match
catch blocks can also do match clauses ("where")
unmarked catch block is the catch-all
Catching exceptions
let vm = VendingMachine()
do {
vm.coinsDeposited = 12
try vm.vend(itemNamed: "Diet Coke")
}
catch VendingMachineError.OutOfStock {
print("I don't have any of that")
}
catch VendingMachineError.InsufficientFunds(let coinsReq) {
print("You need more money: \(coinsReq), to be precise")
}
catch VendingMachineError.InvalidSelection(let desired)
where desired == "Diet Coke" {
print("Sorry, we're a Pepsi place")
}
catch VendingMachineError.InvalidSelection(_) {
print("We don't carry that")
}
catch {
print("We really have no idea what went wrong")
}
suuuuuuuuuure you do
suuuuuuuuuure you do
"try?" will convert the exception to an optional
"try!" will convert the exception to a runtime error
Declarative try
vm.coinsDeposited = 10
let chips = try? vm.vend(itemNamed: "Chips")
print(chips)
let coke = try? vm.vend(itemNamed: "Coke")
print(coke)
let drPepper = try! vm.vend(itemNamed: "Dr Pepper")
// BOOM: "fatal error: 'try!' expression unexpectedly ...
First-class citizens
Lacking some functional-language features...
... but still better than what we get from traditional C/C++
func kicks off a function definition
func name (param : type, ...) -> returnType { ... }
0 to n parameters
Type must always be specified
Body defined by { } pair
Return type optional, defaults to Void
Use "return" to indicate return value of function
Invocation uses parens and labels to delimit call parameters
Functions
func sayHello(personName : String) -> String {
return "Hello, \(personName)"
}
print(sayHello(personName: "Fred"))
Functions : A little more complex
func rollDice(numberOfDice : UInt, times : UInt, withBonus : UInt) -> [UInt] {
var results : [UInt] = []
for _ in 1...times {
var accum : UInt = 0
for _ in 1...numberOfDice {
let dieRoll = UInt(arc4random_uniform(6) + 1)
accum += dieRoll
}
results.append (accum + withBonus)
}
return results
}
let dieRolls = rollDice(numberOfDice: 3, times: 6, withBonus: 0)
print(dieRolls)
Sometimes the parameter name makes sense outside, but not inside
... or vice versa
Declare the external parameter name before the name:type
Callers must now include the name as part of the call
Order of the parameters does not change, however
External parameter names
func rollDice(numberOfDice num : UInt, times t : UInt, withBonus bonus : UInt) -> [UInt] {
var results : [UInt] = []
for _ in 1...t {
var accum : UInt = 0
for _ in 1...num {
let dieRoll = UInt(arc4random_uniform(6) + 1)
accum += dieRoll
}
results.append (accum + bonus)
}
return results
}
let dieRolls = rollDice(numberOfDice: 3, times: 6, withBonus: 0)
print(dieRolls)
Other functions find the parameter name to be redundant
print(), for example
Define "_" as the external parameter name
External parameter names
func rollDice(_ num : UInt, times t : UInt, withBonus bonus : UInt) -> [UInt] {
var results : [UInt] = []
for _ in 1...t {
var accum : UInt = 0
for _ in 1...num {
let dieRoll = UInt(arc4random_uniform(6) + 1)
accum += dieRoll
}
results.append (accum + bonus)
}
return results
}
let dieRolls = rollDice(3, times: 6, withBonus: 0)
print(dieRolls)
Sometimes a certain value makes sense as a default for a parameter
Provide a "= value" after the type in the parameter list
Defaulted parameters are assumed to be externally-named
Default parameters
func rollDice(numberOfDice : UInt, times : UInt, withBonus : UInt = 0) -> [UInt] {
var results : [UInt] = []
for _ in 1...times {
var accum : UInt = 0
for _ in 1...numberOfDice {
let dieRoll = UInt(arc4random_uniform(6) + 1)
accum += dieRoll
}
results.append (accum + withBonus)
}
return results
}
let dieRolls = rollDice(numberOfDice: 3, times: 6)
print(dieRolls)
Pass a 0-n collection of parameters to a function
All must be of the same type
Function receives an array of parameters
Function can only have one variadic parameter
Variadic parameter must be last in the list
Variadic parameters
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
print(arithmeticMean(1, 2, 3, 4, 5))
Normally, all functions pass parameters by value
If you specifically want pass-by-reference, use "inout"
Function is defined at the point of usage
Typically only used for one-off scenarios
Syntax is similar to function declaration without "func"
Anonymous functions
let addFunc = { (left : Int, right : Int) -> Int in return left + right }
print("1 + 2 = \(addFunc(1,2))")
Inferred parameter types
let addFunc : (Int, Int) -> Int = { (left, right) in return left + right }
print("1 + 2 = \(addFunc(1,2))")
Inferred return value
let addFunc : (Int, Int) -> Int = { (left, right) in left + right }
print("1 + 2 = \(addFunc(1,2))")
Inferred parameter names
let addFunc : (Int, Int) -> Int = { $0 + $1 }
print("1 + 2 = \(addFunc(1,2))")
Function types look like (p1, p2, p3) -> r
Functions can be passed to other functions
Locals can be declared to hold function references
Function values can be returned from functions
Function values
func mathOp(left : Int, right : Int, op : (Int, Int) -> Int) -> Int {
return op(left, right)
}
Define functions inside of functions
Full access to outer scope locals and parameters
Encapsulates nested function entirely inside outer function scope
Nested Functions
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
var currentValue = -2
let moveNearerToZero = chooseStepFunction(backwards: currentValue > 0)
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -2...
// -1...
// zero!
If a closure is the last parameter to a function, it is a trailing closure
We can define the closure outside the parameter list as a block
Trailing closures
func mathOp(left : Int, right : Int, op : (Int, Int) -> Int) -> Int {
return op(left, right)
}
let result = mathOp(left: 1, right: 2) { $0 + $1 }
print("Result = \(result)")
custom operations for custom types
defined as global functions or class (static) methods
standard operators can be overridden
... or new operators created
Equality/Inequality operator implementation
struct Point {
// standard properties
var x : Int32
var y : Int32
init(x:Int32, y:Int32) {
self.x = x
self.y = y
}
static func ==(left: Point, right: Point) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
static func !=(left: Point, right: Point) -> Bool {
return (left.x != right.x) || (left.y != right.y)
}
}
let origin = Point(x: 0, y: 0)
let anotherPt = Point(x: 1, y: 1)
if origin != anotherPt {
print("Nope! Different places on the grid")
}
if origin == anotherPt {
print("They point to the same place!")
}
Note: If you do this, you should conform to the Equatable protocol
Simple operator overloading
func +(left: [Double], right: [Double]) -> [Double] {
var sum = [Double](repeating: 0.0, count: left.count)
for (i, _) in left.enumerated() {
sum[i] = left[i] + right[i]
}
return sum
}
let result = [1.0, 2.0] + [3, 4]
print(result)
Extending an operator's meaning
struct Vector: CustomStringConvertible {
let x: Int
let y: Int
let z: Int
var description: String {
return "(\(x), \(y), \(z))"
}
init(_ x: Int, _ y: Int, _ z: Int) {
self.x = x
self.y = y
self.z = z
}
Extending an operator's meaning
static func +(left: Vector, right: Vector) -> Vector {
return Vector(left.x + right.x,
left.y + right.y, left.z + right.z)
}
static prefix func -(vector: Vector) -> Vector {
return Vector(-vector.x, -vector.y, -vector.z)
}
mixed parameter types possible
this allows for some interesting flexibility
Mixed operator parameter types
func * (left: String, right: Int) -> String {
if right <= 0 {
return ""
}
var result = left
for _ in 1..<right {
result += left
}
return result
}
let scream = "a" * 6
print(scream)
we can introduce new operator symbols
this is to avoid confusing developers
but it does require a bit more notice to Swift
global declaration of the operator function type
definition of the operator function
infix: used between two values (1 + 1)
prefix: added before a value ( -1 )
postfix: added after a value
Creating the "UFO" operator for Point
infix operator <==>
extension Point {
static func <==>(left: Point, right: Point) -> Point {
return Point(x: left.x, y: right.y)
}
}
let ufoPt = origin <==> anotherPt
print(ufoPt)
used to provide a more human-readable or domain name to a type
usually used for more complex composite types
nothing more than an alias--no new type is introduced
Type Aliases
typealias Age = UInt8 typealias City = (String, String, UInt32)
Arrays act/behave like C-style arrays (mostly)
Arrays are single-typed (Strings, ints, etc)
Arrays are integer-indexed, 0-based
Arrays
var shoppingList : [String] =
["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
print("I bought a \(shoppingList[1])")
print("She bought \(shoppingList)")
// Use for-in over an array
let names = ["Alice", "Bob", "Mallory", "Eve", "Trent"]
for name in names {
print("Hello, \(name)!")
}
Dictionaries act/behave like C-style arrays
Dictionaries are arrays of key/value pairs
Keys and values can be any type
Dictionaries
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
let malJob = occupations["Malcolm"]
print("Mal is the \(malJob)")
print("People work as \(occupations)")
var explore = [ 4.5 : "StringValue" ]
print("Can we key by doubles? \(explore[4.5])")
// Use for-in over dictionaries
for (key, value) in occupations {
print("\(key) is the \(value)")
}
Provide a language-supported way to represent an absence of a value
Optional types are either a value or nil
Indicated by the use of ? at the end of the type name
... or by the use of the generic Optional type
"!"-suffixed usage attempts reference of possible nil value
triggers runtime error if value is nil, of course
"?"-suffixed usage attempts reference of possible nil value
returns nil if value is nil, without error
"?"-suffixed usage can be "chained" safely, with no runtime errors reported
but you will end up with a nil value at the end
Conforms to the LogicValue protocol
which means we can use it as a boolean expression value
Optionals
var theAnswer = 42
var maybeTheAnswer : Int? = theAnswer
maybeTheAnswer = nil
print("The answer is \(theAnswer)")
//print("... but it might be \(maybeTheAnswer!)")
// Since maybeTheAnswer is nil, this will generate
// an exception at runtime
if (maybeTheAnswer != nil) {
print("The answer is \(maybeTheAnswer!)")
}
types that are based on a primitive type
consist of a discretely-described range of "raw" values
do NOT implicitly convert to integer values
can also take "associated values" as part of the enum
this makes them similar to "discriminated unions"
enums can be based off of other primitive types
raw values can be specified as part of enum declaration
Enums
enum CompassPoint {
case North
case South
case East
case West
}
enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
var direction = CompassPoint.North
switch direction {
case .North: print("Brrr cold")
case .South: print("Watch out for penguins")
case .West : print("Where rises the Sun")
case .East : print("Where sets the Sun")
}
Enum raw values
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
let planet = Planet(rawValue: 3)
// planet is set to .Earth
let planetIndex = Planet.Earth.rawValue
// planetIndex is 3
Enums associated values
enum Barcode {
case UPCA(Int, Int, Int)
case QRCode(String)
}
let barcode = Barcode.UPCA(8, 85909_51226, 3)
// or
let barcode2 = Barcode.QRCode("8A9873CEFAE")
unnamed (sort of) groupings of values
names can be assigned as part of declaration
by default, names are integer-incremented value
can be used to represent "multiple return values"
described as parenthesized list of types
a given value can be "ignored" with the "wildcard" _
Tuples
var city = ("Seattle", "Washington", 5000000)
var person = ("Teresa", "Nguyen", 39)
var ssi = city
ssi = person
// Structurally typed!
print(ssi)
print(ssi.0)
Tuples
var team : (name : String, association : String, wins : Int) =
("Sounders", "MLS", 15)
print(team)
print("The \(team.name) have won \(team.wins) times")
Tuples and Switch
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
case (_, 0):
print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
print("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}
Define properties to store values
Define methods to provide functionality
Define initializers to set up their initial state
use struct keyword
Structures are "value types"
exist "in full" when on the stack
not aliased
passed by value
When in doubt... "What would int do?"
String, Array, and Dictionary are all structures/value types
use class keyword
"reference types"
exist "in full" on the heap
aliasable
passed by reference
Inheritance
Deinitializers
Reference semantics
Reference counting
Create an instance of a struct or class by using "initializer syntax"
essentially the type name followed by parentheses
initializer parameters (more on this later) passed between parens
For structs, instantiation returns the full object
For classes, instantiation returns a pointer/reference
Creating a new instance
var aPoint = Point(x:2, y:3)
print("aPoint = \(aPoint.stringified)")
var anotherPoint = aPoint
aPoint.x = 12
print("anotherPoint = \(anotherPoint), aPoint = \(aPoint)")
Use "===" and "!==" to do reference comparisons
Are the two variables/constants pointing to the same object?
Use "==" and "!=" to do value/equivalence comparisons
Do the two variables/constants contain the same data?
Note that implementing "==" and "!=" is your reponsibility
Reference identity
var ted = Person(firstName:"Ted", lastName:"Neward", age:42, location:nil) var ted2 = ted // These point to the same object ted.firstName = "Theodore" print(ted.stringified) print(ted2.stringified) // "Theodore"
Cannot use "===" or "!==", since we don't have references/pointers
Use of "==" and "!=" requires us to write custom comparison operator methods
Represent access to underlying data values
Never have access to underlying backing store
this is markedly different from Obj-C or other languages
Swift's syntactic sugar helps keep some safety in place
Variable stored properties (var)
Constant stored properties (let)
Type-wide (singleton) properties (static)
Lazy stored properties (lazy var)
Computed properties (defined "get"/"set" blocks)
"Observed" properties (defined "willSet"/"didSet" blocks)
var keyword
most common form of property
Variable stored properties
var name: String
let keyword
immutable once initialized
Constant stored properties
let starterYear = 2017
static keyword
used with any of the other keywords
only one instance is in existence
must be accessed via typename
lazy-prefixed var
not initialized until accessed
Lazy stored properties
lazy var complexObject = ComplexObject()
invoked on get or set access
provides explicit code blocks for each
no set block implies read-only
no get block implies write-only
storage-agnostic/ignorant
note that this will often be to other properties
Computed (unstored) properties
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
blocks fire before/after property-set access
willSet block invoked prior to change
didSet block invoked after change
Observed properties
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
Functions that are defined inside of a struct/class
Implicit "this" parameter (self)
"Type methods" (static methods) prefixed with "static" and have no "self"
Defining and calling methods on structs
// ... still in struct Point ...
func stringRep() -> String {
return "(\(self.x),\(self.y))"
}
static func compare(left: Point, right: Point) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
var o = Point.ORIGIN
print(o.stringRep())
if Point.compare(left: o, right: Point.ORIGIN) {
print("Yep, they point to the same place")
}
o.move(x: 3, y:5)
print(o.stringRep())
special methods used to initialize the struct
always called "init" and have no explicit return type
overload by number/type of arguments, and/or by parameter names
structs always have a "memberwise initializer"
an initializer with a parameter for each variable stored property
classes must initialize any property member
either explicitly in an initializer or at point of declaration
Initializers
init(x:Int32, y:Int32) {
self.x = x
self.y = y
}
init(x:Int32, y:Int32, name: String) {
self.x = x
self.y = y
self.name = name
}
init(y: Int32, x: Int32) {
self.x = x
self.y = y
}
init(pt: Point, offsetX: Int32 = 0, offsetY : Int32 = 0) {
self = pt
self.move(x: offsetX, y:offsetY)
}
}
Use "init?"
Return nil if constuction should fail
Makes the receiving reference an optional
Use "init!"
Return unwrapped optional
Makes the receiving reference an optional
"deinit" method serves as type destructor
Primarily responsible for resource release
and/or anytime there is a finite set of managed resources
Imagine a game of Poker, with a fixed amount of coins...
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.vendCoins(coins)
}
func winCoins(coins: Int) {
coinsInPurse += Bank.vendCoins(coins)
}
deinit {
Bank.receiveCoins(coinsInPurse)
}
}
only necessary on value types (structs, enums)
allow for modification of internal contents
Swift uses reference counting to provide automatic memory management
Each reference object has an associated "reference count"
Each reference object will not be released until its refct is zero
This has several known problems and considerations
Object A is created, assigned to local "x" (A.refct = 1)
A's reference count = 1
"x" goes out of scope
A's reference count goes down by 1
since A.refct == 0, it is released
Typical code
{ // new scope block
x = Object() // call this object "A"; A's refct = 1
y = Object() // call this object "B"; B's refct = 1
}
// when y goes out of scope, it drops its reference to B
// B's refct == 0; B is now deallocated
// when x goes out of scope, it drops its reference to A
// A's refct == 0; A is now deallocated
Typical code
x = Object() // call this object "A"; A's refct = 1
{ // new scope block
y = Object() // call this object "B"; B's refct = 1
// when y goes out of scope, it drops its reference to B
// B's refct == 0; B is now deallocated
}
// x is not out of scope (A.refct == 1) here, so A lives on
Typical code
x = Object() // call this object "A"; A's refct = 1
{ // new scope block
y = x; // this is A, so A's refct = 2
// when y goes out of scope, it drops its reference to A
// A's refct = 1; A remains allocated
}
// x is not out of scope (A.refct == 1) here, so A lives on
Object A holds a reference to object B
Object B holds a reference to object A
Neither has a refct of zero, so they never get released!
Reference cycle memory leak
class Person {
let firstName : String
let lastName : String
var spouse : Person?
init(first: String, last: String) {
firstName = first
lastName = last
}
deinit {
print("\(firstName) is being deinitialized")
}
}
{
var b = Person(first: "Brad", last: "Pitt")
var a = Person(first: "Angela", last: "Jolie")
b.spouse = angelina
a.spouse = brad
}
// They never go away....
"Brad" is created and stored to "b" (Brad.refct = 1)
"Angelina" is created and stored to "a" (Angelina.refct = 1)
"Brad" is assigned to angelina.spouse (Brad.refct + 1 = 2)
"Angelina" is assigned to brad.spouse (Angelina.refct + 1 = 2)
"b" goes out of scope (Brad.refct - 1 = 1)
"a" goes out of scope (Angelina.refct - 1 = 1)
Neither has a refct of zero, so they never get released!
The references described above are "strong" references
a strongly-referenced object will never be reclaimed
the problem in the above code is that these are mutual strong refs
With only strong references, you have a recipe for disaster
Swift allows for several modifiers to change the reference semantics
A "weak" reference will not keep an object alive
essentially, it doesn't add to the referent's reference count
"Weak" references are references modified with the weak keyword
weak reference must be a var of optional type (with a ? suffix)
Reference must be "dereferenced" to get to the actual object
Weak references
class Person {
let firstName : String
let lastName : String
weak var spouse : Person?
init(first: String, last: String) {
firstName = first
lastName = last
}
deinit {
print("\(firstName) is being deinitialized")
}
}
{
var b = Person(first: "Brad", last: "Pitt")
var a = Person(first: "Angela", last: "Jolie")
b.spouse = angelina
a.spouse = brad
}
"Brad" is created and stored to "b" (Brad.refct = 1)
"Angelina" is created and stored to "a" (Angelina.refct = 1)
"Brad" is assigned to angelina.spouse (Brad.refct remains the same!)
"Angelina" is assigned to brad.spouse (Angelina.refct remains the same!)
"b" goes out of scope (Brad.refct - 1 = 0; gets deallocated!)
"a" goes out of scope (Angelina.refct - 1 = 0; gets deallocated!)
An "unowned" reference is a strong reference, but doesn't add to the refct
Also doesn't require dereference, since it can't ever be nil
Useful in scenarios where referencing a long-lived object
An unowned reference example
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned var customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
func setCustomer(customer: Customer) {
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john = Customer(name: "John Appleseed")
john.card = CreditCard(number: 1234_5678_9012_3456, customer: john)
Only classes can inherit (not structs)
"Additive" inheritance
IS-A relationships between base and derived
Single-class implementation inheritance
Swift does not define a universal base class (a la System.Object or java.lang.Object)
Does have an "Any" type
"override" keyword before the "init"
call up to the base using "super"
"override" keyword before the "init"
call up to the base using "super"
"override" keyword before the "init"
call up to the base using "super"
"is" returns true/false if B extends A
"as?" returns optional derived or nil
"as!" returns derived or runtime fatal error
unit of distribution and packaging
practically, a build target or framework
included in a new project via "import"
pretty typical for a programming language
Access Control basics
Declared on any Swift construct
class, field, method, protocol, enum, etc
Five levels of defined access
"open": Accessible to anyone, only for classes/class members
"public": Accessible to anyone
"internal": Accessible to this module
"fileprivate": Accessible only to this file
"private": Accessible to this declaration
Each described by keyword of the same name
Keyword appears before the thing decorated
default: "internal"
can be set to any of the five
this sets the default access level for its members
members obtain same access level as the enumeration itself
members cannot be declared at a different access level
Accessed at MOST restrictive member's level
If I have a tuple of (U, I, R)...
... where U is public, I is internal, and R is private...
... then the tuple type is considered private
Accessed at MOST restrictive element's level
"element": parameter types and return type
if I have func Foo(U, I) -> R...
... where U, I, R are defined above...
... then Foo is private, since R is private
must be at most as accessible as the type they declare
private type => private constant/var/prop
setters can be MORE restrictive than getters
public get: allows for access by clients
private set: allows for mutability by trusted implementation
"public private(set) var mixedProperty = 0"
Protocols
describes an "intent to conform" for disparate types
similar to an interface in C#/Java
use protocol keyword
then declare properties and methods, no implementation
static properties/methods are acceptable
protocols can also require specific initializers
classes, enums, structs can all adopt protocols
protocol members that modify a struct must be declared with "mutating"
extend the protocol using inheritance syntax
protocol FullyNamed {
var fullName : String { get }
}
protocol RandomNumberGenerator {
func random() -> Double
}
Implementing a protocol
classes/structs/enums can conform to multiple protocols as desired
simply list them in the inheritance declarator
"override" is typically not required
structural matching is sufficient to satisfy the protocol
"required" is necessary on protocol-required initializers
I don't think this comes up much in practice
struct Person : FullyNamed {
var fullName : String
}
class LinearCongruentialGenerator:
RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
return lastRandom / m
}
}
Delegation
pattern used a lot within iOS
enables a class to hand off (delegate) some of its responsibilities
define a protocol to define the expected functionality
hold one (or more) instances of the delegate in the class
call to the delegate as the situation warrants/requires
class Dice {
let sides : Int
let generator : RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
protocol DiceGame {
var dice : Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(game: DiceGame)
}
necessary for Objective-C compatibility/use
but also useful in their own right, to add nominative binding capabilities
references to methods by name
with some amount of compiler checking
selectors can be used to invoke methods
permitting a degree of flexibility in setup/invocation
used extensively in Cocoa and iOS to match-by-capabilities
class must inherit from NSObject
necessary for the Obj-C plumbing "underneath"
members must be annotated with @objc
optional name describes the Obj-C name
usable on methods and property methods
#selector obtains method at compile-time
parameter is method with names separated by colons
use getter: or setter: to obtain property getter/setter methods
Example
class SomeClass: NSObject {
@objc let property: String
@objc(doSomethingWithInt:)
func doSomething(_ x: Int) { }
init(property: String) { self.property = property }
}
let selectorForMethod = #selector(SomeClass.doSomething(_:))
let selectorForPropertyGetter = #selector(getter: SomeClass.property)
Obj-C had a number of flexible options regarding objects
name-based access, for one, called "key-value coding"
Swift can provide similar kinds of access
accessing fields via a "key path"
observing changes to fields via "key observing"
a "key" is a string that identifies a specific property
"key path" is dot-separated keys used to specify a sequence of object properties to traverse
keys or key paths can be runtime strings or compile-time constants
a "value" is the value stored in the property
Access object properties
Manipulate collection properties
Invoke collection operators on collection objects
Access non-object properties
including deeply into a hierarchy of objects
compile-time constants obtained via \ or #keyPath constants
these are actually strings at runtime, but compiler-checked ahead of time
NSKeyValueCoding protocol defines the necessary interface
setValue(forKey:) to set a value
value(forKey:) to return a value
dictionary(withValues:forKeys:) to set values in bulk
(several overloads of each of the above)
NSObject implements this
typically K-V classes will just inherit from NSObject
any properties must be @objc-annotated
usually methods must be @objc-annotated, as well
Consider this simple class
class Person : NSObject {
@objc dynamic var firstName = ""
@objc dynamic var lastName = ""
@objc dynamic var age = 0
}
let p = Person()
p.firstName = "Fred"
We can access the property by "key"
let pkeyPath = #keyPath(Person.firstName)
if let value = p.value(forKey: pkeyPath) {
print(value) // "Fred"
}
if let value = p.value(forKey: "firstName") {
print(value) // "Fred"
}
mutableArrayValue(*): return proxy to array property
mutableSetValue(*): return proxy to mutable set property
mutableOrderedSetValue(*): return proxy to mutable ordered set
only available on NSArrays/NSSets of NSObjects
cast the Array to an NSArray to use methods
we often want to operate on collections collectively
@avg, @count, @max, @min, @sum
KVC lets us do this with "collection operators"
these are specified in the keyPath expression and applied before returning
NOTE: many of these operations are now exposed as functional-style methods
map, reduce, etc
Transaction class
class Transaction: NSObject {
@objc var payee: String
@objc var amount: Float
@objc var date: Date
init(_ p: String, _ a: Float, _ d: Date) {
self.payee = p
self.amount = a
self.date = d
}
}
Using collection operators
let transactions = [
Transaction("Green Power", 120.00, "2015-12-01".toDateTime()),
Transaction("Cable", 130.00, "2015-12-01".toDateTime()),
Transaction("Green Power", 150.00, "2016-01-01".toDateTime()),
Transaction("Mortgage", 1250.00, "2016-01-05".toDateTime()),
Transaction("Car Loan", 250.00, "2016-01-15".toDateTime()),
Transaction("Green Power", 170.00, "2016-02-01".toDateTime()),
Transaction("Car Loan", 250.00, "2016-02-15".toDateTime()),
Transaction("Car Loan", 250.00, "2016-03-15".toDateTime()),
]
print((transactions as NSArray).value(forKeyPath:"@avg.amount")!)
print((transactions as NSArray).value(forKeyPath:"@max.date")!)
print((transactions as NSArray).value(forKeyPath:"@count")!)
we can register a block of code to fire on any changes
call observe with a keyPath and a closure
when the property changes via setValue(), the notification handler fires
we can also register third-party observers via addObserver()
observer object, key path, options (.new, .old, .initial, .prior) and arbitrary context
observer object must implement the observeValue() method
make sure to un-register observers--garbage leak if forgotten
capture the result of observe--it is what keeps the observation hook "alive"
Adding in observers
// recall that p = Person(), p.firstName = "Fred"
// recall that pkeyPath = #keyPath(Person.firstName)
let result = p.observe(\.firstName) { person, change in
print("firstName change on \(person) to \(change)")
}
let result2 = p.observe(\.firstName, options: [.old, .new]) { person, change in
print("\(person) changed to \(change)")
}
p.setValue("Barney", forKey: pkeyPath)
p.setValue("Wilma", forKey: "firstName")
p.firstName = "Fred"
Adding in "formal" observers
class Observer : NSObject {
override func observeValue(forKeyPath keyPath: String?,
of object: Any?, change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
if keyPath == "firstName" {
print("Observing a change to firstName: \(change!) on \(object!)")
}
}
}
let o = Observer()
p.addObserver(o, forKeyPath: "firstName", options: .new, context: nil)
p.setValue("Barney", forKey: "firstName")
print(p.firstName)
Apple documentation:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/index.html
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html
Facebook has a "better" implementation:
https://github.com/facebook/KVOController
Protocols
describes an "intent to conform" for disparate types
similar to an interface in C#/Java
use protocol keyword
then declare properties and methods, no implementation
static properties/methods are acceptable
protocols can also require specific initializers
classes, enums, structs can all adopt protocols
protocol members that modify a struct must be declared with "mutating"
extend the protocol using inheritance syntax
protocol FullyNamed {
var fullName : String { get }
}
protocol RandomNumberGenerator {
func random() -> Double
}
Implementing a protocol
classes/structs/enums can conform to multiple protocols as desired
simply list them in the inheritance declarator
"override" is typically not required
structural matching is sufficient to satisfy the protocol
"required" is necessary on protocol-required initializers
I don't think this comes up much in practice
struct Person : FullyNamed {
var fullName : String
}
class LinearCongruentialGenerator:
RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
return lastRandom / m
}
}
Delegation
pattern used a lot within iOS
enables a class to hand off (delegate) some of its responsibilities
define a protocol to define the expected functionality
hold one (or more) instances of the delegate in the class
call to the delegate as the situation warrants/requires
class Dice {
let sides : Int
let generator : RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
protocol DiceGame {
var dice : Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(game: DiceGame)
}
allow us to "extend" an existing type with new functionality
sort of a binary equivalent to source-patching
but less risky or dangerous
they can:
add computed properties and type properties (statics)
define instance and type (static) methods
provide new initializers
define subscripts
make an existing type conform to a protocol
use the "extension" keyword to define an extension
however, use the type to be extended instead of a new name
once opened, add new members as desired
must not "hide" existing members or existing functionality
cannot introduce new fields--only computed properties
new initializers are intended to be protocol-relevant/specific
new methods generally add/extend existing behavior
Adding units-of-measure conversions
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
print("3ft equals \(3.ft) meters")
Add a Ruby-like "repetitions" ability to Int
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
3.repetitions {
print("Hello!")
}
Let's add some useful functionality to String
extension String {
func toDateTime() -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.date(from: self)!
}
}
substitute type parameter at compile-time
provides type-safe compile guarantees
works on both functions and classes
similar to "generics" in Java and C#, or "templates" in C++
a metatype instance describes a class/structure/enum/protocol type
provides information about the type, at runtime
properties
methods
inheritance chain
obtain a metatype instance by...
using type(of:) on an instance
using .self on a typename
the metatype type is classname.Type; Person.Type or UILabel.Type
we can use metatypes to construct instances of that type
explicitly call init on the metatype
we can use metatyeps to call type-level methods as if they were instance
because technically, they are instances on the type
sometimes we just want to repeat a snippet of source multiple times
but copy-paste violates DRY--what if we need to modify it later?
enter macros: the ability to write a source-level construct
syntactic macros (C, C++ preprocessor)
simple text drop-in replacement
little to no compiler support
potentially itself a source of bugs
semantic macros (Lisp, Scheme, etc)
fully checked and understood by the compiler
verified correct
this is Swift's implementation
freestanding macros
#-prefixed
appear anywhere in code
almost like source-level function calls
attached macros
@-prefixed
modify a declaration they're "attached" to
resemble other languages' "custom attributes" or "annotations"
Freestanding macros: "#function" and "#warning"
func myFunction() {
print("Currently running \(#function)")
#warning("Something's wrong")
}
#function returns the name of the currently-scoped function
#warning issues a warning during compilation
An attached macro: "OptionSet"
@OptionSet<Int>
struct SundaeToppings {
private enum Options: Int {
case nuts
case cherry
case fudge
}
}
This macro sets "nuts", "cherry", and "fudge" to be "bit flags" values (1, 2, 4, ...)
Swift standard library provides:
all of these produce....
#column: the column number
#dsohandle: the dynamic shared object (DSO) handle
#fileID: a unique identifier for the source file
#filePath: the complete path to the file
#file: the path to the file
#function: the name of the declaration
#line: the line number
... in which the macro appears
these all used to be "special literals" handled by the compiler
making them macros is much more extensible and reliable
macros work with the compiler during compilation
implementing a macro is thus tied into language compilation concepts
Specifically, Swift expands macros in the following way:
compiler reads the code, creating an in-memory representation of the syntax (AST)
compiler sends part of the in-memory representation to the macro implementation, which expands the macro
compiler replaces the macro call with its expanded form
compiler continues with compilation, using the expanded source code
not for the faint of heart, but exceedingly powerful
consider a simple freestanding macro, #fourcharactercode()
this will take four hex digits as a string (such as ABCD)
and turn it into an Int binary value correspnding to its ASCII value
so let code = #fourcharactercode("ABCD") should be replaced by let code = 1145258561 as UInt32
source:
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros#Implementing-a-Macro
Two parts:
a type that performs the macro expansion
a library (containing that type) that declares the macro to expose it as API
Easiest way to begin is swift package init --type macro
Swift tools version must be 5.9+ in the swift-tools-version comment
import the CompilerPluginSupport module
include macOS 10.15 as a minimum deployment target in the platforms list
Package declaration
// swift-tools-version: 5.9
import PackageDescription
import CompilerPluginSupport
let package = Package(
name: "MyPackage",
platforms: [ .iOS(.v17), .macOS(.v13)],
// ...
)
add a target for the macro implementation
must depend on SwiftSyntaxMacros/swift-syntax
must depend on SwiftCompilerPlugin/swift-syntax
add a target for the macro library to your existing Package.swift file
Package targets
targets: [
// Macro implementation that performs the source transformations
.macro(
name: "MyProjectMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
// Library that exposes a macro as part of its API
.target(name: "MyProject", dependencies: ["MyProjectMacros"]),
]
the macro type must extend an existing AST type (ExpressionMacro)
since #fourcharactercode is a freestanding macro producing an expression...
... it should conform to ExpressionMacro protocol
... which has one requirement: expansion(of:in:) static method
it picks out the elements within the macro
returns an ExprSyntax replacing the macro's contents
The macro's AST type
import SwiftSyntax
import SwiftSyntaxMacros
enum CustomError: Error { case message(String) }
public struct FourCharacterCode: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
guard let argument = node.argumentList.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first
else {
throw CustomError.message("Need a static string")
}
let string = literalSegment.content.text
guard let result = fourCharacterCode(for: string) else {
throw CustomError.message("Invalid four-character code")
}
return "\(raw: result) as UInt32"
}
}
The actual string-to-Int code
private func fourCharacterCode(for characters: String) -> UInt32? {
guard characters.count == 4 else { return nil }
var result: UInt32 = 0
for character in characters {
result = result << 8
guard let asciiValue = character.asciiValue else { return nil }
result += UInt32(asciiValue)
}
return result
}
not really an innovative language, really
still a helluvalot better than ObjC, if you ask me
Architect, Engineering Manager/Leader, "force multiplier"
http://www.newardassociates.com
http://blogs.newardassociates.com
Books
Developer Relations Activity Patterns (w/Woodruff, et al; APress, 2026)
Professional F# 2.0 (w/Erickson, et al; Wrox, 2010)
Effective Enterprise Java (Addison-Wesley, 2004)
SSCLI Essentials (w/Stutz, et al; OReilly, 2003)
Server-Based Java Programming (Manning, 2000)