Level: ★☆☆ Tag: “iOS” “Swift” “Convenient initialization” “Initialization” “uninitialization” author: Mu Ling Lo review: QiShare team


Initialize theInitialization

Initialization is the process of preparing an instance of a class, structure, or enumerated type. This process involves setting initial values of storage properties and initializing configuration items required by instances.

Sets the initial value for the storage property

Because all storage properties of a class or structure must have initial values after an instance of the class or structure is created, appropriate initial values must be set for all storage properties of the class or structure when it is defined. Storage properties cannot be left in indeterminate states (states with no initial values), otherwise the compiler will prompt us: Class ‘*’ has no initializers.

  1. Set the initial value for the storage property in the initialization method.
class Initializers { //! The default value is set when the property is declared var storeProperty: String ="Set the default value of the property when the variable store property is declared."
    let constantStoreProperty : String = "Set the default value of the property when the constant store property is declared."/ /! Properties are declared as optional types, and properties of optional types are automatically initialized to nil var optionalProperty: Array<Int>? }Copy the code
  1. Attribute declaration specifies the default attribute value as its initial value.
class Initializers {
    var storeProperty : String
    letconstantStoreProperty : String //! Properties are declared as optional types, and properties of optional types are automatically initialized to nil. You can set other values in the initialization method, or you can ignore them as needed. var optionalProperty : Array<Int>?init() {
        storeProperty = "Initial values for storage properties are set in initialization methods."
        constantStoreProperty = "Set the initial value of the constant store property in the initialization method"}}Copy the code

#### User-defined initialization

class Initializers {
    var storeProperty : String
    letconstantStoreProperty : String var optionalProperty : Array<Int>? // No parameter, no labelinit() {
        storeProperty = "Set the initial value of the storage property in the initialization method"
        constantStoreProperty = "Set the initial value of the constant store property in the initialization method"} / /! Init (prefixed prefix:String) {storeProperty = prefix +"Set the initial value of the storage property in the initialization method"
        constantStoreProperty = prefix + "Set the initial value of the constant store property in the initialization method"} / /! Init (_ prefix:String) {storeProperty = prefix +"Set the initial value of the storage property in the initialization method"
        constantStoreProperty = prefix + "Set the initial value of the constant store property in the initialization method"} / /! Multiple arguments with tags init(prefixed prefix:String, suffixed suffix:String) {storeProperty = prefix +"Set the initial value of the storage property in the initialization method" + suffix
        constantStoreProperty = prefix + "Set the initial value of the constant store property in the initialization method" + suffix
    }
    init(prefix:String,suffix:String) {
        storeProperty = prefix + "Set the initial value of the storage property in the initialization method" + suffix
        constantStoreProperty = prefix + "Set the initial value of the constant store property in the initialization method"+ suffix } //! Multiple parameters, no label init(_ prefix:String, _ suffix:String) {storeProperty = prefix +"Set the initial value of the storage property in the initialization method" + suffix
        constantStoreProperty = prefix + "Set the initial value of the constant store property in the initialization method" + suffix
    }
    class func usage() {/ /! Call: custom initialization method with parameters and parameter labelslet obj = Initializers("QiShare")
        print(obj.storeProperty + "\n"+ obj.constantStoreProperty) //! Call: custom initialization method with and without parameter labelslet obj1 = Initializers(prefixed: "hasArgumentLabels")
        print(obj1.storeProperty + "\n"+ obj1.constantStoreProperty) //! Call: multi-parameter, custom initialization method with parameter labelslet obj2 = Initializers(prefixed: "QiShare", suffixed: "end")
        print(obj2.storeProperty + "\n"+ obj2.constantStoreProperty) //! Call: multi-parameter, no-parameter tag custom initialization methodlet obj3 = Initializers("Qishare"."end")
        print(obj3.storeProperty + "\n" + obj3.constantStoreProperty)
    }
}
Copy the code

Default initialization method

Swift can provide default initialization methods for any structure or class, provided that all attributes in the structure or class are initialized and that the structure or class does not provide any initialization methods.

Method for initializing a member of a structure type

If structure types do not define any custom initializers, they automatically generate initializers that receive initial values for member attributes. Unlike the structure default initialization method, the structure type generates an initialization method that accepts initial values for member properties, even if there is no default value for a stored property of the structure.

Struct Size {width = 0.0 var height: Double} //! useletSize1 = size.init (width: 3.0, height: 3.0)letSize2 = Size (height: 3.0)print(size1)
Copy the code

Note: If you define a custom initialization method in a value type, you will no longer be able to access the default initialization method for that type, or the initialization method automatically generated by the structure to receive initialization values for member attributes. This constraint ensures the precedence of custom initialization methods.

Struct Size {var width = 0.0 var height: Double init(wid:Double,hei:Double) {width = wid height = hei}} // The original generated initialization method failsletSize1 = size. init(width: 3.0, height: 3.0Copy the code

Value type initialization method nested callsInitializer Delegation

Initializer Delegation: The part of a value type initialization method that calls other initialization methods to perform instance initialization. Purpose: Avoid duplicate code across multiple initialization methods. Note: Nested calls to initialization methods have different rules for value types and class types. Value types (structs and enumerations) do not involve inheritance and are relatively simple. Class types involve inheritance, and we need additional operations to ensure that instance object initialization is correct.

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)
    }
}
Copy the code

Class inheritance and initialization

All storage attributes of a class, including any attributes that the class inherits from its parent class, must be assigned initial values during initialization. Swift defines two initialization methods for class types: the designated initialization method and the convenient initialization method to help ensure that all storage properties of the class can be set to initial values.

Specify initialization methods and facilitate initialization methods

Specify initialization method: is the primary initialization method of the class, responsible for fully initializing all attributes of the class, and will call super to introduce the appropriate initialization method of the parent class. Convenience initialization methods: are secondary initialization methods of the class. Convenience initialization methods can define default values as parameter values of the initialization method call. Allows us to create an instance object with the given default value.

Syntax for specifying and facilitating initialization methods

Specify the initialization method:

init(`parameters`) {
}
Copy the code

The convenience initialization method: specify it using the convenience keyword

convenience init(`parameters`) {
    //statements
}
Copy the code

Class type initializer method nested callsInitializer Delegation

To simplify the relationship between Initializer and Delegation, Swift lays down three rules for Initializer Delegation:

  • A specified initializer must call the specified initializer of its immediate parent.
  • A convenience initialization method must call another initialization method of the same class.
  • The convenience initialization method must eventually call the specified initialization method.
class Animal {
    var name : String
    var kind : String = "Unknown"
    init(name:String = "[UnNamed]") { self.name = name } convenience init(name : String, kind : */ self.init(name: name) self.kind = kind}} class Dog: Animal { var nickName : String ="nihao"Init (name: String, kind: String) {super.init(name: name, kind: String) {super.init(name: name, kind: String) {super.init(name: name, kind: String); kind) // error:Must call a designated initializer of the superclass'Animal'
        super.init(name: name)
    }
}

Copy the code

Summary: Specifying the initialization method must always use the initialization of the super proxy parent up. The convenient initialization method must always use self horizontally to broker the initialization of the same class.

Note: These rules do not affect how classes are used when creating instances. Any of the initializer methods shown above can be used to create a fully initialized instance. Rules affect only how the class’s initial methods are implemented.

The following diagram illustrates how a specified initialization method can act as a chimney or funnel in the initialization process of a class through the complex inheritance of multiple classes.

Two phases of initialization

  • In the first stage, the initial states of all storage properties are set and initial values are assigned.
  • In the second phase, there is an opportunity to further customize its storage properties.

Swift’s compiler performs the following four security checks to ensure that both phases of initialization are completed without errors:

  • The specified initialization method must be guaranteed to be usedsuperInitialize all properties of the class before going up to the proxy parent initialization method.
  • Must be used before assigning a value to an inherited property in the specified initialization methodsuperUpproxy parent initialization method. Otherwise the new value of the inherited property will be called by the parentsuperInitialize the method and override it.
  • Before assigning a value to any property in a convenience initialization method, you must first call a specified initialization method of the same class. Otherwise it will be overwritten.
  • No instance methods of the class can be called in the initialization method to read any instance properties from the parent class until the first phase of initialization is complete. That is: cannot referenceselfAs an instance object of this class. To illustrate, here is an example:
class BaseClass {
    var property1 : String = "defaultvalue"
    var property2 : String
    init() {
        property1 = "property1"
        property2 = "property2"
    }
}
class SubClass: BaseClass {
    var property3 : String
    override init() {
        //property3 = "property3"//super.init() someInstanceMethod(property2) /* Error message: 1'self' used in method call 'someInstanceMethod' before 'super.init' call
         2.'self' used in property access 'property2' before 'super.init'Call */ someInstanceMethod(property3) /* Error message 1.'self' used in method call 'someInstanceMethod' before 'super.init' call
         2.Variable 'self.property3' used before being initialized
         */
    }
    func someInstanceMethod(_ : String) -> Void {
    }
}
Copy the code

The class instance is not fully valid until the end of the first phase.

Based on the above four security checks, the roles of the above two stages are summarized as follows:

Stage 1:

  1. Class’s designated or convenient initialization methods are called.
  2. Allocates memory for a new instance of the class. Memory is not initialized.
  3. The specified initialization method of the class verifies that all storage properties in the class have values. At this point, memory initialization of all stored properties is complete.
  4. The specified initialization method of the class passessuperDelegate the initialization method of the parent class to initialize all the storage properties of the parent class.
  5. The class continues to perform the same operations as in 4 along the inheritance chain, up to the top of the inheritance chain.
  6. When a class at the top of the inheritance chain ensures that all of its storage properties have values, it means that the instance’s memory has been fully initialized. So the first stage is complete.

Stage 2:

  1. Can be accessed in the initialization methodselfYou can modify its properties, call instance methods, and so on.
  2. The convenience initialization method allows you to select a custom instance and use itself.
class convenienceClass: Initializers {/* Relationship between Initializers and designated Initializers must call the designated initializer methods of the parent class. Convenient Initializers can only use 'self.init' proxy class Initializers instead of 'super.init' The convenience initialization method must eventually call the same specified initialization method */ var subClassStoreProperty: String override init(prefixed prefix:String) { subClassStoreProperty ="Properties of subclasses"
        super.init(prefix)
        storeProperty = prefix + "Set the initial value of the storage property in the initialization method"} // The parent class has an initialization method that requires' override 'convenience override init(_ name: String ="Subclass easy initialization prefix",_ suffix : String = "Subclass to facilitate initialization suffixes") { self.init(prefixed: name) //! < must be the same initialization method self.storeProperty ="Reassignment of storage properties in convenient initialization of subclasses"}}Copy the code

Initialization method inheritance and override

Subclass overrides or overrides the parent class initialization method: A subclass provides the same initialization method as the parent class. The Override modifier must be used. Note: Even if a subclass implements the specified initialization method of its parent class as a convenience initialization method, the Override modifier is required.

About inheritance: Unlike Objective-C, subclasses in Swift do not inherit their parent’s initialization methods by default. Inheritance occurs only in certain circumstances.

class Animal {
    var name : String
    init(name:String = "[UnNamed]") { self.name = name } } class Dog: Animal { var nickName : String init(Others :String) {self.nickname = others}} // An error occurs when calling the initialization method of the parent class. //let dog = Dog.init(name:"nihao")
let dog = Dog.init(others:"nihao")
print(dog.name) // print [UnNamed]Copy the code

Note: With respect to implicitly calling the parent’s initialization method, omit super.init() : when the parent’s specified initialization method has zero arguments, a subclass may omit super.init() after assigning an initial value to its storage property in its initialization method.

class Animal {
    var name : String = "defaultValue"// The initial values of the following arguments also comply with the 'zero argument' rule, and can be ignored when called. This method is also correct not to write. init(name:String ="[UnNamed]") { self.name = name } } class Dog: Animal { var nickName : String init(others:String) {self.nickname = others //super.init(),}} // calllet animals = Dog.init(others: "Dog")
print(animals.name) // [UnNamed]
Copy the code

Automatic inheritance initialization method

The automatic inheritance initialization method means that override is not required for overrides. Premise: New attributes introduced by subclasses ensure that default values are provided. There are two rules to follow based on the premises:

  • Rule 1: If a subclass does not define any specified initializers, the subclass automatically inherits all specified initializers from its parent class, as well as the convenience initializers.
class Animal {
    var name : String = "defaultValue"
    var kind : String = "Unknown"
    init(name:String = "[UnNamed]") {
        self.name = name
    }
    convenience init(name : String, kind : String) {
        self.init(name: name)
        self.kind = kind
    }
}
class Dog: Animal {
    var nickName : String = "defaultValue"} // Subclasses use a convenient initialization method inherited from their parent classlet animals = Dog.init(name: "Dog", kind: "Dogs"// Subclasses use the specified initialization methods inherited from their parent classlet animals1 = Dog.init(name: "Dog")
print(animals.name,animals1.kind)//! The dog is UnknownCopy the code
  • Rule two: If a subclass provides implementations of all the initialization methods specified by its parent class. The subclass automatically inherits all of the parent class’s convenient initialization methods. It is also ok to add our own custom parts to the implementation of all the specified initializers specified by subclasses to their parent classes.
class Animal {
    var name : String = "defaultValue"
    var kind : String = "Unknown"
    init(name:String = "[UnNamed]") {
        self.name = name
    }
    init(name2:String) {
        self.name = name2
    }
    convenience init(name : String, kind : String) {
        self.init(name: name)
        self.kind = kind
    }
}
class Dog: Animal {
    var nickName : String
    override init(name:String = "[UnNamed]") {
        self.nickName = "Default nickname"
        super.init(name: name)
        self.name = "Custom implementation as: Husky"
    }
    override init(name2:String) {
        self.nickName = "Default nickname"
        super.init(name2: name2)
        self.name = "Custom implementation as: Husky 2"}} // Subclasses use a convenient initialization method inherited from their parent classlet animals = Dog.init(name: "Dog", kind: "Dogs"// Subclasses use the specified initialization methods inherited from their parent classlet animals1 = Dog.init(name: "Dog")
print(animals.name,animals1.kind)//! < Custom implementation is: Husky UnknownCopy the code

Note that a subclass can implement the initialization method specified by its parent class as a subclass convenience initialization method, which also satisfies rule 2.

class Animal {
    var name : String = "defaultValue"
    var kind : String = "Unknown"
    init(name:String = "[UnNamed]") {
        self.name = name
    }
    init(name2:String) {
        self.name = name2
    }
    convenience init(name : String, kind : String) {
        self.init(name: name)
        self.kind = kind
    }
}
class Dog: Animal {
    var nickName : String
    convenience override init(name:String = "[UnNamed]") {
        self.init(name2: name)
        self.nickName = "Default nickname"
        self.name = "Custom implementation as: Husky"
    }
    override init(name2:String) {
        self.nickName = "Default nickname"
        super.init(name2: name2)
        self.name = "Custom implementation as: Husky 2"}} // Subclasses use a convenient initialization method inherited from their parent classlet animals = Dog.init(name: "Dog", kind: "Dogs"// Subclasses use the specified initialization methods inherited from their parent classlet animals1 = Dog.init(name: "Dog")
print(animals.name,animals1.kind)//! < Custom implementation is: Husky UnknownCopy the code

Specify and facilitate initialization methods in practice

The following example defines three classes, Food, RecipeIngredient, and ShoppingListItem, which are inheritance relationships. Will be used to demonstrate the interaction between designated initializers, facilitated initializers, and automatic inherited initializers.

class Food {
    var name: String
    init(name: String) {
        self.name = name
    }
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? "✔" : "✘"
        return output
    }
}
Copy the code

The roles between initialization methods are explained as follows:

The Food base class defines a specified initialization method init(name: String) and a convenience init() method.

RecipeIngredient inherited from Food:

  1. Defines its own initialization methods:init(name: String, quantity: Int)In which the proxy implements the initialization of the parent classsuper.init(name: name). This conforms to the first stage of initialization: set the initial state of all storage properties and assign initial values.
  2. Implement the specified initialization method of the parent class as a convenience initialization method:override convenience init(name: String). The class can be initialized to ensure that the attributes are addedquantityInitial value; andinit(name: String)For the parent classFoodThe unique specified initialization method of. This satisfies rule two of automatically inheriting initializers: if a subclass provides implementations of all of the parent class’s specified initializers, the subclass automatically inherits all of the parent class’s convenient initializers. Therefore, this class inherits the convenient initialization method of its parent classconvenience init().

ShoppingListItem inherits from RecipeIngredient and does not provide any specified initialization method, which satisfies rule 1 of automatically inheriting initialization methods: If a subclass does not define any specified initializers, the subclass automatically inherits all of the specified initializers in its parent class, as well as the convenience initializers.

Thus, constructing instances of RecipeIngredient and ShoppingListItem can be done in the following three ways:

RecipeIngredient(),
RecipeIngredient(name: "Vinegar"),
RecipeIngredient(name: "Green Chinese onion"ShoppingListItem(), ShoppingListItem(name: ShoppingListItem)"Ginger"),
ShoppingListItem(name: "Eggs", quantity: 6)
Copy the code

The above illustration can be illustrated with a picture:

An initialization method that can fail

Use the init? To declare a class, structure, or enumeration initialization method that can fail. Such failures can be triggered by invalid initialization parameters, lack of necessary resources, and other conditions that prevent successful initialization.

Note that:

  1. You cannot use the same parameter types and names to define both failable and non-failable initialization methods.
  2. A failable initialization method creates an optional value of that type, passed in a failable initialization methodreturn nilTo indicate that the initialization failure condition was triggered.
class Animal { var name : String init? (name:String) {if name.isEmpty {
            return nil
        }
        self.name = name
    }
}
if let _  = Animal.init(name: "") {
    print("Initialization successful")}else {
    print("Initialization failed") / /! The < output}Copy the code

An initialization method for an enumeration type that can fail

enum ResponseStatus {
    case ResponseStatus(Int,String)
    case ResponseFail
    caseResponseSuccess init? (code: Int,des:String){ switch code {case 401:
             self = .ResponseStatus(code,des)
        case 500:
            self = .ResponseFail
        case 200:
            self = .ResponseSuccess
        default:
            returnNil}}} // callif let status = ResponseStatus.init(code: 401, des: "Not authorized") {
    switch status {
    case .ResponseStatus(let code, let status):
        print(code,status) //401 Unauthorized default:print("Failed.")}}Copy the code

A failable initialization method with an enumeration of original values

enum Direction: Int {
    caseEast = 0,west = 1, South = 2, north = 3if let dir = Direction.init(rawValue: 2) {
    print(dir.self) //! < south }else {
    print("Failed.")}Copy the code

Propagation of initialization failure

Value type, class type can use the delegate of the initialization method, can be the same kind of delegate, can also be a subclass delegate parent class, either way, if the initialization method we delegate causes the initialization failure, the whole initialization process will immediately fail, will not continue to execute. Note: a failable initialization method can also delegate a failable initialization method. With this approach, we need to add potential failed operations to the initialization process, otherwise it will not fail.

class Animal {
    var name : String = "defaultValue"Init (name:String) {self.name = name}} class Dog: Animal {var kind :String init? String) {if kind.isEmpty {
            returnNil} self.kind = kind super.init(name: name)}} // callif let _ = Dog.init(name: "", kind: "") {
    print("Initialization successful")}else {
    print("Initialization failed") / /! The < output}Copy the code

Override initialization methods that can fail

Subclasses can override initialization methods that their parent class can fail. Subclasses can also override it to use as an unfailable initialization method: meaning that the method in the parent class can fail, but when overridden, it cannot fail.

Note:

  1. A failable initialization method can be overwritten as a failable initialization method, but not vice versa.
  2. When overwriting an initialization method that can fail to an initialization method that cannot fail, and delegating an initialization method that can fail to a parent class, you need to force unpack the available initialization results of the parent class.
class Animal {
    var animalName : String = "defaultValue"init? (name:String) {if name.isEmpty { return nil }
        animalName = name
    }
}
class Dog: Animal {
    var kind : String
    override init(name:String) {
        kind = "defaultKind"// Handle the case where the parent class may fail to initializeif name.isEmpty {
            super.init(name: "[UnKnown]")!
        } else{ super.init(name: name)! }}} // calllet dog = Dog.init(name: "")
print(dog.animalName)
Copy the code

init!An initialization method that can fail

Define a failable initializer using the keyword init? . When defining a failable initialization method to create an optional instance of implicit unpacking of the corresponding type, use the keyword init! . Can in the init! Commissioned in init? And vice versa, you can rewrite init, right? For the init! Vice versa; You can also delegate init! In init. To do so, init! An assertion is triggered when initialization fails.

Required initialization methods

Use the required keyword to indicate that each subclass must implement the initialization method.

class SomeClass {
    required init() {}}Copy the code

When a subclass implements a required initialization method, it must also use the required keyword to indicate that the required initialization method also applies to other subclasses in the inheritance chain.

class SomeSubclass: SomeClass {
    required init() {
        //super.init()
    }
}
Copy the code

Note: An initialization method decorated with required need not be explicitly implemented if the inheritance conditions for the initialization method are met.

Use closures or functions to set default property values

If the default value of a stored property requires some customization or setting, you can use closures or global functions to provide a custom default value for that property. Each time a new instance of the type to which the property belongs is initialized, the closure or function is called and its return value is specified as the default value for the property.

Using the closure assignment form, the function looks like this:

class SomeClass {
    letSomeProperty: SomeType = {// 'someValue' must be of type SomeTypereturn someValue
    }()
}
Copy the code

Note: If you use closures to initialize properties, note that the rest of the instance is not initialized when the closure is executed. Means that no additional property values can be accessed from the closure, even if they have default values. Nor can you use the implicit self attribute and call any instance’s methods.

The initializationDeinitialization

The Deinitialization function is called prior to the object’s memory being freed. When an object ends its life cycle, the destructor is called to free memory. Memory management of instances is handled in Swift through automatic reference counting. Note:

  1. Destructors are called automatically before the instance frees memory; manual calls are not allowed.
  2. The destructor of the parent class is inherited by the child class and automatically called when the child class’s call to the implementation of the destructor ends. Even if a subclass does not provide its own destructor, the parent class’s destructor is always called.

Destructors are valid only for class types, using the deinit keyword. Writing:

deinit {
}
Copy the code

Resources: Swift 5.1 official programming guide


To learn more about iOS and related new technologies, please follow our official account:

You can add the following xiaobian wechat, and note to join the QiShare technical exchange group, xiaobian will invite you to join the QiShare technical Exchange Group.

QiShare(Simple book) QiShare(digging gold) QiShare(Zhihu) QiShare(GitHub) QiShare(CocoaChina) QiShare(StackOverflow) QiShare(wechat public account)

IOS View and export the project run logs, the use of Flutter Platform Channel and source code analysis and development do not cut map how to do? Vector icon (iconFont) Getting started with guide DarkMode, WKWebView, Apple login must be adapted? ATaller Strange dance weekly