References
Decimals
- When you create a floating-point number, Swift considers it to be a
Double
. That’s short for “double-precision floating-point number”.
- Swift considers decimals to be a wholly different type of data to integers, which means you can’t mix them together. After all, integers are always 100% accurate, whereas decimals are not, so Swift won’t let you put the two of them together unless you specifically ask for it to happen.
- Swift decides whether you wanted to create a
Double
or an Int
based on the number you provide
Older APIs use CGFloat
. If you see CGFloat
you can just ignore it and use Double
.
Collection Types
Swift provides three primary collection types, known as arrays
, sets
, and dictionaries
, for storing collections of values. Arrays are ordered collections of values. Sets are unordered collections of unique values. Dictionaries are unordered collections of key-value associations.
Arrays
An array stores values of the same type in an ordered list. The same value can appear in an array multiple times at different positions.
Sets
A set stores distinct values of the same type in a collection with no defined ordering. You can use a set instead of an array when the order of items isn’t important, or when you need to ensure that an item only appears once.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// Prints "letters is of type Set<Character> with 0 items."
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items
for genre in favoriteGenres {
print("\(genre)")
}
// Classical
// Jazz
// Hip hop
|
Dictionaries
Dictionary is very similar to Java Map. It cannot contain duplicate keys: Each key can map to at most one value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
let person = [
"name": "John Doe",
"title": "Mr",
"location": "US"
]
let hasGraduated = [
"Eric": false,
"Maeve": true,
"Otis": false,
]
let olympics = [
2012: "London",
2016: "Rio de Janeiro",
2021: "Tokyo"
]
|
You can also create an empty dictionary using whatever explicit types you want to store, then set keys one by one:
1
2
3
4
|
var heights = [String: Int]()
heights["Yao Ming"] = 229
heights["Shaquille O'Neal"] = 216
heights["LeBron James"] = 206
|
Type annotations
Type annotations let us be explicit about what data types we want.
1
2
3
4
5
6
7
8
9
10
|
let surname: String = "Lasso" // Sttring
var score: Int = 0 // Int
let pi: Double = 3.141 // Double
var isAuthenticated: Bool = true // Bool
var albums: [String] = ["Red", "Fearless"] // Array
var user: [String: String] = ["id": "@twostraws"] // Dictionary
var books: Set<String> = Set(["The Bluest Eye", "Foundation", "Girl, Woman, Other"]) // Set
var teams: [String] = [String]() // Empty array of strings
var cities: [String] = [] // Empty array of strings
var clues = [String]() // Empty array of strings
|
Conditions & Loops
Loops
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
|
// Loop an array of strings
let platforms = ["iOS", "macOS", "tvOS", "watchOS"]
for os in platforms {
print("Swift works great on \(os).")
}
// Loop between [1,12]
for i in 1...12 {
print("5 x \(i) is \(5 * i)")
}
// Loop between [1,5)
for i in 1..<5 {
print("Counting 1 up to 5: \(i)")
}
// Loop without loop variable
var lyric = "Haters gonna"
for _ in 1...5 {
lyric += " hate"
}
// While loop
var countdown = 5
while countdown > 0 {
print("\(countdown)…")
countdown -= 1
}
// Repeat-While loop
repeat {
statements
} while condition
|
If you call continue
inside the loop body, Swift will immediately stop executing the current loop iteration and jump to the next item in the loop.
If you call break
inside the loop body, Swift will exit the current loop immediately and skip all remaining iterations.
Functions
Return values
1
2
3
4
|
// Returns a single value as Bool
func isUppercase(string: String) -> Bool {
string == string.uppercased()
}
|
In order to return multiple values from a function, an array, set or dictionary can be used as return type.
1
2
3
4
5
6
7
8
9
10
11
12
|
// Returns a single value as Array
func getUser() -> [String] {
["Taylor", "Swift"]
}
// Returns a single value as Dictionary
func getUser() -> [String: String] {
[
"firstName": "Taylor",
"lastName": "Swift"
]
}
|
It is recommended to use Tuple
to return multiple values. Tuple
lets us put multiple pieces of data into a single variable. They have a fixed size and can have a variety of data types.
1
2
3
4
5
6
7
8
9
10
11
|
func getUser() -> (firstName: String, lastName: String) {
(firstName: "Taylor", lastName: "Swift")
}
let user = getUser()
print("Name: \(user.firstName) \(user.lastName)")
func getUser() -> (String, String) {
("Taylor", "Swift")
}
let user = getUser()
print("Name: \(user.0) \(user.1)")
|
We can pull apart the return value from the function into two separate constants.
1
2
|
let (firstName, lastName) = getUser()
print("Name: \(firstName) \(lastName)")
|
If you don’t need all the values from the tuple you can go a step further by using _
to tell Swift to ignore that part of the tuple:
1
2
|
let (firstName, _) = getUser()
print("Name: \(firstName)")
|
Customizing parameter labels
Adding an underscore before the parameter name, it will remove the external parameter label.
1
2
3
4
5
6
|
func isUppercase(_ string: String) -> Bool {
string == string.uppercased()
}
let string = "HELLO, WORLD"
let result = isUppercase(string: string) // without underscore
let result = isUppercase(string) // with underscore, it is no need to write an external parameter name
|
Another problem in parameters is reserved keywords
that you cannot use the keywords as parameter name.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func printTimesTables(for: Int) {
for i in 1...12 {
print("\(i) x \(for) is \(i * for)") // throws error since `for` is not allowed inside the function
}
}
printTimesTables(for: 5)
func printTimesTables(for number: Int) {
for i in 1...12 {
print("\(i) x \(number) is \(i * number)")
}
}
printTimesTables(for: 5) // So, now you can use `for` as second parameter name
|
Closures
Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to lambdas in Java and other programming languages.
Closures can capture and store references to any constants and variables from the context in which they’re defined. This is known as closing over
those constants and variables.
1
2
3
4
|
// Closure Expression Syntax
{ (parameters) -> return type in
statements
}
|
1
2
3
4
5
6
7
8
9
|
let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
})
|
Swift can automatically provide parameter names for us, using shorthand syntax
. Swift provides for us: $0
and $1
, for the first and second string parameters respectively.
1
|
let reverseTeam = team.sorted { $0 > $1 }
|
Functions as parameters
Having three trailing closures is not as uncommon as you might expect. To demonstrate this here’s a function that accepts three function parameters, each of which accept no parameters and return nothing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func doImportantWork(first: () -> Void, second: () -> Void, third: () -> Void) {
print("About to start first work")
first()
print("About to start second work")
second()
print("About to start third work")
third()
print("Done!")
}
doImportantWork {
print("This is the first work")
} second: {
print("This is the second work")
} third: {
print("This is the third work")
}
|
Structures and Classes
Swift doesn’t require you to create separate interface and implementation files for custom structures and classes.
Structures and classes in Swift have many things in common. Both can:
- Define properties to store values
- Define methods to provide functionality
- Define subscripts to provide access to their values using subscript syntax
- Define initializers to set up their initial state
- Be extended to expand their functionality beyond a default implementation
- Conform to protocols to provide standard functionality of a certain kind
Classes have additional capabilities that structures don’t have:
- Inheritance enables one class to inherit the characteristics of another.
- Type casting enables you to check and interpret the type of a class instance at runtime.
- Deinitializers enable an instance of a class to free up any resources it has assigned.
- Reference counting allows more than one reference to a class instance.
Dynamic property values
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
struct Employee {
let name: String
var vacationAllocated = 14
var vacationTaken = 0
// only getter
var vacationRemaining: Int {
vacationAllocated - vacationTaken
}
// with getter and setter
var vacationRemaining: Int {
get {
vacationAllocated - vacationTaken
}
set {
vacationAllocated = vacationTaken + newValue
}
}
}
|
Take action when a property changes
In Swift, there are property observers
that run when properties change. A didSet
observer to run when the property just changed, and a willSet
observer to run before the property changed.
1
2
3
4
5
6
7
|
struct Game {
var score = 0 {
didSet {
print("Score is now \(score)")
}
}
}
|
Swift automatically provides the constant oldValue
inside didSet
and the constant newValue
inside willSet
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
struct App {
var contacts = [String]() {
willSet {
print("Current value is: \(contacts)")
print("New value will be: \(newValue)")
}
didSet {
print("There are now \(contacts.count) contacts.")
print("Old value was \(oldValue)")
}
}
}
var app = App()
app.contacts.append("Adrian E")
app.contacts.append("Allen W")
app.contacts.append("Ish S")
|
Initializers
Swift silently generates the initializer for us based on the properties we place inside a struct. You can still create a custom one yourself.
Swift won’t automatically generate a memberwise initializer for classes. This means you either need to write your own initializer, or assign default values to all your properties.
Inheritance
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
|
class Employee {
let hours: Int
init(hours: Int) {
self.hours = hours
}
func printSummary() {
print("I work \(hours) hours a day.")
}
}
class Developer: Employee {
let isPrincipal: Bool
init(hours: Int, isPrincipal: Bool) {
self.isPrincipal = isPrincipal
super.init(hours: hours)
}
func work() {
print("I'm writing code for \(hours) hours.")
}
override func printSummary() {
print("I'm a developer who will sometimes work \(hours) a day, but other times spend hours arguing about whether code should be indented using tabs or spaces.")
}
}
class Manager: Employee {
func work() {
print("I'm going to meetings for \(hours) hours.")
}
}
|
Copy classes and structures
1
2
3
4
5
6
7
8
|
class User {
var username = "Anonymous"
}
var user1 = User()
var user2 = user1
user2.username = "Taylor"
print(user1.username) // Taylor
print(user2.username) // Taylor
|
Structs do not share their data amongst copies, meaning that if we change class User
to struct User
in our code we get a different result.
1
2
3
4
5
6
7
8
|
struct User {
var username = "Anonymous"
}
var user1 = User()
var user2 = user1
user2.username = "Taylor"
print(user1.username) // Anonymous
print(user2.username) // Taylor
|
If you want to create a unique copy of a class instance – sometimes called a deep copy – you need to handle creating a new instance and copy across all your data safely.
1
2
3
4
5
6
7
8
9
|
class User {
var username = "Anonymous"
func copy() -> User {
let user = User()
user.username = username
return user
}
}
|
Extensions
Extensions add new functionality to an existing class, structure, enumeration, or protocol type. An extension can extend an existing type to make it adopt one or more protocols.
1
2
3
|
extension SomeType: SomeProtocol, AnotherProtocol {
// implementation of protocol requirements goes here
}
|
Example: You can extend the Rect structure to provide an additional initializer that takes a specific center point and size:
1
2
3
4
5
6
7
|
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
|
Protocols
Swift protocols are very similar to Java interfaces.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
protocol SomeProtocol {
init(someParameter: Int) // Initializer Requirements
var mustBeSettable: Int { get set } // Property Requirements
var doesNotNeedToBeSettable: Int { get } // Property Requirements
static func someTypeMethod() // Method Requirements
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set } // Property Requirements
func random() -> Double // Method Requirements
mutating func toggle() // Mutating Method Requirements
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}
class SomeClass: SomeProtocol, FirstProtocol, AnotherProtocol {
// The following is due to Initializer Requirements in `SomeProtocol`
required init(someParameter: Int) {
// initializer implementation goes here
}
// class definition goes here
}
|
Comparable
Comparable
is a Swift protocol, which allows Swift to see if one object should be sorted before another. Custom types cannot be implemented automatically by Swift. It is needed to write a function called <
that accepts two instances of your struct as its parameter, and returns true if the first instance should be sorted before the second.
1
2
3
4
5
6
7
|
struct Location: Equatable, Comparable {
let name: String
}
func <(lhs: Location, rhs: Location) -> Bool {
lhs.name < rhs.name
}
|
Optionals
Unwrapping optionals
By using variable shadowing
, we are temporarily creating a second constant of the same name, available only inside the condition’s body. It is very common and mainly used with optional unwraps.
with if let
, it lets us run if number has a value inside.
1
2
3
4
5
|
var number: Int? = nil
if let number = number {
// Run if number has a value inside
print(function1(number: number))
}
|
with guard let
, it lets us run if number doesn’t have a value inside.
1
2
3
4
5
6
7
8
|
func printSquare(of number: Int?) {
// Run if number doesn't have a value inside
guard let number = number else {
print("Missing input")
return
}
print("\(number) x \(number) is \(number * number)")
}
|
with nil coalescing operator
, it lets us provide a default value if the optional was empty.
1
2
3
4
5
6
7
|
let captains = [
"Enterprise": "Picard",
"Voyager": "Janeway",
"Defiant": "Sisko"
]
let new = captains["Serenity"] ?? "N/A"
let new = captains["Serenity", default: "N/A"]
|
Optional chaining
1
2
3
4
5
6
7
|
struct Book {
let title: String
let book: String?
}
var book: Book? = nil
let author = book?.author?.first?.uppercased() ?? "A"
|