swift – extension

Extensions can add new functionality to an existing class, structure, enumeration, or protocol. It also has the ability to extend without accessing the source code of the type being extended (that is, reverse modeling). Extensions are very similar to objective-C classification. (Unlike objective-C classification, Swift extensions are nameless.)

The role of Extensions

  1. Add computed Instance Properties and computed Type Properties Add computed instance properties and computed class properties
  2. Define Instance methods and Type methods Define instance methods and class methods
  3. Provide New initializers
  4. Define subscripts Defines subscripts
  5. Define and use new nested types
  6. Make an existing type conform to a protocol

Extensions can add new functionality to a type, but cannot override existing functionality.

1. Use the extension keyword to declare the extension

Extension SomeType {// add a new function to SomeType}Copy the code

2. Extensions extend an existing type by adding one or more protocols to it. Protocol names are written the same way as classes or structs:

Extension SomeType: SomeProtocol, AnotherProtocol {// extension SomeType: SomeProtocol, AnotherProtocol}Copy the code

Computational attribute extensions can add computational instance attributes and computational class attributes to existing types. This example adds five computational instance attributes to Swift’s built-in Double to provide basic support for working with distance units

Extension Double {var km: Double {return self * 1_000.0} var m: Double {return self} var cm: Double {return self / 100.0} var mm: Double {return self / 1_000.0} var ft: Double {return self / 3.28084}} let oneInch = 25.4.mm print("One inch is \(oneInch) meters") // Print "One inch is 0.0254 Ft print("Three feet is \(threeFeet) meters") // Print "Three feet is 0.914399970739201 meters"Copy the code

Constructor extensions can add new constructors to existing types. It allows you to use custom types as arguments for other type constructors, or to add additional construction options to the original implementation of the type.

Extensions can add new convenience constructors to a class, but they cannot add new specified constructors or destructors to a class. Specifies that the constructor and destructor must always be provided by the original implementation of the class.

If you use extension to add a constructor for a value type that already provides default values for all stored properties and does not define any custom constructors, you can use the default constructor and member constructor in the constructor for the value type extension. This is not the case if you have already written the constructor in the original implementation of the value type, as described in the constructor delegate for the value type.

If you use an extension to add a constructor to a structure defined in another module, the new constructor cannot access self until a constructor is used in the module defined.

In the following example, a custom Rect structure is used to represent a geometric rectangle. This example also defines two supporting structures Size and Point, both of which set the default value of the attribute to 0.0:

Struct Size {var width = 0.0, height = 0.0} struct Point {var x = 0.0, Y = 0.0} struct Rect {var origin = Point() var size = size ()}Copy the code

Because the Rect structure provides default values for all attributes, it automatically acquires a default constructor and a member constructor, as described in the default constructor. These constructors can be used to create new Rect instances:

Let memberwiseRect = Rect(Origin: Point(x: 2.0, y: 2.0), size: size (width: 5.0, height: 5.0))Copy the code

You can extend the Rect structure to provide a constructor that allows you to specify point and size:

extension Rect {
    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

The new constructor first computes an appropriate origin based on the provided center and size. The constructor then calls the constructor’s built-in member constructor init(Origin :size:), which stores the new Origin and size values in the appropriate properties:

Let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: size (width: 3.0, height: 0) // centerRect origin is (2.5, 2.5) and its size is (3.0, 3.0).Copy the code

Pay attention to

If you provide a new constructor through an extension, it is your responsibility to ensure that every instance created through that constructor is fully initialized.

Method extensions can add new instance methods and class methods to existing types. In the following example, a new instance method called repetitions is added to the Int type:

extension Int { func repetitions(task: () -> Void) { for _ in 0.. <self { task() } } }Copy the code

Repetitions (task:) method receives only one parameter of type () -> Void, which represents a method with no parameters and no return value.

After defining this extension, you can call the repetitions(Task 🙂 method on arbitrary integers to perform the corresponding repetitions:

repetitions { print("Hello!" ) } // Hello! // Hello! // Hello!Copy the code

Mutable instance methods Instance methods added by extension can also modify (or mutate) the instance itself. Methods of structures and enumerations that can modify self or its own properties must mark the instance method as mutating, as if changing the method’s original implementation.

In the following example, a new mutating method, called square, has been added to Swift’s Int, which squares the original value:

Extension Int {mutating func square() {self = self * self}} var someInt = 3 someint.square () // someInt is now 9Copy the code

Subscript extensions can add new subscripts to existing types. In the following example, we add an integer subscript to the Swift Int. The subscript [n] returns the NTH place after the decimal point, starting at the right of the number:

123456789 [0] to return to 9

123456789 return to 8 [1]

… And so on:

extension Int { subscript(digitIndex: Int) -> Int { var decimalBase = 1 for _ in 0.. <digitIndex {decimalBase *= 10} return (self/decimalBase) % 10}} 746381295[0] // 746381295[1] // return 9 746381295[2] // return 2 746381295[8] // return 7Copy the code

If the Int value of the operation does not have enough bits to satisfy the requested subscript, then the reality of the subscript returns 0, as if a 0 had been added to the left of the number:

746381295[9] // Return 0 as if you had made the request: 0746381295[9] Nested type extensions can add new nested types to existing classes, structs, and enumerations:

extension Int {
    enum Kind {
        case negative, zero, positive
    }
    var kind: Kind {
        switch self {
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}
Copy the code

This example adds a new nested enumeration to Int. This enumeration is called Kind and represents the type of number represented by a particular integer. Specifically, it indicates whether a number is negative, zero, or positive.

This example also adds a new computational instance property to Int called kind, which returns the branch of the Kind enumerated case for the integer being operated on.

Now, any Int value can use this nested type:

func printIntegerKinds(_ numbers: [Int]) { for number in numbers { switch number.kind { case .negative: print("- ", terminator: "") case .zero: print("0 ", terminator: "") case .positive: print("+ ", terminator: "")}} print (" ")} printIntegerKinds ([3, 19, 27, 0, 6, 0, 7]) / / print" + + - 0-0 +"Copy the code

The printIntegerKinds(_:) method takes an array of Int as input and iterates through the values in turn. For each integer in the array, the method checks its KIND computed property and prints the appropriate description.

  • Note that number.kind is already considered to be of type int. kind. So, all int. Kind case branches in the switch statement can be abbreviated, just as.negative is used instead of in.kind. Negative.

Calculation properties can be used for classes, structs, and enumerations. Storage properties can only be used for classes and structs. 2. The storage property can be a variable storage property (defined with the keyword var) or a constant storage property (defined with the keyword let). Computed attributes can only be (defined with the keyword var). 3. Computed properties do not store values directly. Instead, they provide a getter and an optional setter to indirectly obtain and set the values of other properties or variables. 4. You can add attribute observers for other storage attributes except for deferred storage attributes, or you can add attribute observers for inherited attributes (including storage attributes and calculated attributes) by overwriting attributes. You don’t have to add a property observer for a non-overridden computed property because you can directly monitor and respond to value changes through its setter.

Swift-book Swift compute attribute and storage attribute