忍者刘米米

15.Initialization & Deinitialization

Initialization

  • Initialization is the process of preparing an instance of a class, structure, or enumeration for use.
  • Unlike Objective-C initializers, Swift initializers do not return a value. Their primary role is to ensure that new instances of a type are correctly initialized before they are used for the first time.

Setting Initial Values for Stored Properties

  • Classes and structures must set all of their stored properties
  • When you assign a default value to a stored property, or set its initial value within an initializer, the value of that property is set directly, without calling any property observers.
  • set an initial value for a stored property within an initializer, or by assigning a default property value as part of the property’s definition.

Initializers

1
2
3
4
5
6
7
8
9
10
11
12
13
init() {
// perform some initialization here
}

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"

Default Property Values

1
2
3
struct Fahrenheit {
var temperature = 32.0
}

Customizing Initialization

Initialization Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Celsius {

var temperatureInCelsius: Double

init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}

init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}

let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0

let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

Parameter Names and Argument Labels

Swift provides an automatic argument label for every parameter in an initializer if you don’t provide one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Color {

let red, green, blue: Double

init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}

init(white: Double) {
red = white
green = white
blue = white
}
}

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required

Initializer Parameters Without Argument Labels

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Celsius {

var temperatureInCelsius: Double

init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}

init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}

init(_ celsius: Double) {
temperatureInCelsius = celsius
}
}

let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0

Optional Property Types

  • stored property that is logically allowed to have “no value”
  • Properties of optional type are automatically initialized with a value of nil, indicating that the property is deliberately intended to have “no value yet” during initialization.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SurveyQuestion {
var text: String
var response: String?

init(text: String) {
self.text = text
}

func ask() {
print(text)
}
}

let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")

cheeseQuestion.ask()
// Prints "Do you like cheese?"

cheeseQuestion.response = "Yes, I do like cheese."

Assigning Constant Properties During Initialization

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SurveyQuestion {
let text: String
var response: String?

init(text: String) {
self.text = text
}

func ask() {
print(text)
}
}

let beetsQuestion = SurveyQuestion(text: "How about beets?")

beetsQuestion.ask()
// Prints "How about beets?"

beetsQuestion.response = "I also like beets. (But not with cheese.)"

Default Initializers

  • Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself.
  • The default initializer simply creates a new instance with all of its properties set to their default values.
1
2
3
4
5
6
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()

Memberwise Initializers for Structure Types

  • Structure types automatically receive a memberwise initializer if they do not define any of their own custom initializers.
  • Unlike a default initializer, the structure receives a memberwise initializer even if it has stored properties that do not have default values.
1
2
3
4
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

Initializer Delegation for Value Types

  • Initializers can call other initializers to perform part of an instance’s initialization. This process, known as initializer delegation
  • Value types: initializer delegation is simple,they can only delegate to another initializer that they provide themselves.
  • Classes: ensuring that all stored properties they inherit are assigned a suitable value during initialization.
  • if you define a custom initializer for a value type, you will no longer have access to the default initializer .If you want your custom value type to be initializable with the default initializer and memberwise initializer, and also with your own custom initializers, write your custom initializers in an extension rather than as part of the value type’s original implementation.
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
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}

// custom initializers
struct Rect {

var origin = Point()
var size = Size()

init() {}

init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}

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)
}
}

let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

Class Inheritance and Initialization

  • All of a class’s stored properties—including any properties the class inherits from its superclass—must be assigned an initial value during initialization.
  • Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value. These are known as designated initializers and convenience initializers.

Designated Initializers and Convenience Initializers

Designated initialzer

  • Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
  • Must have one, normally one ,sometimes inherits one or more from superclass

Convenience initializer

  • Convenience initializers are secondary, supporting initializers for a class.
  • You do not have to provide convenience initializers if your class does not require them.

Syntax for Designated and Convenience Initializers

1
2
3
4
5
6
7
8
9
//  Designated Initializers
init(parameters) {
statements
}

// Convenience Initializers
convenience init(parameters) {
statements
}

Initializer Delegation for Class Types

Rule 1

A designated initializer must call a designated initializer from its immediate superclass.

Rule 2

A convenience initializer must call another initializer from the same class.

Rule 3

A convenience initializer must ultimately call a designated initializer.

A simple way to remember this is:

  • Designated initializers must always delegate up.
  • Convenience initializers must always delegate across.

Simple Example

Complicated example

Two-Phase Initialization

Two phase

Class initialization in Swift is a two-phase process.

  1. In the first phase, each stored property is assigned an initial value by the class that introduced it.
  2. each class is given the opportunity to customize its stored properties further

Swfit & Objective-C

Swift’s two-phase initialization process is similar to initialization in Objective-C. The main difference is that during phase .Objective-C assigns zero or null values (such as 0 or nil) to every property. Swift’s initialization flow is more flexible in that it lets you set custom initial values, and can cope with types for which 0 or nil is not a valid default value.

Safety check

Swift’s compiler performs four helpful safety-checks to make sure that two-phase initialization is completed without error:

  1. A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.(Stored property first then superclass)
  2. A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property. If it doesn’t, the new value the designated initializer assigns will be overwritten by the superclass as part of its own initialization.
  3. A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class). If it doesn’t, the new value the convenience initializer assigns will be overwritten by its own class’s designated initializer.
  4. An initializer cannot call any instance methods, read the values of any instance properties, or refer to self as a value until after the first phase of initialization is complete.

Phase 1

  • A designated or convenience initializer is called on a class.
  • Memory for a new instance of that class is allocated. The memory is not yet initialized.
  • A designated initializer for that class confirms that all stored properties introduced by that class have a value. The memory for these stored properties is now initialized.
  • The designated initializer hands off to a superclass initializer to perform the same task for its own stored properties.
  • This continues up the class inheritance chain until the top of the chain is reached.
  • Once the top of the chain is reached, and the final class in the chain has ensured that all of its stored properties have a value, the instance’s memory is considered to be fully initialized, and phase 1 is complete.

Phase 2

  • Working back down from the top of the chain, each designated initializer in the chain has the option to customize the instance further. Initializers are now able to access self and can modify its properties, call its instance methods, and so on.
  • Finally, any convenience initializers in the chain have the option to customize the instance and to work with self.

Initializer Inheritance and Overriding

  • Unlike subclasses in Objective-C, Swift subclasses do not inherit their superclass initializers by default.
  • If you want a custom subclass to present one or more of the same initializers as its superclass, you can provide a custom implementation of those initializers within the subclass.
  • You always write the override modifier when overriding a superclass designated initializer, even if your subclass’s implementation of the initializer is a convenience initializer.
  • you do not write the override modifier when providing a matching implementation of a superclass convenience initializer
  • Subclasses can modify inherited variable properties during initialization, but can not modify inherited constant properties.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)

Automatic Initializer Inheritance

Rule 1

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

Rule 2

If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

A subclass can implement a superclass designated initializer as a subclass convenience initializer as part of satisfying rule 2.

Designated and Convenience Initializers in Action

  • Classes do not have a default memberwise initializer

Basic class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}

let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"

let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"

2017013001

Second hierachy class

RecipeIngredient has nonetheless provided an implementation of all of its superclass’s designated initializers. Therefore, RecipeIngredient automatically inherits all of its superclass’s convenience initializers too.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}

// overrides a designated initializer from its superclass
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

2017013002

Third hierachy class

Because it provides a default value for all of the properties it introduces and does not define any initializers itself, ShoppingListItem automatically inherits all of the designated and convenience initializers from its superclass.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}

var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘

Failable Initializers

  • You write a failable initializer by placing a question mark after the init keyword (init?).
  • You cannot define a failable and a nonfailable initializer with the same parameter types and names.
  • A failable initializer creates an optional value of the type it initializes. You write return nil within a failable initializer to indicate a point at which initialization failure can be triggered.
  • Strictly speaking, initializers do not return a value. Rather, their role is to ensure that self is fully and correctly initialized by the time that initialization ends. Although you write return nil to trigger an initialization failure, you do not use the return keyword to indicate initialization success.
  • Checking for an empty string value (such as “” rather than “Giraffe”) is not the same as checking for nil to indicate the absence of an optional String value. In the example below, an empty string (“”) is a valid, nonoptional String.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}

let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal

if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"

let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal

if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"

Failable Initializers for Enumerations

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
enum TemperatureUnit {
case kelvin, celsius, fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .kelvin
case "C":
self = .celsius
case "F":
self = .fahrenheit
default:
return nil
}
}
}

let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

Failable Initializers for Enumerations with Raw Values

  • Enumerations with raw values automatically receive a failable initializer, init?(rawValue:)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum TemperatureUnit: Character {
case kelvin = "K", celsius = "C", fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

Propagation of Initialization Failure

  • A failable initializer of a class, structure, or enumeration can delegate across to another failable initializer from the same class, structure, or enumeration. Similarly, a subclass failable initializer can delegate up to a superclass failable initializer.
  • A failable initializer can also delegate to a nonfailable initializer. Use this approach if you need to add a potential failure state to an existing initialization process that does not otherwise fail.
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
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}

class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil }
self.quantity = quantity
super.init(name: name)
}
}

if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"

if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"

Overriding a Failable Initializer

  • You can override a superclass failable initializer in a subclass, just like any other initializer
  • Alternatively, you can override a superclass failable initializer with a subclass nonfailable initializer. the only way to delegate up to the superclass initializer is to force-unwrap the result of the failable superclass initializer.
  • You can override a failable initializer with a nonfailable initializer but not the other way around.
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
class Document {
var name: String?
// this initializer creates a document with a nil name value
init() {}
// this initializer creates a document with a nonempty name value
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}

class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}

// forced unwrapping in an initializer to call a failable initializer from the superclass

class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}

The init! Failable Initializer

  • You typically define a failable initializer that creates an optional instance of the appropriate type by placing a question mark after the init keyword (init?).
  • define a failable initializer that creates an implicitly unwrapped optional instance of the appropriate type. Do this by placing an exclamation mark after the init keyword (init!) instead of a question mark.
  • You can delegate from init? to init! and vice versa,
  • you can override init? with init! and vice versa.
  • You can also delegate from init to init!, although doing so will trigger an assertion if the init! initializer causes initialization to fail.

Required Initializers

  • Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer:
  • You do not write the override modifier when overriding a required designated initializer:
1
2
3
4
5
6
7
8
9
10
11
class SomeClass {
required init() {
// initializer implementation goes here
}
}

class SomeSubclass: SomeClass {
required init() {
// subclass implementation of the required initializer goes here
}
}

Setting a Default Property Value with a Closure or Function

  • If you omit these parentheses, you are trying to assign the closure itself to the property, and not the return value of the closure.
  • If you use a closure to initialize a property, remember that the rest of the instance has not yet been initialized at the point that the closure is executed. This means that you cannot access any other property values from within your closure, even if those properties have default values. You also cannot use the implicit self property, or call any of the instance’s methods.
1
2
3
4
5
6
7
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}

game board 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

struct Chessboard {
let boardColors: [Bool] = {
var temporaryBoard = [Bool]()
var isBlack = false
for i in 1...8 {
for j in 1...8 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squareIsBlackAt(row: Int, column: Int) -> Bool {
return boardColors[(row * 8) + column]
}
}

let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"

Deinitialization

  • A deinitializer is called immediately before a class instance is deallocated.
  • Deinitializers are only available on class types.

How Deinitialization Works

  • Swift automatically deallocates your instances when they are no longer needed.(ARC)
  • However, when you are working with your own resources, you might need to perform some additional cleanup yourself. For example, if you create a custom class to open a file and write some data to it, you might need to close the file before the class instance is deallocated
  • Deinitializers are called automatically, just before instance deallocation takes place. You are not allowed to call a deinitializer yourself.
  • Superclass deinitializers are inherited by their subclasses, and the superclass deinitializer is called automatically at the end of a subclass deinitializer implementation.
  • Superclass deinitializers are always called, even if a subclass does not provide its own deinitializer.
  • Class definitions can have at most one deinitializer per class. The deinitializer does not take any parameters and is written without parentheses:
1
2
3
deinit {
// perform the deinitialization
}

Deinitializers in Action

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
54
55
class Bank {
static var coinsInBank = 10_000

static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}

static func receive(coins: Int) {
coinsInBank += coins
}
}

class Player {

var coinsInPurse: Int

init(coins: Int) {
coinsInPurse = Bank.distribute(coins: coins)
}

func win(coins: Int) {
coinsInPurse += Bank.distribute(coins: coins)
}

deinit {
Bank.receive(coins: coinsInPurse)
}
}

// An optional variable is used here, because players can leave the game at any point. The optional lets you track whether there is currently a player in the game.
var playerOne: Player? = Player(coins: 100)

print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// Prints "A new player has joined the game with 100 coins"

print("There are now \(Bank.coinsInBank) coins left in the bank")
// Prints "There are now 9900 coins left in the bank"

playerOne!.win(coins: 2_000)

print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// Prints "PlayerOne won 2000 coins & now has 2100 coins"

print("The bank now only has \(Bank.coinsInBank) coins left")
// Prints "The bank now only has 7900 coins left"

playerOne = nil

print("PlayerOne has left the game")
// Prints "PlayerOne has left the game"

print("The bank now has \(Bank.coinsInBank) coins")
// Prints "The bank now has 10000 coins"