preface

The main purpose of writing this article is to prepare content for sharing within the group. These days, Swift has been used in several projects. In the last GIAC conference, I was asked why I wanted to use Swift, and this topic can be used to talk about what features of Swift attract me.

The generic

Let’s start with an example of what generics solve.

let nations = ["China"."The United States." "."Japan"]
func showNations(arr : [String]) {
    arr.map { str in
        print("\(str)")}}Copy the code

Let’s make an array of strings and print out the strings. The map notation here can also be optimized:

arr.map { print("\ [$0)")}Copy the code

So what else can you optimize. Put showNations into parameter group generics to support multiple types, such as [int], [double], etc.

func showArray<T>(arr: [T]) {
    arr.map { print("\ [$0)")}}Copy the code

You can see that generics make it easy to merge logic that does the same thing with different types of data.

Type constraints

Let’s start by defining the Transition structure for the state machine in my HTN project

struct HTNTransition<S: Hashable.E: Hashable> {
    let event: E
    let fromState: S
    let toState: S
    
    init(event: E, fromState: S, toState: S) {
        self.event = event
        self.fromState = fromState
        self.toState = toState
    }
}
Copy the code

In this case, fromState, toState and Event can be different types of data, such as enumerations, strings or integers, etc. Defining two different generics S and E can make the state and event types different, so that the interface will be more flexible and easier to adapt to more projects.

You’ll notice that the S and E have a Hashable protocol after the colon, which requires them to conform to the type constraints of the protocol. Using protocols makes both types more formal and extensible.

Swift’s basic String, Int, Double, and Bool types follow Hashable, as do unassociated enumerations. Hashable provides a hashValue method for determining whether compliant objects are equal.

The Hashable protocol also complies with the Equatable protocol, which implements the == operator to determine whether custom classes or structures are the same.

Association types

Association types defined in the protocol can also be handled with generics. Let’s say we define a protocol

protocol HTNState {
    associatedtype StateType
    func add(_ item: StateType)
}
Copy the code

The implementation with non-generics is as follows:

struct states: HTNState {
    typealias StateType = Int
    func add(_ item: Int) {
        / /...}}Copy the code

The generic compliance protocol can be written as follows:

struct states<T> :HTNState {
    func add(_ item: T) {
        / /...}}Copy the code

In this way, associated types can also enjoy the benefits of generics.

Type erasure

When declaring a protocol that uses an association attribute as an attribute, as in the following code:

class stateDelegate<T> {
    var state: T
    var delegate: HTNState
}
Copy the code

No initializers errors will be displayed, followed by error: Protocol ‘HTNState’ can only be used as a generic constraint because it has Self or associated type requirements. The HTNState protocol can only be used as a generic constraint because it contains the required self or association type.

So how to deal with it? The main idea is to add an intermediate layer to externalize the abstract type in your code. In fact, there is a good use of type erasure in the Swift standard library, such as the AnySequence protocol.

Where clause

Functions, extensions, and association types can all use where statements. The WHERE statement is a constraint on the application of generics. Such as:

func stateFilter<FromState:HTNState, ToState:HTNState>(_ from:FromState, _ to:ToState) where FromState.StateType= =ToState.StateType {
    / /...
}
Copy the code

This function requires that their StateType be of the same type.

Generic and Any types

The two types look similar, but be careful with the differences. The difference is that the Any type avoids type checking, so use it sparingly and preferably not. Generics are flexible and secure on the one hand. Here’s an example to see the difference:

func add<T>(_ input: T) -> T {
    / /...
    return input;
}

func anyAdd(_ input: Any) -> Any {
    / /...
    return input;
}
Copy the code

Either of these functions can allow input arguments of any type. The difference is that the type returned by anyAdd can be different from that of the input parameter, which can get out of control and lead to errors in subsequent operations.

A collection of

The basic concept

Let’s start with the basic concepts of sets. First, sets are generic. For example:

let stateArray: Array<String> = ["Work"."Eat"."Play the game"."Sleep"]
Copy the code

Set it needs to have a traversal function first, through the GeneratorType protocol, can not care about the specific element type as long as the iterator call next to get all the elements. However, iterators cannot iterate more than once, so Sequence is used to solve this problem. ForEach, elementsEqual, Contains, minElement, maxElement, Map, flatMap, filter, reduce and other functions of the set are all due to the multiple traversal of Sequence.

Finally, the concept of Collection realizes Indexable protocol based on Sequence because Sequence cannot determine the position in the Collection. Having a Collection allows you to determine the location of elements, including the start and end locations, so that you can determine which elements have already been accessed and avoid accessing the same element multiple times. You can also find elements at a given location directly.

The above description is as follows:

The iterator

Swift has a simple AnyIterator structure

struct AnyIterator<Element> :IteratorProtocol { 
    init(_ body: @escaping () -> Element?)./ /...
}
Copy the code

AnyIterator implements the IteratorProtocol and Sequence protocols. Let’s see how to use AnyIterator:

class stateItr : IteratorProtocol {
    var num:Int = 1
    func next(a) -> Int? { num +=2
        return num
    }
}

func findNext<I: IteratorProtocol>( elm: I) -> AnyIterator<I.Element> where I.Element= =Int
{
    var l = elm
    print("\(l.next() ?? 0)")
    return AnyIterator { l.next() }
}

findNext(elm: findNext(elm: findNext(elm: stateItr())))
Copy the code

The first step is to define a class that follows the IteratorProtocol and implements the next function. Implement an AnyIterator iterator method, so that calls to this method can constantly find elements that match.

There is a use of the WHERE statement, where I.E. lement == Int. If modified to where I.E. lement == String, the following error will appear

Playground execution failed:

error: MyPlayground.playground:18:37: error: cannot invoke 'findNext(elm:)' with an argument list of type '(elm: stateItr)'
findNext(elm: findNext(elm: findNext(elm: stateItr())))
                                    ^

MyPlayground.playground:11:6: note: candidate requires that the types 'Int' and 'String' be equivalent (requirement specified as 'I.Element'= ='String' [with I = stateItr])
func findNext<I: IteratorProtocol>( elm: I) -> AnyIterator<I.Element> where I.Element == String
     ^
Copy the code

The compiler finds type mismatches through code tracing during the code review phase, so I have to give Swift a thumbs up

Sequence

The above iterator will only iterate over the next element in a single triggered manner, but if you want to be able to re-find or regenerate the generated element, you need a new iterator and a subsequence. In the Sequence protocol, you can see the following definition:

public protocol Sequence {
    //Element represents the type of the sequence Element
    associatedtype Element where Self.Element= =Self.Iterator.Element
    // Iterate over the interface type
    associatedtype Iterator : IteratorProtocol
    // Subsequence type
    associatedtype SubSequence
    // Returns an iterator for the Sequence element
    public func makeIterator(a) -> Self.Iterator
    / /...
}
Copy the code

Research relies on the new iterator, while operations such as slicing that will regenerate a new Sequence need SubSequence storage and return.

Collection

The most important improvement to the Sequence is that it has a subscript index, so that elements can be retrieved by subscript index. A Collection is a finite range, with an opening index and an ending index, so the infinite range of a Collection and Sequence is different. With a limited range, a Collection can have the count attribute to count.

In addition to the library’s String, Array, Dictionary, and Set sets such as Data and IndexSets gain library capabilities for Collection types by following the Collection protocol.

map

In the first example of generics we saw the use of map. Let’s look at the definition of map:

func map<T>(transform: (Self.Generator.Element) -> T) rethrows- > [T]
Copy the code

Here (self.generator.Element) -> T is the definition of the map closure, and self.generator.Element is the current Element type.

flatmap

A two-dimensional array is reduced to one dimension through a flatmap and can filter out nil values. Look at the below Swift code (Swift/stdlib/public/core/SequenceAlgorithms Swift. Gyb) realization of flatmap:

/ / = = = -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- = = = / /
// flatMap()
/ / = = = -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- = = = / /

extension Sequence {
  /// Returns an array containing the concatenated results of calling the
  /// given transformation with each element of this sequence.
  ///
  /// Use this method to receive a single-level collection when your
  /// transformation produces a sequence or collection for each element.
  ///
  /// In this example, note the difference in the result of using `map` and
  /// `flatMap` with a transformation that returns an array.
  ///
  /// let numbers = [1, 2, 3, 4]
  ///
  /// let mapped = numbers.map { Array(count: $0, repeatedValue: $0) }
  /// // [[1], [2, 2], [3, 3, 3], [4, 4, 4]]
  ///
  /// let flatMapped = numbers.flatMap { Array(count: $0, repeatedValue: $0) }
  /// // [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
  ///
  /// In fact, `s.flatMap(transform)` is equivalent to
  /// `Array(s.map(transform).joined())`.
  ///
  /// - Parameter transform: A closure that accepts an element of this
  /// sequence as its argument and returns a sequence or collection.
  /// - Returns: The resulting flattened array.
  ///
  /// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
  /// and *n* is the length of the result.
  /// - SeeAlso: `joined()`, `map(_:)`
  public func flatMap<SegmentOfResult : Sequence>(
    _ transform: (${GElement}) throws -> SegmentOfResult
  ) rethrows- > [SegmentOfResult. ${GElement{}]var result: [SegmentOfResult. ${GElement}] = []
    for element in self {
      result.append(contentsOf: try transform(element))
    }
    return result
  }
}

extension Sequence {
  /// Returns an array containing the non-`nil` results of calling the given
  /// transformation with each element of this sequence.
  ///
  /// Use this method to receive an array of nonoptional values when your
  /// transformation produces an optional value.
  ///
  /// In this example, note the difference in the result of using `map` and
  /// `flatMap` with a transformation that returns an optional `Int` value.
  ///
  /// let possibleNumbers = ["1", "2", "three", "///4///", "5"]
  ///
  /// let mapped: [Int?]  = possibleNumbers.map { str in Int(str) }
  /// // [1, 2, nil, nil, 5]
  ///
  /// let flatMapped: [Int] = possibleNumbers.flatMap { str in Int(str) }
  /// // [1, 2, 5]
  ///
  /// - Parameter transform: A closure that accepts an element of this
  /// sequence as its argument and returns an optional value.
  /// - Returns: An array of the non-`nil` results of calling `transform`
  /// with each element of the sequence.
  ///
  /// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
  /// and *n* is the length of the result.
  public func flatMap<ElementOfResult>(
    _ transform: (${GElement}) throws -> ElementOfResult?).rethrows- > [ElementOfResult] {
    var result: [ElementOfResult] = []
    for element in self {
      if let newElement = try transform(element) {
        result.append(newElement)
      }
    }
    return result
  }
}
Copy the code

You can see from the code that leveling works by adding all the elements of a set to another set. An if let statement in the second extension blocks elements that are not unpacked successfully.

Reduce

Reduce is the reduced semantics of programming language semantics, also known as accumulator. The following can also look at the Swift source code for its implementation:

/ / = = = -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- = = = / /
// reduce()
/ / = = = -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- = = = / /

extension Sequence {
  /// Returns the result of combining the elements of the sequence using the
  /// given closure.
  ///
  /// Use the `reduce(_:_:)` method to produce a single value from the elements
  /// of an entire sequence. For example, you can use this method on an array
  /// of numbers to find their sum or product.
  ///
  /// The `nextPartialResult` closure is called sequentially with an
  /// accumulating value initialized to `initialResult` and each element of
  /// the sequence. This example shows how to find the sum of an array of
  /// numbers.
  ///
  /// let numbers = [1, 2, 3, 4]
  /// let numberSum = numbers.reduce(0, { x, y in
  /// x + y
  / / /})
  /// // numberSum == 10
  ///
  /// When `numbers.reduce(_:_:)` is called, the following steps occur:
  ///
  /// 1. The `nextPartialResult` closure is called with `initialResult`---`0`
  /// in this case---and the first element of `numbers`, returning the sum:
  / / / ` ` 1.
  /// 2. The closure is called again repeatedly with the previous call's return
  /// value and each element of the sequence.
  /// 3. When the sequence is exhausted, the last value returned from the
  /// closure is returned to the caller.
  ///
  /// If the sequence has no elements, `nextPartialResult` is never executed
  /// and `initialResult` is the result of the call to `reduce(_:_:)`.
  ///
  /// - Parameters:
  /// - initialResult: The value to use as the initial accumulating value.
  /// `initialResult` is passed to `nextPartialResult` the first time the
  /// closure is executed.
  /// - nextPartialResult: A closure that combines an accumulating value and
  /// an element of the sequence into a new accumulating value, to be used
  /// in the next call of the `nextPartialResult` closure or returned to
  /// the caller.
  /// - Returns: The final accumulated value. If the sequence has no elements,
  /// the result is `initialResult`.
  public func reduce<Result>(
    _ initialResult: Result,
    _ nextPartialResult:
      (_ partialResult: Result, ${GElement}) throws -> Result
  ) rethrows -> Result {
    var accumulator = initialResult
    for element in self {
      accumulator = try nextPartialResult(accumulator, element)
    }
    return accumulator
  }
}
Copy the code

You can see that initialResult is used to record the previously returned result and the operation performed on the current element in the closure.

Array

Take a look at the basic use of arrays

// Create an array
var nums = [Int] ()// Create an empty array
var mArray = nums + [2.3.5] + [5.9]// Merge the values of multiple arrays of elements of the same type
var animals: [String] = ["dragon"."cat"."mice"."dog"]

// Add an array
animals.append("bird")
animals += ["ant"]

// Get and change arrays
var firstItem = mArray[0]
animals[0] = "red dragon"
animals[2.4] = ["black dragon"."white dragon"] // Change multiple elements with subscripts
animals.insert("chinese dragon", at: 0) // Add elements before index values
let mapleSyrup = animals.remove(at: 0) // Remove an element from the array
let apples = animals.removeLast() // Remove the last element

//// array traversal
for animal in animals {
    print(animal)
}
for (index, animal) in animals.enumerated() {
    print("animal \(String(index + 1)) :\(animal)")}/* animal 1: red dragon animal 2: cat animal 3: black dragon animal 4: white dragon */
Copy the code

Weakly referenced Swift array

Arrays in Swift have strong references by default, but sometimes you want to have weak references, so you can use NSPointerArray. It determines whether to use weak or strong references during initialization.

let strongArr = NSPointerArray.strongObjects() / / strong reference
let weakArr = NSPointerArray.weakObjects() // Maintains weak references
Copy the code

Weak references to Dictionary can be used with NSMapTable, Set corresponds to NSHashTable.

Dictionary

Take a look at the basics:

/ / create a Dictionary
var strs = [Int: String] ()var colors: [String: String] = ["red": "#e83f45"."yellow": "#ffe651"]
strs[16] = "sixteen"

// the updateValue method returns the value before the update
if let oldValue = colors.updateValue("#e83f47", forKey: "red") {
    print("The old value for DUB was \(oldValue).")}/ / traverse
for (color, value) in colors {
    print("\(color): \(value)")}//map
let newColorValues = colors.map { "hex:\ [$0.value)" }
print("\(newColorValues)")

//mapValues returns the complete new Dictionary
let newColors = colors.mapValues { "hex:\ [$0)" }
print("\(newColors)")
Copy the code

Protocol programming

Swift is designed for single inheritance, and you need to use protocols if you want multiple inheritance. Another important function of the protocol is to require users to comply with specified generic constraints through associatedType.

Let’s take a look at the development mode of traditional programming:

class Dragon {}class BlackDragon: Dragon{
    func fire(a) {
        print("fire!!!")}}class WhiteDragon: Dragon {
    func fire(a) {
        print("fire!!!")}}BlackDragon().fire()
WhiteDragon().fire()
Copy the code

This example shows that fire() is repetitive code, so the first thing that comes to mind is either adding the method directly to the base class or extending it with extension:

extension Dragon {
    func fire(a) {
        print("fire!!!")}}Copy the code

At this point we want to add a method to make Dragon fly:

extension Dragon {
    func fire(a) {
        print("fire!!!")}func fly(a) {
        print("fly~~~")}}Copy the code

So BlackDragon and WhiteDragon both have these two abilities. What if we design a new Dragon, YellowDragon, or more dragons that don’t have the ability to fly? Because you can’t inherit more, you can’t split it into two base classes, which inevitably leads to duplicate code. But this problem is easily solved with an agreement. The concrete implementation is as follows:

protocol DragonFire {}
protocol DragonFly {}

extension DragonFire {
    func fire(a) {
        print("fire!!!")}}extension DragonFly {
    func fly(a) {
        print("fly~~~")}}class BlackDragon: DragonFire.DragonFly {}
class WhiteDragon: DragonFire.DragonFly {}
class YellowDragon: DragonFire {}
class PurpleDragon: DragonFire {}

BlackDragon().fire()
WhiteDragon().fire()
BlackDragon().fly()
YellowDragon().fire()
Copy the code

It can be seen that on the one hand, there is no duplicate code, and on the other hand, the structure is much clearer and easier to expand. The combination of types and capabilities of Dragon is also more convenient and clear. Extension enables the protocol to implement default methods.

Swift uses traits in a way that other languages like C++ directly support multiple inheritance in that the class holds multiple instances of its parent class. Java multiple inheritance is all about what inheritance can do, and it’s up to you to do it. A similar solution to traits is mixins, the metaprogramming idea that Ruby uses.

Protocols can also be inherited, and can be aggregated by & to determine whether a class complies with a protocol. The is keyword can be used.

Of course, protocols can also be used as types. For example, if an array generic element is specified as a protocol, all elements in the array need to follow that protocol.

Swift Memory Management

Memory allocation

Heap

The free blocks that can hold objects on the Heap need to be locked when memory is allocated on the Heap. For thread safety, these need to be locked and synchronized.

Heap is a complete binary tree that is filled except for the lowest node, which is filled from left to right. Swift’s Heap is implemented via bidirectional linked lists. Since the Heap can be retained and released, it is easy to allocate space discontiguously. The purpose of using linked lists is to be able to concatenate blocks of memory and consolidate space by adjusting the linked list pointer at release.

It is inevitable that the Heap will be traversed during retain and the appropriate size of the memory block will be found. All that can be optimized is to reduce some traversal by recording the previous traversal. However, the Heap is very large, so each traverse is still time-consuming. Moreover, in order to integrate the space, release also needs to determine whether the previous and subsequent blocks of the current memory block are free, etc. If they are free, it also needs to traverse the linked list query, so the final solution is bidirectional linked list. By only linking free memory blocks with Pointers to form linked lists, you can reduce traversal on retain and theoretically double the efficiency by inserting the extra space at the beginning of the Heap on release and integrating it with the space previously moved to the front.

Even though it’s more efficient, it’s still not as efficient as Stack, so Apple has converted some of the types that used to be on the OC Heap to value types.

Stack

Stack structure is very simple, push and pop are done, memory only need to maintain the end of the Stack pointer. Because of its simplicity, it is very suitable for dealing with things that are not time-sensitive and temporary, so the Stack can be thought of as an area of memory where temporary data is exchanged. In multithreading, the Stack is thread-specific, so there are no thread-safety concerns.

Memory alignment

Swift also has the concept of memory alignment

struct DragonFirePosition {
    var x:Int64 //8 Bytes
    var y:Int32 //4 Bytes
    / / 8 + 4
}
struct DragonHomePosition {
    var y:Int32 //4 Bytes + align memory (4 Bytes)
    var x:Int64 //8 Bytes
    //4 + 4 + 8
}
let firePositionSize = MemoryLayout<DragonFirePosition>.size / / 12
let homePositionSize = MemoryLayout<DragonHomePosition>.size / / 16
Copy the code

Swift distribution mechanism

The purpose of dispatch is to let the CPU know where the function being called is. Swift language supports the direct distribution of compiled languages, function table distribution and message mechanism distribution of three distribution modes, respectively, the three distribution modes are described below.

Direct distribution

C++ default uses direct distribution, with the virtual modifier can be changed to function table distribution. Direct distribution is the fastest because there are fewer calls to instructions and optimizations can be made by the compiler, such as inlining. The disadvantage is that inheritance is not supported due to lack of dynamics.

struct DragonFirePosition {
    var x:Int64
    var y:Int32
    func land(a){}}func DragonWillFire(_ position:DragonFirePosition) {
    position.land()
}
let position = DragonFirePosition(x: 342, y: 213)
DragonWillFire(position)
Copy the code

After compiling inline DragonWillFire(DragonFirePosition(x: 342, Y: 213)) jumps directly to the method implementation, resulting in Position.land ().

Function table distribution

The Java default is to use the function table distribution, with the final modifier changed to direct distribution. Function table distribution is dynamic. Function tables are called Witness Tables in Swift and virtual tables in most languages. A class uses an array to store Pointers to its functions. The override parent replaces the previous function, and any functions added by subclasses are added to the array. Here’s an example:

class Fish {
    func swim(a) {}
    func eat(a) {
        //normal eat}}class FlyingFish: Fish {
    override func eat(a) {
        //flying fish eat
    }
    func fly(a){}}Copy the code

The compiler creates witness tables for the Fish class and FlyingFish class respectively. In the FlyingFish table, there are swim and eat functions. In the FlyingFish table, there are swim classes, covering eat and fly.

A function is called to read the function table of the object, add the offset of the function to the address of the class, and jump to that address. In terms of compiled bytecode, it’s two reads and one jump, which is slower than direct distribution.

Message mechanism dispatch

This mechanism allows you to change the behavior of a function at run time. Both KVO and CoreData are examples of this mechanism. OC is sent by default using the message mechanism. C is directly sent to achieve high performance. Swift can support message mechanism dispatch through the dynamic modifier.

When a message is dispatched, the runtime looks up the called function based on inheritance. However, this is not efficient, so you need to improve efficiency through caching, so that lookup performance is similar to function distribution.

The specific distribution

The statement

All value types are distributed directly. Both class and extension protocols are distributed directly. Classes and protocols are function table dispatches.

Specified distribution mode

  • Final: Make a function ina class distributable directly, so that the function is not dynamic and cannot be retrieved at runtime.
  • Dynamic: Functions in the class can be dispatched using the message mechanism, and functions in the extension can be override.

Distributed optimization

Swift will optimize in this way. For example, if a function does not have override, Swift may use direct dispatch. Therefore, if the property is bound to KVO, its getter and setter methods may be optimized to direct dispatch, resulting in the invalidation of KVO. So remember to add dynamic to make sure it works. Swift should do more with this optimization later.

Basic data types Memory management

Use MemoryLayout to see how much memory is used for basic data types

MemoryLayout<Int>.size      / / 8
MemoryLayout<Int16>.size    / / 2
MemoryLayout<Bool>.size     / / 1
MemoryLayout<Float>.size    / / 4
MemoryLayout<Double>.size   / / 8
Copy the code

Struct memory management

For structs, space can be determined during compilation, so there is no need for extra space for the runtime, which simply passes the address when running the procedure call.

Let’s look at MemoryLayout of structs

struct DragonFirePosition {
    var x:Int64 //8 Bytes
    var y:Int32 //4 Bytes
    / / 8 + 4
    func land(a){}}MemoryLayout<DragonFirePosition>.size       / / 12
MemoryLayout<DragonFirePosition>.alignment  / / 8
MemoryLayout<DragonFirePosition>.stride     / / 16
Copy the code

Alignment can be seen as 8 Bytes, so byte alignment is used in the struct here, and the actual occupied size can be seen from the stride that 8 * 2 is 16.

Changing var x:Int64 to optional adds 4 Bytes, but the actual size is still 16 for this case, again due to memory alignment.

Class memory Management

The Class itself is allocated on the Stack, and the Class Type information needs to be stored on the Heap. This Type information contains the function table, which can be distributed according to the function table when the function is distributed. Subclasses simply record their own Type information.

Protocol Type Memory management

The memory model for the protocol type is Called Interface Container. Let’s look at the following example

protocol DragonFire {}
extension DragonFire {
    func fire(a) {
        print("fire!!!")}}struct YellowDragon: DragonFire {
    let eyes = "blue"
    let teeth = 48
}

let classSize = MemoryLayout<YellowDragon>.size  / / 32
let protocolSize = MemoryLayout<DragonFire>.size / / 40
Copy the code

You can see that the protocol is larger than the class. This is because the first three Words of the Container are called Value Buffers to store inline values. The fourth word is Value Witness Table, which stores various operations of values, such as allocate, copy, destruct, deallocate, etc. The fifth word is Protocol Witness Table, which is a function for storing protocols.

Memory management for generics

The adoption of generics is similar to the Principle of An Interface Container. Value Witness Table and Protocol Witness Table are passed to generic methods as invisible parameters. After layers of inline optimization by the compiler, however, the type is eventually deduced and the Container is no longer needed.