Reference: 52 Effective Ways to Write High Quality iOS and OS X Code in Effective Objective-C 2.0, Article 16: Provide a “universal initialization method.” Although it is written in OC initialization method writing rules and considerations, from this you can understand why Swift should know the specified constructor and convenience constructor, why the call chain of the specified constructor is maintained, and why there are so many rules for constructor inheritance. Swift enforces these writing rules and considerations.

Default constructor

Class does not have a default one-by-one initializer. Swift provides a default constructor for all attributes of a class if they have default values

init()Copy the code

When ClassName() is used to create an object, the init() method with no arguments is called by default. When a class has attributes that are not initialized, the constructor must be specified to initialize them.

Specify the constructor and convenience constructor

If there is more than one way to create a class, then that class has multiple constructors, which is fine. However, you still select a constructor in between as the designated constructor and let the other constructors (convenience constructors) call it. NSDate is an example:

open class NSDate : NSObject, NSCopying, NSSecureCoding { public init() public init(timeIntervalSinceReferenceDate ti: TimeInterval) public init? (coder aDecoder: NSCoder) } extension NSDate { ... public convenience init(timeIntervalSinceNow secs: TimeInterval) public convenience init(timeIntervalSince1970 secs: TimeInterval) public convenience init(timeInterval secsToBeAdded: TimeInterval, since date: Date) }Copy the code

As mentioned in the document, in several initialization method above, public init (timeIntervalSinceReferenceDate ti: TimeInterval is a specified constructor, that is, it is called by all other convenience constructors, so the internal data is stored in the specified constructor, so that when the underlying data storage mechanism changes, you just need to modify the code in this constructor without changing the other convenience constructors.

Specify the internal structure of the constructor:

init(...) {//step1: Initializes its own properties... //step2: Call the specified constructor of the parent class... //step3: do what you want to do, such as modify the parent class attributes, call object methods, etc. . }Copy the code

Convenient constructor internal structure

convenience init(...) {//step1: call the specified constructor of the same class... //step2: do what you want to do, such as modify attribute values, call object methods, etc. . }Copy the code

Specify the call relationship between the constructor and the convenience constructor:

  • All attributes are initialized in the specified constructor
  • The specified constructor must call the specified constructor of its immediate parent
  • Property values cannot be accessed or modified by calling any object method until the property has been fully initialized.
  • The convenience constructor cannot access and modify property values until the same class specified constructor is called

Two-paragraph constructor:

  • Phase 1: Each storage property gets an initial value
  • The second stage: further customize the values of the stored properties

Example: Define a class that represents a rectangle:

Class EOCRectangle {rectangle width: Float rectangle height: Float Float) {// Initialize all attributes self.width = width self.height = height}}Copy the code

We want to have to provide width and height to create the rectangle, and when we customize the specified constructor init(withWidth width: Float, height: Float), we cannot use the init() constructor to initialize it. If we want to create an object using EOCRectangle(), which gives us the default width and height, we can do this in one of two ways:

// Change init() to a convenience constructor by calling the specified constructor of the same class {self.init(withWidth: 10, height: 20)}Copy the code

Method 2:

Init () {self.width = 10 self.height = 20}Copy the code

The first is recommended because when the underlying data storage mechanism changes, init(withWidth width: Float, height: Float) needs to be modified without changing init(), such as storing width and height in a structure instead.

Rectangle = rectangle; rectangle = rectangle; rectangle = rectangle; rectangle = rectangle

Class EOCSquare: EOCRectangle {init(withDimension Dimension: Float) {class EOCRectangle: EOCRectangle {init(withDimension Dimension: Float) { dimension, height: dimension) } }Copy the code

Custom specified constructor that specifies the height and width must be equal to initialize. At this point, EOCSquare objects can only be created externally using EOCSquare(withDimension: 10). The parent’s specified constructor init(withWidth width: Float, height: Float) is not available. If we want init(withWidth width: Float, height: Float) to still work, but take the maximum of width and height as the side length:

override init(withWidth width: Float, height: Float) {
        let dimension = max(width, height)
        super.init(withWidth: dimension, height: dimension)
    }Copy the code

Method 2:

convenience override init(withWidth width: Float, height: Float) {
        let dimension = max(width, height)
        self.init(withDimension: dimension)
    }Copy the code

What these two ways of writing have in common:

  • Overrides the constructor of the parent classinit(withWidth width: Float, height: Float)
  • allAutomatically inherits the parent class’s convenience constructorconvenience init()

The difference between:

  • The first way is to rewrite it as a specified constructor, and the second way is to rewrite it as a convenience constructor, and externally there is no difference, except that the specified constructor calls the specified constructor of the parent class, and the convenience constructor calls the specified constructor of its own class.

EOCSquare can now be initialized in the following three ways:

EOCSquare() // Call the parent's convenience constructor. Width: 20 height:20 Width :30 height:30 EOCSquare(withDimension:25) // Call a custom constructor width:25 height:25Copy the code

From the above example, we can conclude the inheritance rules for specifying constructors and convenience constructors:

  • If a subclass does not have a custom constructor, it inherits all of its parent class’s specified constructors.
  • A subclass defines a custom constructor and does not inherit any constructor from its parent class.
  • When a subclass implements all of its parent’s specified constructors (including the first one), it automatically inherits all of its parent’s convenience constructors.

Write multiple specified constructors

This can happen, for example, if there are two completely different ways of creating an instance of an object that must be handled separately. Take the NSCoding protocol as an example. This protocol provides a serialization mechanism by which an object can indicate its encode and decode methods. UIKit uses this mechanism to serialize objects and save them to an ‘NIB’ file in XML format. The system decodes the view controller during unarchiving. The NSCoding protocol defines the following initialization method, which should be implemented by all who comply with the protocol:

public init? (coder aDecoder: NSCoder)Copy the code

We typically implement this method without calling the usual specified constructor because it relies on aDecoder (aDecoder) to decompress the object data, unlike normal initialization methods. And if the parent class also implements the NSCoding protocol, then the init(coder:) method of the superclass needs to be called, so strictly speaking, two specified constructors appear in this case. For the EOCRectangle example, the code looks like this:

class EOCRectangle:NSObject, NSCoding { var width: Float var height: Float convenience override init() { self.init(withWidth: 10, height: 20) } init(withWidth width: Float, height: Float) { self.width = width self.height = height } required init? (coder aDecoder: NSCoder) { self.width = aDecoder.decodeFloat(forKey: "width") as Float self.height = aDecoder.decodeFloat(forKey: "height") as Float } func encode(with aCoder: NSCoder) { aCoder.encode(width, forKey: "width") aCoder.encode(height, forKey: "height") } }Copy the code
class EOCSquare: EOCRectangle { init(withDimension dimension: Float) { super.init(withWidth: dimension, height: dimension) } override convenience init(withWidth width: Float, height: Float) { let dimension = max(width, height) self.init(withDimension: dimension) } required init? (coder aDecoder: NSCoder) { super.init(coder: aDecoder) } }Copy the code

Notice if EOCSquare does not write required init? (Coder aDecoder: NSCoder) Compilations report errors because if a subclass needs to add an initialization method that is different from its parent class, it must first implement the parent class’s initialization method modified with the required modifier, and also use the required modifier instead of Override.

The specified constructor of each subclass must use the corresponding specified constructor of the parent class, layer by layer. Maintains a chain of calls to the specified constructor.

To summarize, the Swift constructor dictates that we must call the correct initialization method to initialize our objects, such as using EOCRectangle(withWidth:20, height:). 20) If you want to create a rectangle this way by using EOCRectangle() instead of overwriting init() in your class, you will get an error because there is no such constructor.

Implementation of these rules in OC

  • For multiple initializers of a class, one is selected as the universal initializer, and the other initializers call it
  • The universal initializer of a subclass calls the universal initializer of the superclass
  • If subclassOmnipotent initialization methodUnlike the name of the superclass method, the superclass’s universal initialization method should always be overridden, either:
    • Use a subclass’s omnipotent initialization method in overridden methods
    • Throw exceptions, which are mandatory to be constructed using the universal initialization method of subclasses

This maintains the chain of calls to the omnipotent initializer method to ensure proper construction.