These notes are continued from Part 1 found here.
Functions
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
print(greet(person: "Olivia"))
// Prints "Hello, Olivia!"
Functions can have multiple input parameters, which are written within the function’s parentheses, separated by commas.
Functions are not required to define a return type. That’s the -> String part in the above code block. Functions without a defined return type return a special value of type Void
. This is simply an empty tuple, which is written as ()
. Return values can be ignored, but a function that says it will return a value must always do so.
If the tuple type to be returned from a function has the potential to have “no value” for the entire tuple, you can use an optional tuple return type to reflect the fact that the entire tuple can be nil
.
An optional tuple type such as
(Int, Int)?
is different from a tuple that contains optional types such as(Int?, Int?)
. With an optional tuple type, the entire tuple is optional, not just each individual value within the tuple.
If the entire body of the function is a single expression, the function implicitly returns that expression.
You write an argument label before the parameter name, separated by a space:
func someFunction(argumentLabel parameterName: Int) {
// do something
}
If you don’t want an argument label for a parameter, write an underscore (_
) instead of an explicit argument label for that parameter.
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// do something
}
someFunction(1, secondParameterName: 2)
You can define a default value for any parameter in a function by assigning a value to the parameter after that parameter’s type. If a default value is defined, you can omit that parameter when calling the function.
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// do something
}
someFunction(parameterWithoutDefault: 4)
// parameterWithDefault is 12
You can define a constant or variable to be of a function type and assign an appropriate function to that variable:
var mathFunction: (Int, Int) -> Int = addTwoInts
This can be read as: “Define a variable called mathFunction
, which has a type of ‘a function that takes two Int
values, and returns an Int
value.’ Set this new variable to refer to the function called addTwoInts
.”
Closures
Closures take one of three forms:
- Global functions are closures that have a name and don’t capture any values.
- Nested functions are closures that have a name and can capture values from their enclosing function.
- Closure expressions are unnamed closures written in a lightweight syntax that can capture values from their surrounding context.
For characters in strings, “greater than” means “appears later in the alphabet than”:
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
var reversedNames = names.sorted(by: backward)
{ (parameters) -> return type in
statements
}
Structures & Classes
Whenever you define a new structure or class, you define a new Swift type. Give types
UpperCamelCase
names (such asSomeStructure
andSomeClass
here) to match the capitalization of standard Swift types (such asString
,Int
, andBool
). Give properties and methodslowerCamelCase
names (such asframeRate
andincrementCount
) to differentiate them from type names.
The code below creates a new instance of the class or structure, with any properties initialized to their default values.
// A struct
struct Resolution {
var width = 0
var height = 0
}
// Creating a new instance of that struct
let someResolution = Resolution()
You can access the properties of an instance using dot syntax.
print("The width of someResolution is \(someResolution.width)")
You can drill down into subproperties, such as the width
property in the resolution
property of a VideoMode
:
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
You can also use dot syntax to assign a new value to a variable property:
someVideoMode.resolution.width = 1280
All structures and enumerations are value types in Swift.
Unlike value types (struct), reference types (class) are not copied when they are assigned to a variable or constant, or when they are passed to a function.
// Example of a struct
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
// When you check the value of hd.width, it will still be 1920
// Example of a class
let tenEighty = VideoMode()
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
// Now when you check the value of tenEighty.frameRate, it will be 30.0
This example also shows how reference types can be harder to reason about. If tenEighty
and alsoTenEighty
were far apart in your program’s code, it could be difficult to find all the ways that the video mode is changed. Wherever you use tenEighty
, you also have to think about the code that uses alsoTenEighty
, and vice versa. In contrast, value types are easier to reason about because all of the code that interacts with the same value is close together in your source files.
tenEighty
andalsoTenEighty
are declared as constants, rather than variables. However, you can still changetenEighty.frameRate
andalsoTenEighty.frameRate
because the values of thetenEighty
andalsoTenEighty
constants themselves don’t actually change.
Because classes are reference types, it’s possible for multiple constants and variables to refer to the same single instance of a class behind the scenes. (The same isn’t true for structures and enumerations, because they are always copied when they are assigned to a constant or variable, or passed to a function.) This is where the === operator comes into play. Use === to find out whether two constants or variables refer to exactly the same instance of a class.
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."
Properties
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy
modifier before its declaration.
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
}
Global variables are variables that are defined outside of any function, method, closure, or type context. Local variables are variables that are defined within a function, method, or closure context.
Methods
Methods are functions that are associated with a particular type.
Static variables are those variables whose values are shared among all the instance or object of a class. When we define any variable as static, it gets attached to a class rather than an object.
Instance Methods
Instance methods are functions that belong to instances of a particular class, structure, or enumeration. Instance methods have exactly the same syntax as functions.
Every instance of a type has an implicit property called self
, which is exactly equivalent to the instance itself. You use the self
property to refer to the current instance within its own instance methods. In practice, you don’t need to write self
in your code very often. If you don’t explicitly write self
, Swift assumes that you are referring to a property or method of the current instance whenever you use a known property or method name within a method. This assumption is demonstrated by the use of count
(rather than self.count
) inside the three instance methods for Counter
.
// Counter Class
class Counter {
var count = 0
func increment() {
count += 1
}
}
// increment() can also be written like this:
func increment() {
self.count += 1
}
Type Methods
Type methods are methods that are called on the type itself. You indicate type methods by writing the static
keyword before the method’s func
keyword. Classes can use the class
keyword instead, to allow subclasses to override the superclass’s implementation of that method.
The example below defines a structure called LevelTracker
, which tracks a player’s progress through the different levels or stages of a game. It is a single-player game, but can store information for multiple players on a single device. All of the game’s levels (apart from level one) are locked when the game is first played. Every time a player finishes a level, that level is unlocked for all players on the device. The LevelTracker
structure uses type properties and methods to keep track of which levels of the game have been unlocked. It also tracks the current level for an individual player.
struct LevelTracker {
static var highestUnlockedLevel = 1
var currentLevel = 1
static func unlock(_ level: Int) {
if level > highestUnlockedLevel { highestUnlockedLevel = level }
}
static func isUnlocked(_ level: Int) -> Bool {
return level <= highestUnlockedLevel
}
@discardableResult
mutating func advance(to level: Int) -> Bool {
if LevelTracker.isUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}
The LevelTracker
structure keeps track of the highest level that any player has unlocked. This value is stored in a type property called highestUnlockedLevel
.
LevelTracker
also defines two type functions to work with the highestUnlockedLevel
property. The first is a type function called unlock(_:)
, which updates the value of highestUnlockedLevel
whenever a new level is unlocked. The second is a convenience type function called isUnlocked(_:)
, which returns true
if a particular level number is already unlocked. (Note that these type methods can access the highestUnlockedLevel
type property without your needing to write it as LevelTracker.highestUnlockedLevel
.)
In addition to its type property and type methods, LevelTracker
tracks an individual player’s progress through the game. It uses an instance property called currentLevel
to track the level that a player is currently playing.
To help manage the currentLevel
property, LevelTracker
defines an instance method called advance(to:)
. Before updating currentLevel
, this method checks whether the requested new level is already unlocked. The advance(to:)
method returns a Boolean value to indicate whether or not it was actually able to set currentLevel
. Because it’s not necessarily a mistake for code that calls the advance(to:)
method to ignore the return value, this function is marked with the @discardableResult
attribute. For more information about this attribute, see Attributes.
The LevelTracker
structure is used with the Player
class, shown below, to track and update the progress of an individual player:
class Player {
var tracker = LevelTracker()
let playerName: String
func complete(level: Int) {
LevelTracker.unlock(level + 1)
tracker.advance(to: level + 1)
}
init(name: String) {
playerName = name
}
}
The Player
class creates a new instance of LevelTracker
to track that player’s progress. It also provides a method called complete(level:)
, which is called whenever a player completes a particular level. This method unlocks the next level for all players and updates the player’s progress to move them to the next level. (The Boolean return value of advance(to:)
is ignored, because the level is known to have been unlocked by the call to LevelTracker.unlock(_:)
on the previous line.)
You can create an instance of the Player
class for a new player, and see what happens when the player completes level one:
var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// Prints "highest unlocked level is now 2"
If you create a second player, whom you try to move to a level that is not yet unlocked by any player in the game, the attempt to set the player’s current level fails:
player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
print("player is now on level 6")
} else {
print("level 6 has not yet been unlocked")
}
// Prints "level 6 has not yet been unlocked"
Initialization
Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.
You implement this initialization process by defining initializers, which are like special methods that can be called to create a new instance of a particular type. Their primary role is to ensure that new instances of a type are correctly initialized before they are used for the first time.
In its simplest form, an initializer is like an instance method with no parameters, written using the init
keyword:
init() {
// perform some initialization here
}
The example below defines a new structure called Fahrenheit
to store temperatures expressed in the Fahrenheit scale. The Fahrenheit
structure has one stored property, temperature
, which is of type Double
:
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"
This example defines a class called ShoppingListItem
, which encapsulates the name, quantity, and purchase state of an item in a shopping list:
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
Because all properties of the ShoppingListItem
class have default values, and because it is a base class with no superclass, ShoppingListItem
automatically gains a default initializer implementation that creates a new instance with all of its properties set to their default values. (The name
property is an optional String
property, and so it automatically receives a default value of nil
, even though this value is not written in the code.) The example above uses the default initializer for the ShoppingListItem
class to create a new instance of the class with initializer syntax, written as ShoppingListItem()
, and assigns this new instance to a variable called item
.
Memberwise Initializers
The example below defines a structure called Size
with two properties called width
and height
. Both properties are inferred to be of type Double
by assigning a default value of 0.0
. The Size
structure automatically receives an init(width:height:)
memberwise initializer, which you can use to initialize a new Size
instance:
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
When you call a memberwise initializer, you can omit values for any properties that have default values. In the example above, the Size
structure has a default value for both its height
and width
properties. You can omit either property or both properties, and the initializer uses the default value for anything you omit—for example:
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// Prints "0.0 2.0"
let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// Prints "0.0 0.0"
Swift API Guidelines
Fundamentals
- Write a documentation comment for every declaration. Insights gained by writing documentation can have a profound impact on your design, so don’t put it off.
- Names of types and protocols are
UpperCamelCase
. Everything else islowerCamelCase
. - Clarity at the point of use is your most important goal. Entities such as methods and properties are declared only once but used repeatedly. Design APIs to make those uses clear and concise. When evaluating a design, reading a declaration is seldom sufficient; always examine a use case to make sure it looks clear in context.
- Clarity is more important than brevity. Although Swift code can be compact, it is a non-goal to enable the smallest possible code with the fewest characters. Brevity in Swift code, where it occurs, is a side-effect of the strong type system and features that naturally reduce boilerplate.
Begin with a summary that describes the entity being declared. Often, an API can be completely understood from its declaration and its summary. Focus on the summary; it’s the most important part. Many excellent documentation comments consist of nothing more than a great summary. Use a single sentence fragment if possible, ending with a period. Do not use a complete sentence.
/// Returns a "view" of `self` containing the same elements in
/// reverse order.
func reversed() -> ReverseCollection
Optionally, continue with one or more paragraphs and bullet items. Paragraphs are separated by blank lines and use complete sentences.

Naming
More words may be needed to clarify intent or disambiguate meaning, but those that are redundant with information the reader already possesses should be omitted. In particular, omit words that merely repeat type information.
func removeElement(_ member: Element) {} // wrong
func remove(_ member: Element) {} // correct
Name variables, parameters, and associated types according to their roles, rather than their type constraints.
var string = "Hello" // wrong
var greeting = "Hello" // correct
Prefer method and function names that make use sites form grammatical English phrases. The following are all good examples.
x.insert(y, at: z) “x, insert y at z”
x.subViews(havingColor: y) “x's subviews having color y”
x.capitalizingNouns() “x, capitalizing nouns”
Begin names of factory methods with “make
”, e.g. x.makeIterator()
.
Name functions and methods according to their side-effects
- Those without side-effects should read as noun phrases, e.g.
x.distance(to: y)
,i.successor()
. - Those with side-effects should read as imperative verb phrases, e.g.,
print(x)
,x.sort()
,x.append(y)
. - When the operation is naturally described by a noun, use the noun for the nonmutating method and apply the “form” prefix to name its mutating counterpart.

- Uses of Boolean methods and properties should read as assertions about the receiver when the use is nonmutating, e.g.
x.isEmpty
,line1.intersects(line2)
. - Protocols that describe what something is should read as nouns (e.g.
Collection
). - Avoid obscure terms if a more common word conveys meaning just as well. Don’t say “epidermis” if “skin” will serve your purpose. Terms of art are an essential communication tool, but should only be used to capture crucial meaning that would otherwise be lost. The only reason to use a technical term rather than a more common word is that it precisely expresses something that would otherwise be ambiguous or unclear. The reasons for this are as follows:
- Don’t surprise an expert: anyone already familiar with the term will be surprised and probably angered if we appear to have invented a new meaning for it.
- Don’t confuse a beginner: anyone trying to learn the term is likely to do a web search and find its traditional meaning.
- That said, Embrace precedent. Don’t optimize terms for the total beginner at the expense of conformance to existing culture.
Conventions
Argument Labels
func move(from start: Point, to end: Point)
x.move(from: x, to: y)
- Omit all labels when arguments can’t be usefully distinguished, e.g.
min(number1, number2)
,zip(sequence1, sequence2)
. - In initializers that perform value preserving type conversions, omit the first argument label, e.g.
Int64(someUInt32)
- When the first argument forms part of a prepositional phrase, give it an argument label. The argument label should normally begin at the preposition, e.g.
x.removeBoxes(havingLength: 12)
. - Otherwise, if the first argument forms part of a grammatical phrase, omit its label, appending any preceding words to the base name, e.g.
x.addSubview(y)
@joekotlan on X