Class inheritance and construction process
All stored attributes in the class, including all attributes inherited from the parent class, must be set to the initial value during the construction process.
Swift provides two types of class constructor to ensure that the storage attributes in all class instances can obtain the initial values, which are the specified constructor and convenience constructor.
Specify the constructor and constructor
Specify that the constructor is the most important constructor in the class. A specified constructor initializes all the attributes provided in the class and calls the constructor of the parent class based on the parent class chain to initialize the parent class.
Each class must have at least one specified constructor. In some cases, many classes meet this condition by inheriting the specified constructor in the parent class. For details, refer to the subsequent sections on Inheritance of the automatic constructor.
Constructor is a secondary constructor in the class. You can define a constructor to call the specified constructor in the same class and provide default values for its parameters. You can also define the constructor to create an instance for special purposes or specific input.
You should only provide convenience constructor for the class when necessary. For example, you can quickly call a specified constructor by using the constructor in some cases, it can save more development time and make the class construction process clearer and clearer.
Constructor chain
To simplify the call relationship between the specified constructor and the constructor, swift uses the following three rules to restrict proxy calls between constructor:
Rule 1
The specified constructor must call the specified constructor of its direct parent class.
Rule 2
The constructor must call other constructor defined in the same class.
Rule 3
The constructor must end by calling a specified constructor.
One easier way to remember is:
Indicates that the constructor must always forward to the proxy
The constructor must always be a horizontal proxy
These rules can be illustrated by the following example:
The parent class contains a specified constructor and two constructor. One constructor calls another constructor, which calls the unique specified constructor. This satisfies the rules 2 and 3 mentioned above. This parent class does not have its own parent class, so rule 1 is useless.
The subclass contains two specified constructors and one convenient constructor. The constructor must call either of the two specified constructor because it can only call other constructor in the same class. This satisfies the rules 2 and 3 mentioned above. The two specified constructors must call the unique specified constructors in the parent class. This satisfies Rule 1.
Note:
These rules do not affect how to use classes to create instances. Any constructor displayed in can be used to create a complete instance of the corresponding class. These rules only affect the definition of classes.
The following figure shows a more complex class hierarchy. It demonstrates that if the specified constructor acts as a "Pipeline" at the class level, the internal relationships between classes are simplified in the constructor chain of the class.
Two-stage construction process
The construction process of classes in swift contains two stages. In the first phase, each storage type attribute sets the initial value by introducing their class constructor. When each stored property value is determined, the second stage begins, giving each class A Chance To further customize their stored properties before the new instance is ready to use.
The use of two-stage constructor makes the constructor safer, and gives full flexibility to each class in the class hierarchy. The two-segment constructor prevents attribute values from being accessed before initialization. It also prevents attribute values from being accidentally assigned to different values by another constructor.
Note:
The two-segment construction process of SWIFT is similar to the construction process in objective-C. The main difference is that in Stage 1, objective-C assigns 0 or null values (for example, 0 or nil) to each attribute ). Swift's construction process is more flexible. It allows you to set custom initial values and handle situations where some attributes cannot use 0 or nil as the legal default values.
The swift compiler will execute four effective security checks to ensure the smooth completion of the two-segment constructor process:
Security check 1
The specified constructor must ensure that all attributes introduced by its class must be initialized before other constructor can delegate other constructor tasks to its parent class.
As mentioned above, the memory of an object can be fully initialized only after all its storage properties are determined. To meet this rule, the specified constructor must ensure that the attributes introduced by its class are initialized before it goes up to the proxy.
Security check 2
Specify that the constructor must first call the parent class constructor to the proxy and then set a new value for the inherited attributes. If this is not done, the new value assigned by the specified constructor will be overwritten by the constructor in the parent class.
Security check 3
The constructor must call other constructor in the same class by proxy and then assign new values to any attribute. If this is not done, the new value assigned by the constructor will be overwritten by other specified constructor in the same class.
Security check 4
The constructor cannot call any instance method, read the value of any instance attribute, or reference the value of self before completing the first phase of the constructor.
The following shows the construction process based on the preceding security check during the two-stage construction process:
Phase 1
A specified constructor or constructor is called;
The memory of the new instance is allocated, but the memory is not initialized yet;
Specify the constructor to ensure that the initial values are assigned to all storage properties introduced by the class. The memory to which the stored property belongs is initialized;
The specified constructor calls the constructor of the parent class to initialize the attributes of the parent class;
The process of calling the parent class constructor goes up the constructor chain until it reaches the top of the constructor chain;
When it reaches the top of the constructor chain and ensures that all the storage attributes contained in the instance have been assigned a value, the memory of this instance is considered to have been fully initialized. At this time, Phase 1 is complete.
Phase 2
From the top constructor chain down, the specified constructor of each constructor chain has the opportunity to further customize the instance. The constructor can access self, modify its attributes, and call the instance method.
Finally, the constructor In Any constructor chain can have the opportunity to customize instances and use self.
In this example, the constructor starts from calling a constructor In the subclass. This constructor cannot modify any attributes at this time. It delegates the constructor to the specified constructor in the same class.
As shown in security check 1, specifying the constructor will ensure that all subclass attributes have values. It then calls the specified constructor of the parent class and completes the construction of the parent class along the generator chain.
The specified constructor in the parent class ensures that all attributes of the parent class have values. Since there are no more parent classes to be built, you do not need to continue building the proxy up.
Once all attributes of the parent class have an initial value, the instance memory is considered to be fully initialized, and Phase 1 has been completed.
The specified constructor in the parent class now has the opportunity to further customize the instance (although it is not necessary ).
Once the specified constructor in the parent class completes the call, the sub-class constructor can execute more custom operations (Similarly, it is not necessary ).
Finally, once the specified constructor of the subclass completes the call, the convenience constructor that is initially called can perform more custom operations.
Constructor inheritance and overloading
Unlike the subclass in objective-C, The subclass in swift does not inherit the constructor of the parent class by default. Swift's mechanism prevents a simple constructor of a parent class from being inherited by a more professional subclass and is mistakenly used to create a subclass instance.
If you want to implement one or more constructors of the same parent class in the Custom subclass-perhaps to complete some custom constructor-you can provide and reload the same constructor as the parent class.
If the reloaded constructor is a specified constructor, You can reload its implementation in the subclass and call the constructor of the parent class in the constructor of the custom version.
If your overloaded constructor is a convenient constructor, you must call other specified constructor provided in the same class. For details about this rule, see constructor chain.
Note:
Unlike methods, attributes, and subscripts, you do not need to use the keyword override when reloading the constructor.
Inheritance of the automatic Constructor
As mentioned above, subclass does not inherit the constructor of the parent class by default. However, if specific conditions can be met, the parent class constructor can be automatically inherited. In practice, this means that for many common scenarios, you do not have to reload the constructor of the parent class, and inherit the constructor of the parent class at the minimum cost if possible.
If you want to provide default values for any new attribute introduced in the subclass, follow these two rules:
Rule 1
If the subclass does not define any specified constructor, it automatically inherits the specified constructor of all parent classes.
Rule 2
If the subclass provides the implementation of all the parent class specified constructors-whether inherited by Rule 1 or implemented by custom-it will automatically inherit the constructor of all the parent classes.
Even if you add more constructor In the subclass, these two rules still apply.
Note:
Subclass can partially meet rule 2 and use the subclass constructor to implement the specified constructor of the parent class.
Specify the constructor and constructor syntax
The syntax of the specified constructor of the class is the same as that of the simple constructor of the Value Type:
init(parameters) { statements}
The convenience keyword must be placed before the init keyword and separated by spaces:
convenience init(parameters) { statements}
Specify the constructor and convenience Constructor
The following example shows the inheritance of the specified constructor, constructor, and automatic constructor. It defines the class hierarchies that contain three types of food, recipeingredient, and shoppinglistitem, and demonstrates how their constructors interact with each other.
The base class in the class hierarchy is food, which is a simple class used to encapsulate the food name. The food class introduces a string type attribute named name, and provides two constructors to create a food instance:
class Food { var name: String init(name: String) { self.name = name } convenience init() { self.init(name: "[Unnamed]") }}
The class does not provide a default one-by-one member constructor, so the food class provides a specified constructor that accepts a single parameter name. This constructor can use a specific name to create a new food instance:
Let namedmeat = food (name: "bacon") // The Name Of namedmeat is "bacon"
The constructor Init (Name: string) in the food class is defined as a specified constructor, because it ensures that the stored attributes of all new food instances are initialized. The food class has no parent class, so the init (Name: string) constructor does not need to call Super. INIT () to complete the construction.
The food class also provides a convenient constructor Init () without parameters (). The init () constructor provides a default placeholder name for the new food. It calls the specified constructor Init (Name: string) defined in the same class through a proxy) and pass the value [unnamed] to the parameter name for implementation:
Let mysterymeat = food () // The Name Of mysterymeat is [unnamed]
The second class in the class level is the food subclass recipeingredient. The recipeingredient class builds a flavor in a recipe. It introduces the quantity attribute quantity of the int type (and the name attribute inherited from food), and defines two constructors to create a recipeingredient instance:
class RecipeIngredient: Food { var quantity: Int init(name: String, quantity: Int) { self.quantity = quantity super.init(name: name) } convenience init(name: String) { self.init(name: name, quantity: 1) }
The recipeingredient class has a specified constructor Init (Name: String, quantity: INT), which can be used to generate all attribute values of the new recipeingredient instance. The constructor first assigns the passed quantity parameter to the quantity attribute, which is also the only attribute introduced in recipeingredient. The constructor then delegates the task to the init (Name: string) of the parent class food ). This process meets the security check 1 in the two-stage construction process.
Recipeingredient also defines a convenient constructor Init (Name: string). It creates recipeingredient instances only through name. This constructor assumes that the quantity of any recipeingredient instance is 1, so you can create an instance without displaying the specified quantity. The definition of this constructor makes instance creation more convenient and convenient, and avoids repeated code to create multiple recipeingredient instances with a quantity of 1. This constructor simply Delegates tasks to the specified constructor provided in the same class.
Note that the convenient constructor Init (Name: string) of recipeingredient uses the same parameter as the init (Name: string) of the constructor specified in food. Although the recipeingredient constructor is a convenient constructor, The recipeingredient still provides the implementation of the specified constructor for all parent classes. Therefore, the recipeingredient can automatically inherit the constructor of all parent classes.
In this example, the parent class of the recipeingredient is food, which has a convenient constructor Init (). This constructor is also inherited by the recipeingredient. The inherited Init () function version is the same as the version provided by food, except that it proxies the task to the init (Name: string) of the recipeingredient version rather than the version provided by food.
All three constructors can be used to create a recipeingredient instance:
let oneMysteryItem = RecipeIngredient()let oneBacon = RecipeIngredient(name:"Bacon")let sixEggs = RecipeIngredient(name:"Eggs", quantity: 6)
The third and last class in the class level is the subclass of recipeingredient, which is called shoppinglistitem. This class constructs a seasoning that appears in the shopping list.
Each item in a shopping order always starts from the unpurchased status. To show this fact, shoppinglistitem introduces a Boolean attribute purchased, which defaults to false. Shoppinglistitem also adds a computing attribute description, which provides some text descriptions about the shoppinglistitem instance:
class ShoppingListItem: RecipeIngredient { var purchased = false var description: String { var output = "\(quantity) x \(name.lowercaseString)" output += purchased ? " ?" :" ?" return output }}
Note:
Shoppinglistitem does not define the constructor to provide initialization values for purchased, because the initial status of any item added to the shopping order is always not purchased.
Because it provides default values for all attributes introduced by itself and does not define any constructor, shoppinglistitem automatically inherits the specified constructor and constructor from all parent classes.
You can use all three constructors to create a new shoppinglistitem instance:
var breakfastList = [ ShoppingListItem(), ShoppingListItem(name: "Bacon"), ShoppingListItem(name: "Eggs", quantity: 6),]breakfastList[0].name = "Orangejuice"breakfastList[0].purchased = truefor item in breakfastList { println(item.description)}// 1 x orange juice ?// 1 x bacon ?// 6 x eggs ?
As mentioned above, a new array breakfastlist is created literally, which contains three new shoppinglistitem instances. Therefore, the array type can also be automatically deduced as shoppinglistitem []. After the array is created, the name of the first shoppinglistitem instance in the array is changed from [unnamed] to orange juice and marked as purchased. Next, we traverse each element of the array and print their description values to demonstrate that the current default status of all items has been assigned as expected.
Use closures and functions to set the default attribute values.
If the default value of a stored property requires special customization or preparation, you can use closures or global functions to provide custom default values for its properties. When an instance of the new type to which an attribute belongs is created, the corresponding closure or function is called, and their return values are assigned to this attribute as default values.
This type of closure or function generally creates a temporary variable with the same property type, and then modifies its value to meet the expected initial state, finally, the value of this temporary variable is returned as the default value of the property.
The following describes how to provide the default value for a closure:
Class someclass {Let someproperty: sometype = {// create a default value for someproperty in this closure // somevalue must be of the same type as return somevalue }()}
Note that the braces at the end of the closure are followed by an empty braces. This is used to tell swift to immediately execute this closure. If you ignore this pair of parentheses, it is equivalent to assigning the closure itself as a value to the attribute, rather than assigning the return value of the closure to the attribute.
Note:
If you use a closure to initialize the attribute value, remember that when the closure is executed, other parts of the instance are not initialized yet. This means that you cannot access other attributes in the closure, even if this attribute has a default value, it is not allowed. Similarly, you cannot use the implicit self attribute or call other instance methods.
The following example defines a checkerboard structure, which constructs the checker of a checkers game:
The checkers game is played on a 10x10 board with an alternate black and white grids. To present the game board, the checkerboard struct defines an attribute boardcolors, which is an array containing 100 boolean values. If the Boolean value of an element in the array is true, it indicates a corresponding Black Grid, and if the Boolean value is false, it indicates a corresponding white grid. The first element in the array represents the grid in the upper left corner of the board, and the last element represents the grid in the lower right corner of the Board.
The boardcolor array uses a closure to initialize and assemble color values:
struct Checkerboard { let boardColors: Bool[] = { var temporaryBoard = Bool[]() var isBlack = false for i in 1...10 { for j in 1...10 { temporaryBoard.append(isBlack) isBlack = !isBlack } isBlack = !isBlack } return temporaryBoard }() func squareIsBlackAtRow(row: Int, column: Int) -> Bool { return boardColors[(row * 10) + column] }}
When a new checkerboard instance is created, the corresponding value assignment closure is executed, and a series of color values are calculated and assigned to boardcolors as default values. The closure described in the above example calculates the appropriate color of each cell in the play board and saves these color values to a temporary array temporaryboard, this array is returned as the closure return value when the build is complete. The returned value is saved to boardcolors and can be queried through squareisblackatrow.
Let board = checkerboard () println (board. squareisblackatrow (0, column: 1) // output "true" println (board. squareisblackatrow (9, column: 9) // output "false"