The introduction

Continuing with the Swift documentation, we learned about Swift nested types from the previous section: Nested types. We explained that enumerations, structs, and classes can be nested arbitrarily to facilitate the use of complex types. Now, let’s learn about Swift extensions. Due to the long space, here is a section to record, next, let’s begin!

extension

Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes extending types that do not have access to the original source code (called traceability modeling). Extensions are similar to categories in Objective-C. (Unlike objective-C categories, Swift extensions have no name)

In Swift, extensions can:

  • Added compute instance attributes compute class attributes
  • Define instance methods and class methods
  • Provides a new initialization method
  • Define the subscripts
  • Define and use new nested types
  • Make existing types conform to the protocol

In Swift, it is even possible to extend a protocol to provide implementation of its requirements, or to add additional capabilities that conformance types can take advantage of. See Protocol Extension for more details.

Pay attention to

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

1 Extension syntax

Declare the extension using the extension keyword

extension SomeType {
    // new functionality to add to SomeType goes here
}
Copy the code

Extensions extend an existing type to adopt one or more protocols. To add protocol consistency, write the protocol name the same way you write the protocol name for a class or structure:

extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}
Copy the code

Adding protocol conformance in this way is described in Adding protocol conformance using extensions.

Extensions can be used to extend existing generic types, as described in Extending Generic Types. You can also extend generic types to conditionally add functionality, as described in the extension using the generic Where clause.

Pay attention to

If an extension is defined to add new functionality to an existing type, the new functionality will be available to all existing instances of that type, even if they were created before the extension was defined.

2 Calculating attributes

Extensions can add compute instance attributes and compute type attributes to existing types. This example adds five compute instance attributes to Swift’s built-in Double to provide basic support for using 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") // Prints "One inch is Meters "let threeFeet = 3. Ft print("Three feet is \(threeFeet) meters") // Prints "Three feet is 0.914399970739201 meters"Copy the code

These calculated properties indicate that the double value should be treated as a unit of length. Although these properties are implemented as computed properties, the names of these properties can be attached to floating point text values using point syntax as a way to perform distance conversions using that text value. In this case, the double value 1.0 is considered to represent “one meter.” This is why m computed returns the self expression 1. M is considered to compute a double precision value of 1.0. Other units require some conversion, expressed in meters. One kilometer is equal to 1000 meters, so the “Kilometer Count” feature multiplies this value by 1_000.00 to convert it to a number in meters. Similarly, there are 3.28084 feet in meters, so the FT calculation property divides the base double value by 3.28084, converting it from feet to meters. These properties are read-only computed properties, so for brevity, they are not represented by the GET keyword. They return values of type Double and can be used in mathematical calculations if Double is accepted:

Let aMarathon = 42.km + 195.m print("A marathon is \(aMarathon) meters long") // Prints "A marathon is 42195.0 meters long"Copy the code

Pay attention to

Extensions can add new computed properties, but they cannot add storage properties, nor can they add property observers to existing properties.

3 the initialization

Extensions can add new initializers to existing types. This enables you to extend other types to accept your own custom type as an initializer parameter, or to provide other initialization options not included in the original implementation of that type.

Extensions can add new convenience initializers to a class, but cannot add new designated initializers or de-initializers to a class. The specified initializers and deinitializers must always be provided by the original class implementation.

If you use an extension to add an initializer to a value type that provides default values for all of its storage properties and do not define any custom initializer, you can call the default initializer and member-level initializer for that value type from the extended initializer. This does not happen if you write initializers as part of the original implementation of a value type, as described in the initializer delegate for a value type.

If an initializer cannot call an initializer from another module, it can only be used if the initializer cannot call the initializer from another module. The following example defines a custom rectangle structure to represent a geometric rectangle. The example also defines two supporting constructs called Size and Point, both of which provide a default value of 0.0 for all their attributes:

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 of its properties, it automatically receives default initializers and member-level initializers, as described in default initializers. These initializers 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

The Rect structure can be extended to provide additional initializers with specific center points and sizes:

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

This new initializer first computes the appropriate origin based on the provided center point and size values. The initializer then calls the structure’s automatic member 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's 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 initializer with extensions, you are still responsible for ensuring that each instance is fully initialized after the initializer is complete.

Four methods

Extensions can add new instance methods and type methods to existing types. The following example adds a new instance method called repetitions to the Int type:

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

The repetitions (task 🙂 method accepts a parameter of type () ->Void, which represents a function with no parameters and no return value. After defining this extension, the repetitions (task:) method can be invoked on arbitrary integers to perform multiple tasks:

3.repetitions { print("Hello!" ) } // Hello! // Hello! // Hello!Copy the code
4.1 Mutable instance methods

Instance methods added using extensions can also modify (or change) the instance itself. Struct and enumeration methods that modify self or its attributes must mark instance methods as mutable, just as mutable methods were in the original implementation.

The following example adds a new mutable method called square to Swift’s Int type, which squares the original value:

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

5 Subscripts

Extensions can add new subscripts to existing types. This example adds an integer subscript to Swift’s built-in Int type. This subscript [n] returns the n-bit decimal number to the right of the number:

  • 123456789[0] returns 9
  • 123456789[1] returns 8

You can also:

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

If the Int value does not have enough digits for the requested index, the subscript implementation returns 0 as if the number were padded with zeros on the left:

746381295[9]
// returns 0, as if you had requested:
0746381295[9]
Copy the code

6 Nested types

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, called Kind, represents the Kind of number represented by a particular integer. Specifically, it indicates whether a number is negative, zero, or positive.

This example also adds a new compute instance attribute to Int, called KIND, which returns the corresponding kind enumeration case for that integer.

Nested enumerations can now be used with any Int value:

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])
// Prints "+ + - 0 - 0 + "
Copy the code

The function printIntegerKinds (:) takes an input array of Int values and iterates over those values in turn. For each integer in the array, the function takes into account the type of calculated attribute for that integer and prints the appropriate description.

Pay attention to

Number. kind is already known to be of type int.kind. Because of this, all int.kind case values can be written in shorthand in switch statements, for example. Negative instead of int.kind.

Refer to swift-extensions