1. 1. The Basics
    1. 1.1. Implicitly Unwrapped Optional
    2. 1.2. Assertions, Preconditions and Fatal Errors
    3. 1.3. MeatType
    4. 1.4. Self Type
  2. 2. Basic Operators
    1. 2.1. Remainder Operator
    2. 2.2. Comparison Operators
    3. 2.3. Range Operators
  3. 3. Strings and Characters
    1. 3.1. String Literals
    2. 3.2. Extended String Delimiters
    3. 3.3. Unicode Scalars and Extended Grapheme Clusters
    4. 3.4. Counting Characters
    5. 3.5. String Indices
    6. 3.6. Comparing Strings
  4. 4. Control Flow
    1. 4.1. Where
    2. 4.2. Fallthrough
    3. 4.3. Labeled Statements
  5. 5. Functions
    1. 5.1. Variadic Parameters
  6. 6. Closures
    1. 6.1. Escaping Closures
  7. 7. Enumerations
    1. 7.1. Recursive Enumerations
  8. 8. Properties
    1. 8.1. Lazy Stored Properties
    2. 8.2. Property Observers
    3. 8.3. Property Wrapper
    4. 8.4. Global and Local Variables
    5. 8.5. Type Properties
    6. 8.6. Type Property Syntax
  9. 9. Methods
  10. 10. Inheritance
    1. 10.1. Accessing superclasses Methods, Properties, and Subscripts
    2. 10.2. Preventing Overrides
  11. 11. Initialization
    1. 11.1. Setting initial values for Stored Properties
    2. 11.2. Initializer Delegation for Value Types
    3. 11.3. Initializer Delegation for Class Types
    4. 11.4. Two-Phase Initialization
    5. 11.5. Initializer Inheritance and Overriding
    6. 11.6. Automatic Initializer Inheritance
    7. 11.7. Overriding a Failable Initializer
    8. 11.8. Required Initializers
  12. 12. Optional Chaining
    1. 12.1. Calling Methods Through Optional Chaining
    2. 12.2. Linking Multiple Levels of Chaining
  13. 13. Concurrency
  14. 14. Type Casting
    1. 14.1. Type Casting for Any and AnyObject
  15. 15. Nested Types
  16. 16. Extensions
  17. 17. Protocols
    1. 17.1. Property Requirements
    2. 17.2. Mutating Method Requirements
    3. 17.3. Initializer Requirements
    4. 17.4. Protocol Extensions
  18. 18. Generics
    1. 18.1. Type Constraints
  19. 19. Opaque Types
  20. 20. Automatic Reference Counting
    1. 20.1. Unowned Optional References
    2. 20.2. Defining a Capture List
  21. 21. Memory Safety
  22. 22. Access Control
  23. 23. Advanced Operators
    1. 23.1. Result Builder

Learning Swift Again

I have used Swift for 6 years, from the first version to the version 5.5 now. I had learned Objective-C just for four months when I first learned Swift. At that time, I had written an application by Objective-C and then I decide to rewrite the application by Swift. It’s a very painful experience for me, especially the Optional Value, I had to unwrap the optional values again and again before using them. But I love its new concise grammar compared with Objective-C.

I didn’t know whether or not I can use it when I work in a company to write an iOS project, because almost all of the development teams were using Objective-C as the major development language. Maybe I can use Swift in company projects after two or three years I thought at the time. Fortunately, the company I first joined is a entrepreneurial team, we had only two iOS developers. I decided to import Swift to our projects one year later because I thought I had learned Swift for a whole year and written several example applications.

In that after I began using Swift in company’s projects, I have never backed to Objective-C. The Swift has updated several significant versions in these years, some versions need us to make many syntax modifications in our projects. And I have to learn these new features and changes over and over again. I don’t know how many times learning Swift this is, but now I decide to read the official Swift document seriously to search the knowledge I missed.

This is a checklist to record the syntax problems I’m interested in and some magical usages about some types.

The Basics

Implicitly Unwrapped Optional

When should we use implicitly unwrapped optionals?

First, we should know that the implicitly unwrapped optionals are also of the normal Optional type.

1
2
3
let value: Int!
print(type(of: value))
// Optional<Int>

When you assign an implicitly unwrapped optional to an optional variable, the compiler do nothing, because they are the same type. But when you assign an implicitly unwrapped optional to an un-optional variable, the compile will force-unwraps the value. If the implicitly unwrapped optional is nil and you try to access its wrapped value, you’ll trigger a runtime error.

We all know that we shouldn’t use implicitly unwrapped optionals, because they are dangerous to crash applications. But we still need it in some special situations, such as below.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Owner {
var pet: Pet!
init() {
pet = Pet(owner: self)
}
}

class Pet {
weak var owner: Owner?
init(owner: Owner) {
self.owner = owner
}
}

An owner must has a pet, to initialize a pet needing an owner. If we modify the var pet: Pet! to var pet: Pet, the compiler will require us to assign a value to the property pet before using the self, but we have to get the self to create a pet, they conflict each other. So we need to use implicitly unwrapped optional to declare that the pet must has a value and to tell the compiler not checking the property during initialization because we will assign it a value soon after.

Assertions, Preconditions and Fatal Errors

What’s the Precondition?

They are all used to check code conditions. We usually use Assertions, such as assert(id != nil, "The goods id cannot be nil."). Sometimes, we would use assert(false, "error message") under a checked condition situation, here is a better syntax assertFailure("error message").

I have never used Preconditions. To be honest, I have never heard about it. They are all same in usages, but the compiler evaluates Assertions only in development mode. The Preconditions can be still checked in release mode if the compiler doesn’t open the unchecked mode(-Ounchecked).

The Fatal Errors are always checked in any compiler mode.

MeatType

A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types.

  1. The metatype of a class, structure, enumeration type is the name of that type followed by .Type (let metatype: SomeClass.Type)
  2. The metatype of a protocol type is the name of the protocol followed by .Protocol(let metatype: SomeProtocol.Protocol). If you want to depict the metatype of the concrete type satisfying the protocol conformance, you can use the type followed by .Type to do this.(let concreteMetatype = SomeProtocol.Type)
  3. You can postfix the types with .self to get their metatypes. (let metatype: SomeClass.Type = SomeClass.self)

We can use a type’s metatype to construct its instances. But if the type is class type, we must declare the class type with final or mark the initializers that will be used as required.

Self Type

Swift provides the Self type that we can conveniently use it to represent the type itself without repeating its name. The Self refers to the same type as the type(of:) function in the Swift standard library.

Basic Operators

Remainder Operator

A knowledge point about %.

We use the % to calculate a number’s remainder. We assume an expression let reminder = a % b, the sign of the number b is ignored when calculating. This means that a % b and a % -b always give the same answer.

Comparison Operators

Compare two tuples.

We can use comparison operators directly to compare two tuples.

1
2
3
(10, "a") > (9, "b") // true
(9, "a") > (9, "b") // false
(9, "a") == (9, "a") // true

The compiler will compare their elements one by one from left to right.

Range Operators

How many kinds of range operators?

1
2
3
4
5
6
7
8
print(type(of: 1...2)) // ClosedRange<Int>
print(type(of: 1..<2)) // Range<Int>

print(type(of: 1...)) // PartialRangeFrom<Int>
print(type(of: ...1)) // PartialRangeThrough<Int>
print(type(of: ..<1)) // PartialRangeUpTo<Int>

print(type(of: ...)) // (UnboundedRange_) -> ()

The ... can only be used in sequences to declare slicing all elements of it.

There are some alias types of range operators.

1
2
3
public typealias CountableRange<Bound> = Range<Bound> where Bound : Strideable, Bound.Stride : SignedInteger
public typealias CountableClosedRange<Bound> = ClosedRange<Bound> where Bound : Strideable, Bound.Stride : SignedInteger
public typealias CountablePartialRangeFrom<Bound> = PartialRangeFrom<Bound> where Bound : Strideable, Bound.Stride : SignedInteger

Strings and Characters

String Literals

Set up a single line string with the multiline string literal format via the symbol \.

1
2
3
4
let softWrappedQuotation = """
The White Rabbit put on his spectacles. "Where shall I begin, \
please your Majesty?" he asked.
"""

A multiline string can be indented to match the surrounding code.

Extended String Delimiters

Do not render string interpolations.

1
2
3
4
5
6
7
8
9
10
11
12
13
let name = "CSL"
let age = 25

print("name: \(name)\nage: \(age)")
//name: CSL
//age: 25

print(#"name: \(name)\nage: \(age)"#)
//name: \(name)\nage: \(age)

print(##"name: \(name)\#nage: \(age)"##)
//name: \(name)
//age: \(age)

We can surround the string with number signs # to disable string interpolation. If we want to use string interpolation in a string that uses extended delimiters, we can add a sign # behind the backslash of string interpolations.

Unicode Scalars and Extended Grapheme Clusters

What is a Swift’s string composed by?

A string contains a sequence of characters of Swift’s Character type.
Every instance of Swift’s Character type represents a single extended grapheme cluster.
An extended grapheme cluster contains a sequence of unicode scalars.

1
2
let eAcute: Character = "\u{E9}"                         // é
let combinedEAcute: Character = "\u{65}\u{301}" // e followed by ́

The two characters above are rendered as the same format but they are absolutely different in underlying values.

Counting Characters

Do you know how to calculate the length of a string?

1
2
3
4
5
6
7
let eAcute: String = "\u{E9}"
let combinedEAcute: String = "\u{65}\u{301}"
let combinedEAcute2: NSString = "\u{65}\u{301}"

print(eAcute.count) // 1
print(combinedEAcute.count) // 1
print(combinedEAcute2.length) // 2

As you can see, the count of a string of Swift’s String type means the count of extended grapheme clusters. The length of a string of NSString is based on the number of 16-bit code units within the string’s UTF-16 representation.

String Indices

Why does Swift not provide the Int indices for String?

As mentioned above, a string is composed by a sequence of grapheme clusters and a grapheme cluster might contain several unicode scalars(One scalar occupies 21 bits memory). So the compiler can not use an int index to offset the memory address to find the specific character, because the memory size one character occupied is uncertain.

Comparing Strings

"\u{E9}" == "\u{65}\u{301}", true or false?

The result of the comparison expression above is true. The "\u{E9}" is é and the "\u{65}\u{301}" is e followed by ́. Theirs unicode scalars are not equal, but the representations of their grapheme clusters are all the character é. So Swift thinks they are the same strings.

Control Flow

Where

Using Where in switch cases to perform dynamic filtering.

1
2
3
4
5
6
7
8
9
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}

You can write any boolean result expressions following the statement where.

Fallthrough

Fallthrough won’t check the next case’s conditions, directly executes its block.

1
2
3
4
5
6
7
8
9
10
11
12
13
let age = 18

switch age {
case 18:
print("18")
fallthrough
case 19...100:
print("An adult")
default:
break
}
// 18
// An adult

The variable age is 18 of type Int, it’s outside of the range 19...100. But the compiler would ignore the next case’s condition checking to straightly execute its block when using the keyword fallthrough.

Labeled Statements

Exit the specified one in nested control flows.

1
2
3
4
5
6
7
8
9
10
11
for x in 0...2 {
switch x {
case 1:
break
// Want to exit the outer loop.
// But can only exit the switch scope and still enter the next loop.
default:
break
}
print("outer: \(x)")
}

In the switch statement, we want to exit the outer loop when checking the x is equal to 2. But the break statement in the switch’s scope means exiting the switch control flow rather than the outer loop.
The resolution is to label the outer loop and to use the break followed by the loop’s label to definitely exit the outer loop.

1
2
3
4
5
6
7
8
9
outerLoop: for x in 0...2 {
switch x {
case 1:
break outerLoop // Want to exit the outer loop.
default:
break
}
print("outer: \(x)")
}

Every control statements can prefix labels.

Functions

Variadic Parameters

We can define multiple variadic parameters for a function.

1
2
3
func zip(leftValues: String..., rightValues: String...) -> [(String, String)] {
zip(leftValues, rightValues).map({ $0 })
}

We defined a zip method of variadic parameter style above. The count of variadic parameters is unlimited, but every parameter following a variadic parameter must have the argument label.

Closures

Escaping Closures

Escaping closures can’t capture a mutable reference to self for structures.

1
2
3
4
5
6
7
struct SomeStruct {
var x = 10
mutating func doSomething() {
someFunctionWithNonescapingClosure { x = 200 } // Ok
someFunctionWithEscapingClosure { x = 100 } // Error
}
}

Structures and enumerations don’t allow shared mutability.

Enumerations

Recursive Enumerations

Use indirect to indicate that an enumeration or enumeration cases are recursive.

1
2
3
4
enum MatryoshkaDoll {
case empty
indirect case doll(MatryoshkaDoll)
}

The inner of a matryoshka doll has either a smaller doll or nothing. We declare an enumeration above named MatryoshkaDoll to represent a matryoshka doll. The case doll of the enumeration has an associated value of type MatryoshkaDoll means it has another one in its inner. The case doll’s associated values a value of itself enumeration type means it’s a recursive case that we need to add the indirect attribute before the keyword case. Alternatively, you can only add the indirect attribute before the keyword enum of the enumeration to indicate all cases of the enumeration are indirect.

Properties

Lazy Stored Properties

Access a lazy stored property in multiple threads simultaneously isn’t thread-safe.

If we access a lazy stored property of an instance in multiple threads simultaneously, the initialization of the property would be executed repeatedly.

Property Observers

What’s the observers calling order when we assign a new value to an override stored property of a subclass instance?

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
class Person {
var age = 0 {
didSet {
print("person's age was set to \(age)")
}
willSet {
print("person's age will be set to \(newValue)")
}
}
}

class Student: Person {
override var age: Int {
didSet {
print("student's age was set to \(age)")
}
willSet {
print("student's age will be set to \(newValue)")
}
}
}

let me = Student()
me.age = 100

// student's age will be set to 100
// person's age will be set to 100
// person's age was set to 100
// student's age was set to 100

As described in the example above, if we set a new value to the age property of an Student instance, the willSet property observer of the age property in Student(subclass) is invoked before it’s invoked in Person(superclass), but the didSet property observer is invoked in Student(subclass) after it’s invoked in Person(superclass).

The calling order is like this: willSet(subclass) -> willSet(superclass) -> didSet(superclass) -> didSet(subclass).

If we pass a value property as an inout parameter to a function, the property observers for that property will be invoked even though the function doesn’t change the property’s value in its body.

1
2
3
4
5
6
7
8
9
10
let me = Student()

func modify(age: inout Int) {}

modify(age: &me.age)

// student's age will be set to 0
// person's age will be set to 0
// person's age was set to 0
// student's age was set to 0

This is because the implementation principle of the inout parameter. The value of the inout parameter will be written back to the property at the end of the function. The writing operation would trigger the property observers for the property.

Property Wrapper

You can use a structure, class, or enumeration to define a property wrapper.

Usually, we would use a structure or class to implement a property wrapper, because they can have stored properties to as the storage of the wrappedValue.

If we don’t need to store the wrappedValue's value or the underlying storage system is global static or is a singleton like UserDefaults or DataBase, using enumerations maybe is an alternative choice.

When you apply a wrapper to a property, the compiler synthesizes code that provides storage for the wrapper and code that provides access to the property through the wrapper.

1
2
3
4
5
6
7
8
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}

The example above is from official documents. The TwelveOrLess property wrapper would limit the value of the Int property to max 12.
We can use it like below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12"

We use the TwelveOrLess wrapper to modify the two properties height and width of the SmallRectangle struct. When we build code, the compiler will automatically synthesize code for wrapped properties to provide access methods and private storage properties like below.

1
2
3
4
5
6
7
8
9
10
11
12
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}

As described in the example above, the wrapped properties will be converted to computed properties, and the value storing is actually supported by the stored properties of type TwelveOrLess(propertyWrapper). The underlying private stored properties are named with the wrapped properties names prefixed an underscore.

1
2
3
4
5
6
7
8
9
10
11
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int

func accessSynthesizedProperties() {
print(_height, _width)
}
}

SmallRectangle().accessSynthesizedProperties()
// TwelveOrLess(number: 0) TwelveOrLess(number: 0)

As you can see, we can directly access the _height and the _width properties. They are of type TwelveOrLess.

How to set up initial values for wrapped properties?

Int the example above, we can not customize an initial value for the wrapped value. Before learning set up an initial value, let’s take a look at how the propertyWrapper is instanced, because the initial value you passed is actually stored to the underlying property wrappers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@propertyWrapper
struct SmallNumber {
private var number: Int
private var maximum: Int

var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}

init() {
number = 0
maximum = 12
}
}

If we just write a pure property wrapper type name for a property like below, the code synthesized by the compiler will call the wrapper’s init() initializer.

1
2
3
4
struct SmallRectangle {
@SmallNumber var height: Int
// private var _height = SmallNumber()
}

Of course, you can add other initializers for SmallNumber to pass some initial arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@propertyWrapper
struct SmallNumber {

// ...

init(number: Int, maximum: Int) {
self.number = number
self.maximum = maximum
}
}

struct SmallRectangle {
@SmallNumber(number: 10, maximum: 100) var height: Int
// private var _height = SmallNumber(number: 10, maximum: 100)
}

Alternatively, if you implements an init(wrappedValue:) initializer for the propertyWrapper, you can assign a initial value to the wrapped property as part of declaring the property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@propertyWrapper
struct SmallNumber {

// ...

init(wrappedValue: Int) {
self.number = wrappedValue
self.maximum = 12
}
}

struct SmallRectangle {
@SmallNumber var height: Int = 100
// private var _height = SmallNumber(wrappedValue: 100)
}

There is a complex usage that you can config the SmallNumber wrapper with custom arguments and assign an initial value to the property in one single declaring code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@propertyWrapper
struct SmallNumber {

// ...

init(wrappedValue: Int, maximum: Int) {
number = wrappedValue
self.maximum = maximum
}
}

struct SmallRectangle {
@SmallNumber(maximum: 100) var height: Int = 80
// private var _height = SmallNumber(wrappedValue: 80, maximum: 100)
}

If the initializer has an parameter named wrappedValue, the initial value to the wrapped property will be pass to the initializer as the wrappedValue parameter’s argument.

Using projected values to expand property wrappers functionality.

Projected values are similar as wrapped values. You can declare a property named projectedValue of any type you need and then use the dollar sign $ followed the property’s name to access its projected value.

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
@propertyWrapper
struct SmallNumber {
private var number: Int
private var maximum: Int

var wrappedValue: Int {
get { return number }
set {
number = min(newValue, maximum)
projectedValue += 1
}
}

private(set) var projectedValue: Int = 0

init() {
number = 0
maximum = 12
}

init(wrappedValue: Int, maximum: Int) {
number = wrappedValue
self.maximum = maximum
}
}

We add a property named projectedValue in SmallNumber to record how many times the wrapped property has been changed.

1
2
3
4
5
var rectangle = SmallRectangle()
(0...10).forEach {
rectangle.height = $0
}
print(rectangle.$height) // 11
  • From Swift 5.4 onwards the property wrapper can be used in parameters of functions. Although the parameters of functions are let constants wil limit the power of properties wrappers, but we can also utilize the all features like using in properties or variables.

Global and Local Variables

All the computing, observing and property wrappers can be used in global or local variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct ValueBox {
var value: Any
init(_ value: Any) {
self.value = value
}
}

var globalStoredVariable: ValueBox = ValueBox(100)
var globalComputedVariable: ValueBox {
get { globalStoredVariable }
set { globalStoredVariable = newValue }
}

func someFunction() {
var localStoredVariable: ValueBox = ValueBox(200)

var localComputedVariable: ValueBox {
get { localStoredVariable }
set { localStoredVariable = newValue }
}

@SmallNumber(maximum: 120)
var age: Int = 27
}

As you can see, we separate variables to four kinds, globalStoredVariable, globalComputedVariable, localStoredVariable and localComputedVariable. Property wrappers can only be used in localStoredVariable, the global variables and computed variables are not supported.

Global constants and variables are always computed lazily, in a similar manner to lazy stored properties.

Type Properties

Stored type properties are always computed lazily

Stored type properties are always computed lazily, they are initialized on their fist access. Unlike lazy instance properties, Swift guarantee that they are initialized only once, even when accessed by multiple threads simultaneously.

Type Property Syntax

The differences between static and class when defining a type property.

We can use static to define a type property, stored or computed. If you want to override a computed type property in a subclass, you need to replace static to class.

Methods

The differences of the implicit self property between classes and structures.

The implicit property self in classes is a constant that you can’t assign a new value to it. But the self in structures or enumerations is a variable that you can assign a new value to it in a mutating method.

The differences between class and static when defining a type method.

Using the class keyword to replace of the static keyword to define a type method to make it can be overridden by subclasses.

Inheritance

Accessing superclasses Methods, Properties, and Subscripts

Where can we use the super keyword?

  • An overridden method, calling super.someMethod().
  • An overridden property, calling super.someProperty in getter or setter.
  • An overridden subscript, calling super[someIndex].

Overrides getter and setter for stored or computed properties inherited from superclasses.

The subclass doesn’t know the property’s nature is computed or stored when inheriting it from the superclass. So we can override properties of any type and don’t care whether its type is computed or stored.

However, we can only override a read-only property to be a read-write property, but we can not override a read-write property to be a read-only property.

Overrides property observers for variable stored properties or read-write computed properties.

The constant properties and read-only properties value can’t change, so they don’t need the willSet and didSet observers.

Preventing Overrides

Using the final keyword to prevent any override.

We can write the final modifier before the method, property, or subscript’s introducer keyword to prevent them from being overridden. The method, property, or subscript defined in an extension can also use the final modifier. Adding the final modifier before the class introducer can mark the class type that can’t be inherited.

Initialization

Setting initial values for Stored Properties

When you assign a default value to a stored property, or set its initial value within an initializer. The value of the property is set directly, without calling any property observers.

For class instances, a constant property can be modified during initialization only by the class that introduces it. It can’t be modified by a subclass.

Initializer Delegation for Value Types

Customizes an initializer for a structure and keeps its automatically generated memberwise initializer.

When we provide a custom initializer for structures or classes, the automatically generated initializer(memberwise initializer for structures) will be disabled. If you still want to access the default initializer, you can place your custom initializers in an extension for the type rather than declaring them in the type’s original implementation.

Initializer Delegation for Class Types

Initializer delegation rules

  1. A designated initializer must call a designated initializer from its immediate superclass.
  2. A convenience initializer must call an initializer from the same class.
  3. A convenience initializer must ultimately call a designated initializer.

Two-Phase Initialization

How to write a correct initializer?

To completely initialize an instance of a class needs through two phases. The first phase is allocating memory for the new instance and initializing all stored properties with a default value. The second phase is customizing properties further and then the instance is ready for use.

The detail checking rules in the official document.
Next, let me show you a standard example:

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Superclass {
let property1: Int
let property2: Int

init(property1: Int, property2: Int) {
self.property1 = property1
self.property2 = property2
}
}

class Subclass: Superclass {
let property3: Int
let property4: Int

init(property3: Int, property4: Int) {
// **Phase 1**
// You must initialize all stored properties introduced by current class.
self.property3 = property3
self.property4 = property4

// self.property2 = 10
// You can't access the properties inherited from superclasses,
// because these properties will later be overridden by the superclass initializer.

super.init(property1: 0, property2: 0)

// **Phase 2**
// you can now customize any property or call any instance method.

self.property1 = 0
self.property3 = 2
instanceMethod()
}

convenience init(value: Int) {
// **Phase 1**
// self.property3 = value
// In convenience initializers, you can't access any property, introduced by
// current class or inherited from superclasses, before delegating to another initializer.
self.init(property3: value, property4: value)

// *Phase 2*
// you can now customize any property or call any instance method.

self.property2 = 1
self.property4 = 3
instanceMethod()
}

private func instanceMethod() {
// Do something...
}
}
  1. A subclass designated initializer must initialize all stored properties introduced by itself before calling the superclass initializer.
  2. Before calling the superclass initializer, the subclass can’t access any inherited properties or instance methods.
  3. After then, you can begins customizing any property or calling instance methods you want.
  4. Within a subclass convenience initializer, you can’t access any property before calling another initializer.
  5. Then, you can customize whatever you want.

If an initializer doesn’t do any customization in phase 2, and the superclass has a zero-argument designated initializer, you can omit a call to super.init() after initializing all stored properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
var name: String = ""
}

class Student: Person {
var number: Int

init(number: Int) {
self.number = number

// super.init()
// You can ignore this line of code. The compiler will automatically synthesize it for you.
}
}

Initializer Inheritance and Overriding

By default, subclasses in Swift don’t inherit any initializer from its superclasses. If you want to use initializers that’s the same as the superclass, you can customize them again.

Constant properties can only be modified in initializers for the classes introduced them. So subclasses can only modify inherited variable properties during initialization, but can’t modify inherited constant properties.

Just like overriding properties, methods, or descriptions. If a subclass override a superclass initializer, it needs to add an override modifier before the initializer.

When you inherit a superclass designated initializer, you always need to write the override modifier before your overriding initializer, whether your implementation of it is designated or convenience.

Conversely, if you override a superclass convenience initializer, subclasses can’t directly access superclasses convenience initializers, so strictly speaking that you subclass doesn’t provide an override of the superclass initializer. Therefore, you don’t need writing the override modifier for your overriding initializers.

Automatic Initializer Inheritance

In some specific scenes, subclasses can inherit their superclass initializers.

Rule 1
If your subclass doesn’t define any designated initializer, it automatically inherits all of its superclass designated initializers.

Rule 2
If your subclass provides an implementation of all its superclass designated initializers, it automatically inherits all of its superclass convenience initializers.(satisfying the rule 1, or overriding all superclass designated initializers. Overriding superclass designated initializers to convenience initializers is also ok)

Overriding a Failable Initializer

You can override a failable initializer with a nonfailable initializer but not the other way around.

You can delegate from init! to init? and vise versa, and you can override init? with init! and vice versa. And also you can delegate from init to init! and visa versa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
init() {}
init?(name: String) {}
}
class Student: Person {
// By default, we can't override a nonfailable initializer with
// a failable initializer, but the implicit `init!` failable initializer
// is an exception.
override init!() {
super.init()
}
override init!(name: String) {
super.init(name: name )
}
}

Required Initializers

Write a required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer.

You don’t have to provide an override modifier when overriding a required designated initializer, you can think required init to required override init.

You don’t have to explicitly implement a required initializer if you can satisfy the requirement with an inherited initializer.

Optional Chaining

Calling Methods Through Optional Chaining

We know that a function without specifying a returned value type returns a Void(alias to empty tuple ()) value. Alternatively, the assignment operation also returns a Void value.

So we can compare a method call’s returned value against nil to see if the method call was successful.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
var pet: Pet?
}
class Pet {
var name: String?
func call() {}
}
let me = Person()
if me.pet?.call() != nil {
print("The method call of the person's pet was called.")
} else {
print("Didn't invoke the method call.")
}

Similarly, we can also compare the returned value of an assignment operation against nil to see if the property was set successfully.

1
2
3
4
5
if (me.pet?.name = "Raspberry") != nil {
print("Succeeded in assigning the value to the `name` property")
} else {
print("Failed to assign the value to the `name` property")
}

Linking Multiple Levels of Chaining

  1. If the type you are trying to retrieve isn’t optional, it will become an optional because of the optional chaining.
  2. If the type you are trying to retrieve is already optional, it won’t become more optional because of the chaining.

Concurrency

What’s the __consuming keyword?

Unused syntax feature StackOverflow

What’s the @rethrows keyword before definitions of protocols?

1
2
3
@rethrows public protocol AsyncSequence {
// Content...
}

If you want to executes a sequence of asynchronous tasks, you can call the asynchronous methods within a block scope of a loop method. Otherwise the more concise way is using the AsyncSequence type. Swift provides many extensions for common asynchronous types to conform to the AsyncSequence protocol. Then you can easily use the for await-in pattern to iterate a sequence of asynchronous tasks. Of course, you can custom you own types that conform to the AsyncSequence protocol.

Executing asynchronous tasks in parallel.

Using await to call synchronous tasks is sequential. But sometimes the multiple tasks are executing independently, so they can run at the same time to increase the processing performance. This time, you can select to use the await let pattern to execute several tasks in parallel.

1
2
3
4
5
6
7
8
func generateDiplomaImage() async throws -> UIImage {
await let diplomaTemplate = downloadDiplomaTemplate()
await let aQRCodeImage = downloadQRCodeImage()
let diploma = try await composite(diplomaTemplate, aQRCodeImage)
// let diploma = composite(try await diplomaTemplate, try await aQRCodeImage)
// You can fetch these parameters values separately.
return diploma
}

Type Casting

Type Casting for Any and AnyObject

The type Any represents values of any type, including optional types.

Swift gives you a warning if you use an optional value where a value of Any type is expected. If you really do need to use an optional value as an Any value, you can explicitly cast the optional to Any.

1
2
3
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning

Nested Types

Extensions

Extensions can add new functionality to a type, but they can’t override existing functionality.

Extensions can add new computed properties for a type, but they can’t add stored properties, or add any property observers to existing properties.

Extensions can’t override any properties or methods for a type.

Extensions can only expand new convenience initializers for a type, adding designated initializers and deinitializers aren’t allowed.

Protocols

Swift thinks that all protocols should be used as type constraints rather than type declarations. In fact, a type declared by a protocol will be implicitly cast to a existential that carries the protocol’s information.

In Swift 5.6, a new syntax any Protocol will be introduced to explicitly declare variables of existential types. Existential containers will erase all information about the actual type, you can only access methods or variables required by the protocol.
Erasing type information will prevent the performance improvement by Swift compiler, so we should always use generic types or opaque types to support Swift compiler to optimize code performance base on static type system.

Good news, in Swift 5.7, all protocols (having associated values or using Self) can be used as existential types, but remember do not abuse it.

Property Requirements

  1. A protocol can require a property to be gettable or gettable and settable.
  2. If a protocol require a property to be gettable and settable, the requirement can’t be satisfied by a constant stored property or a read-only computed property.
  3. If a protocol require a property to be gettable, the requirement can be satisfied by any kind of property. And it’s valid for the property to be also settable if this is useful for your own code.

Always using the static keyword to require a type property in protocols, you can implement it by using static or class freely in classes.

Mutating Method Requirements

A protocol instance method requirement marked with mutating.

  1. The adopting classes can ignore the mutating keyword.
  2. And the adopting value types(structures or enumerations) can also ignore the mutating keyword if their implementation to this method don’t mutate themselves instances when executing.
  3. If the protocol instance methods requirement doesn’t prefix with mutating, the implementation to this method in value types prefixed with mutating isn’t fulfilled to the requirement.

Initializer Requirements

A protocol initializer requirement requires that the adopting classes must mark their implementing initializer as required.

1
2
3
4
5
6
7
8
9
protocol InitWithName {
init(name: String)
}

class Person: InitWithName {
required init(name: String) {
//...
}
}

This rule can make sure all subclasses of the Person class perform to the InitWithName protocol.
If the class conforming to the protocol was marked as final(final classes can’t be inherited), you can omit the required modifier.
Note that a final subclass inherited from a superclass defined a required initializer has to override the required initializer with the required modifier

If a subclass overrides a designated initializer from its superclass, and also implements a matching initializer requirement from a protocol, mark the initializer implementation with both the required and override modifiers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protocol SomeProtocol {
init()
}

class SomeSuperClass {
init() {
// initializer implementation goes here
}
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
// "required" from SomeProtocol conformance; "override" from SomeSuperClass
required override init() {
// initializer implementation goes here
}
}

Protocol Extensions

Protocol extensions can add implementation to conforming types but can’t make a protocol extend or inherit from another protocol.

Generics

Type Constraints

When defining a generic type function, we can require type constraints to limit the selection scope of the generic types. As described in above, we know that we can also add type constraints when expanding an existing type.

Normally, we constrain a generic type by appending a colon(😃 following a type name or protocol composition that represents the generic type has to inherits from the class type or conforms to the protocol or protocol composition.

1
func someFunction<T: SomeClass & SomeProtocol>(a: T) {}

In extensions with type constraints, we can also write type constraints with the where clause. Similar to generic type constraints above, we can write a extension example like this:

1
extension Array where Element: SomeClass & SomeProtocol {}

This means that the type of elements of an array has to inherit from SomeClass and conform to SomeProtocol to use the additional functionality expanded by the extension.

In addition to the colon symbol, we can replace it with the equal to(==) operator in extension constraints. That means the Element type has to be SomeClass, not its subclasses.

Opaque Types

We know the Opaque Type's syntax is some SomeProtocol. It’s similar to Protocol but having completely different meaning. You use a protocol as a method’s return value means that this method can return values of various types conforming to the protocol. A function or method has an opaque return value means that they return a value of a certain type conforming to the protocol, but they hide the return value’s type information.

The biggest difference is that a protocol return value doesn’t have any certain type information-the actual return values type can be any conforming types, but an opaque return value takes the implicit type information the compiler can access-the method’s body can only return the same type values.

Why do we use an opaque return value to replace of a protocol one? A protocol value is clearly more flexible. Let’s see an example:

1
2
3
4
5
6
7
8
9
protocol Animal: Comparable {}

func catchAnAnimal() -> Animal { /*...*/ }

let oneAnimal = catchAnAnimal()
let anotherAnimal = catchAnAnimal()

if oneAnimal == anotherAnimal { // You get a compile error.
}

The method catchAnAnimal will return a value conforming to Animal. We call this method twice to return two values of Animal, but they can’t be compared by the compare == operator. Because the compiler can’t determine whether these two values are of the same type although they are both conforming to Animal. Swift is a type-safe language that you can’t compare two values of different types.
But if we replace the protocol value of an opaque values, we can satisfy the situation above.

1
func catchAnAnimal() -> some Animal { /*...*/ }

The method catchAnAnimal also returns a value that conforms to Animal, but the difference from Protocol is that the opaque return values from one method is always of the same type. The compiler make sure that all return branches of the method can only return values of the same type in compiling-time. So that we can compare values returned by the method catchAnAnimal returning opaque values above.

There is another special situation about that a value of a protocol doesn’t conform to the protocol itself. As shown in the example below:

1
2
3
4
5
6
struct Dog: Animal {}
func getAnAnimal<T: Animal>(from animal: T) -> Animal {
// ...
}

getAnAnimal(from: getAnAnimal(from: Dog())) // error: Protocol Animal as a type cannot conform to the protocol itself.

In the example above, you build the code will result in a compiling-time error. The error info is that Protocol Animal as a type cannot conform to the protocol itself. Why? It’s all because of the type-safe. The type T for the generic method is certain in compiling-time, so the animal parameter is of the certain type T not the protocol Animal. You can think the T is a type placeholder and it will be replaced to a certain type(Dog, Cat, or others). So if the method takes a Dog property but you pass it a value of Animal, there’s obviously an error of type matching.

If we do a little modifications for the method, giving it an opaque return value, you can build the code successfully. This is due to whether the opaque type or generic type are all type placeholders, they are representing one certain type at compile time.
Not like C++ or Rust, Swift doesn’t always specialize generic functions to certain type functions

In additional to the usages above, opaque values have another functionality. We know that protocols with associated values or using Self can’t be used as types. They can only be used in type constraints. This is because the protocol with different associated values are completely different types. When you pass an argument of protocol Container with an associated Element to a method, how do we use the elements of the container without type information about these elements?
If we use opaque values to replace result values of protocols with associated values of methods, the compiling can pass because of the types are constant and the compiler can get their type information.

1
2
3
4
5
6
7
8
9
10
11
protocol Container {
associatedtype Element
}

struct IntContainer: Container {
typealias Element = Int
}

func makeIntContainer() -> some Container {
IntContainer()
}

As you can see above, the type of some Container is a certain type. In compiling-time, you can argue that the compiler think of some Container as IntContainer.

Automatic Reference Counting

Keyword unowned(unsafe)

The unowned keyword has two different formats, unowned(safe) and unowned(unsafe). We write the unowned before a property or variable means that the instance assigned to the property or variable won’t be strong referenced. But we must sure the instance to that property refers isn’t deallocated when accessing.

The other format unowned(unsafe) is equivalent of Objective-C’s @property(assign) or __unsafe_unretained. It shouldn’t be used in Swift code, because it’s purpose is to bridge to code written in Objective-C.

An __unsafe_unretained value is a simple pointer, it won’t increase the referenced instance’s reference count just like using the weak keyword. But the only difference from weak is the pointer won’t be automatically set to nil when the value it refers to is deallocated. The benefit of using __unsafe_unretained is having more performance, because the runtime doesn’t need to record the pointer and set the pointer to nil in due time.

Unowned Optional References

We can declare a property of Optional as unowned. We know the unowned modifier can only be used for reference types, but Optional is an exception. A property declared with weak has to be a variable of Optional, because the property may be set to nil in run-time. Not like weak, an unowned optional property won’t be set to nil when the value it refers to is deallocated. It behaves the same as an unowned property with an extra implication that the property may be nil. If the value of an unowned optional property is Optional.none, you can safely access it whenever. But if its value is of Optional.some, you can think of it as a normal unowned property. Trying to unwrap an unowned optional property that’s referenced value has been deallocated will trigger a runtime error.

Defining a Capture List

Let’s firstly see an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
let name: String
var block: (() -> Void)?


init(name: String) {
self.name = name
print("A person \(name) is initializing.")
doTest()
}

private func doTest() {
block = {
_ = self.name // Capturing self will cause a strong reference cycle.
}
}

deinit {
print("A person \(name) is deiniting.")
}
}

AS you can see above, if we directly use self in the body of the block property of the instance, this will cause a reference cycle. Using a capture list can break the reference cycle, we usually write the capture list like this:

1
2
3
4
5
private func doTest() {
block = { [weak self] in
_ = self?.name
}
}

Using [weak self], the block will only weakly reference to self for preventing a strong reference cycle. But the weak self makes self in the block’s be an optional value, so we need to add a question mark after self before accessing it.

Unfamiliar usages for Capture List.

If we just want to use the name property in the block’s body, we can only write [name] in the capture list, and then we can directly use the name like a local variable in the block’s body. Another writing format is [name = self.name].

1
2
3
4
5
private func doTest() {
block = { [name] in
_ = name
}
}

Memory Safety

Keeping memory access safety in single thread.

Whenever we read the value of a variable or assign a new to the variable will access its location in memory. If a function or method gets a value’s write permission and another operation writes or reads the value at the same time, there is a memory conflict occurred. There are three key points that least one operation gets the write access, accessing the same location in memory, and their accessing durations overlap.

In our development routine, the inout parameters is the most possible place occurring memory conflicts.
We list the three commonest scenes:

  1. Simultaneously accessing an inout parameter’s location in memory in functions body.
1
2
3
4
5
6
7
8
9
var stepSize = 1

func increment(_ number: inout Int) {
// 1. The inout parameter number's accessing permission has been held until this function call ends.
number += stepSize // 2. Try to read the value of stepSize.
}

increment(&stepSize) // The operations of number 1 and 2 will access the value of stepSize simultaneously.
// Error: conflicting accesses to stepSize
1
2
3
4
5
6
7
8
9
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // OK
balance(&playerOneScore, &playerOneScore) // Error

See the last line of code, these two inout parameters refers to the same location in memory of the global variable playerTwoScore. The function will get the playerTwoScore's accessing permission twice at the same time. This will result in a memory conflict.

  1. Mutating functions in structures.

A function of structures marked with the mutating modifier means that the function can modify the structure itself when calling, so the mutating function will holds the structure’s accessing permission.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Player {
var name: String
var health: Int
var energy: Int

static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}

extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}

var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // OK
oscar.shareHealth(with: &oscar) // Error

As show in the code above, the searchHealth is a mutating function, it will hold the accessing permission of the player itself. If we pass the searchHealth function a inout parameter that refers to the same player, there is a memory conflict.

  1. If you pass a property of structures as a function’s inout parameter, the function gets the whole structure’s accessing permission.
1
2
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // Error

Access Control

Main guiding principle of declaring access levels

  1. The main guiding principle of declaring access levels is that the access level of your custom entities(methods, types) must be more or as restrictive as the access levels of its constituent types.
  2. If you state the whole custom type as private or fileprivate, all its properties, methods, or nested types are declared as private or fileprivate by default. But if you state the custom type as public or open, all its constituent’s access levels are internal rather than the corresponding access levels. This rule is used to prevent accidently leaking the detail implementation of your code’s functionality when your declare your code as public or open for creating an API.

The open access level can only be used in class types.

Custom Types

  1. Defining a custom type as private or file private, all its properties or methods are marked as private or file private.
  2. Defining a custom type as public or public, all its properties or methods are marked as internal rather than the corresponding access levels.

Tuple Types
A tuple type can’t define its own access level. The access level of a tuple type depends on the one of the lowest access level of its elements.

Function Types
A function’s highest access level depends on the most restrictive access level of its constituent types(property types and return value types). If you don’t explicitly state the function’s access level(defaults to internal) and the inferred access level of the function is lower than internal, the compiler will report an error that warns you to explicitly add private or fileprivate keywords for your function.

Enumeration Types
Similarly, an enumeration with a raw value type or associated values, its highest access level depends on the lowest access level of its constituent types, raw value type and associated values type.

Nested Types
A nested type’s access level is normally equal to its containing type. But if the containing type is declared as public, the nested type’s access level is internal by default. You can then explicitly add the public modifier for the nested type to upgrade its access level to public.

Subclassing

  1. A subclass’s access level must be lower than its superclass.
  2. The access level of any member of the class can have a higher level than the class itself.
  3. An overriding member of the subclass can have a higher access level than the overridden member of its superclass.
  4. There is an exception that the subclass can’t override the member as private if it’s fileprivate in the superclass.
Kinds Superclass Subclass
Class Type Any access level Lower
Members open/public/internal Any access level(equal, lower, or higher)
Members fileprivate fileprivate or higher(can’t be private)

Constants, Variables, Properties, and Subscripts

  1. A constant, variable, or property can’t be more public than its type.
  2. Similarly, a subscript’s access level can’t be public than either its index type or return type.

Default Memberwise Initializers for Structure Types
The access level of the default memberwise initializer of a structure depends depends on the lowest access level of its all stored properties. Having any private stored property, the memberwise initializer is considered as private. Likewise, having any fileprivate stored property, the memberwise initializer is considered as fileprivate.

Protocols

  1. The requirements of protocols can’t be separately defined to different access levels.
  2. If we can access a protocol in an access context, all the protocol’s implementations of the classes conforming to the protocol and visible in the context must be accessible.
  3. As described above, if a public class conforms to a public protocol, all its implements for the protocol’s requirements must be explicitly marked as public or more higher access levels(open, if can)

Protocol Inheritance
As with class inheritance, a sub protocol’s access level can’t be more public than its super protocol.

Extensions

  1. If you extend a public or internal type, the extension will be marked as internal.
  2. If you extend a private or fileprivate type, the extension will be marked as private or fileprivate
  3. You can explicitly specify the extension’s access level, as a result, all its extended properties, methods, and so on are marked as the specified access level.
  4. An extension conforming to a protocol can’t explicitly specify an access level, its access level is marked as equal to the protocol by default.

Type Aliases
The type alias access level can be lower or equal to the type it aliases.

An identifier prefixed with one underscore _ means its access level is internal even though explicitly marked as public.
An identifier prefixed with two underscores are reserved for the Swift compiler and standard library.

Advanced Operators

Two ways to define operators for you custom types.

  1. Global methods
1
2
3
4
5
6
7
8
9
10
11
12
13
struct Point {
var x: Int
var y: Int
}

func +(left: Point, right: Point) -> Point {
Point(x: left.x + right.x, y: left.y + right.y)
}

let a = Point(x: 1, y: 2)
let b = Point(x: 2, y: 3)

print(a + b) //#=> Point(x: 3, y: 5)
  1. Extensions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Point {
var x: Int
var y: Int
}

extension Point {
static func +(left: Point, right: Point) -> Point {
Point(x: left.x + right.x, y: left.y + right.y)
}
}

let a = Point(x: 1, y: 2)
let b = Point(x: 2, y: 3)

print(a + b)

We can’t overload the default assignment operator(=) and the ternary condition operator(a ? b : c).

You don’t specify a precedence for a prefix or postfix operator. However, if you apply both a prefix and postfix operator to the same operand, the postfix operator is applied first.

Result Builder

Link to Swift 5.4 New Feature ResultBuilder