My blog

Value types and reference types

In iOS, virtual memory is divided into five memory partitions: heap, stack, global, constant and code. Objects in Swift are divided into value types and reference types according to their location in memory.

  • Value types: The value types in Swift include enum, struct, Array, Dictionary, Tuple, and so on

  • Reference types: The main reference types in Swift are Methods,class, and Clousre

1.1 Memory allocation and management of value types and reference types

Value types in Swift are generally stored in stack memory (or not necessarily). Due to the characteristics of the stack, this part of memory is directly managed and optimized by the CPU, and the copy of values is also a deep copy. When a value is used, the system will immediately release this part of memory. So memory stored on the stack is very efficient to create, use, and release.

In general, when a struct is created, it is stored in the stack area by default. When the compiler detects that a structure variable is closed by a function, the structure is stored on the heap. See the closure and Variability section of the Structures and Classes section in the Swift Progression.

The reference type in Swift is generally stored in the heap. Apple uses ARC to manage this part of memory. Tracing this part of memory is tracing the reference count.

Complex value typesstruct

Swift provides a variety of ways to store data structurally, such as struct, enum, class, etc. I’ll start with a struct that is often used as a Model in development.

2.1 Definition and initialization of struct

So let’s start by defining a struct, and here I’m defining a struct for color

struct Color {
    var red: Double
    var green: Double
    var blue: Double
    / / transparency
    var alpha: Double? = 1
}
Copy the code

I defined four attributes, red, double and Blue for red, green and blue values, and alpha for transparency. The amount of memory used by these attributes determines the size of Color. When I define the four attributes and build directly, there is no problem, here first to understand the initialization method of struct.

2.1.1,Memberwise initializer

When I define a struct without creating any init methods for it, it works fine because the Swift compiler automatically creates an init method for the struct called Memberwise Initializer. When I need to use this struct I can initialize a color object like this:

var color = Color(red: 200, green: 200, blue: 200)
Copy the code
2.1.2,Default initializer

If I want to create a color object without specifying a parameter and automatically set the default value for the property, there are two options:

  • Default values are set for each attribute as it is defined

    struct Color {
        var red: Double = 0
        var green: Double = 0
        var blue: Double = 0
        
        var alpha: Double? = 1
    }
    Copy the code

    This allows you to initialize the color object without specifying a parameter.

    var color = Color(a)Copy the code

    The requirement for doing this is to set the default value for each attribute when setting the attribute, because Swift requires that the init method initialize each attribute of the custom type.

  • Set a default value for each property in the init method

struct Color {
    var red: Double
    var green: Double 
    var blue: Double
    
    var alpha: Double?
    
    init(red: Double = 0.green: Double = 0.blue: Double = 0.alpha: Double? = 1) {
        self.red = red
        self.green = green
        self.blue = blue
        self.alpha = alpha
    }
}
Copy the code

This has the same effect as the previous method of setting the default value directly to the property. When creating an init method for a struct, it is important to ensure that it is correct, because when you override the init method, the system will no longer create the default init method.

2.1.3, Failable init

Structs that are displayed on the screen eventually need to be converted to strings can be converted back into blocks using the Codable protocol or other open source libraries. Because Data may fail to be parsed during initialization, init? Class, and returns nil if parsing fails.

struct Color {
    var red: Double
    var green: Double 
    var blue: Double
    
    var alpha: Double? = 1
  
    private enum CodingKeys: String.CodingKey {
        case red
        case green
        case blue
        case alpha
    }
}

extension Color: Codable {
    init?(data: Data) {
        guard let model = try? JSONDecoder().decode(Color.self, from: data) else { return nil }
        self = model
    }
}
Copy the code
2.1.4,Type propertySet a common value

For a value that is often used in a struct, we can use a Type property defined in the struct. If the theme color of the App is often used in the code, the Type Roperty initialization method can be adopted.

extension Color {
    static let themeColor = Color(red: 200, green: 200, blue: 200, alpha: 1)}// When I want to use the App theme color
let color = Color.themeColor
Copy the code

When the default theme color is created, it is not part of the struct object, so it does not increase the size of the color object and makes the code look cleaner.

2.2. Add methods to struct

Struct methods are read-only by default. For example, I want to add a method that changes transparency to Color:

func modifyWith(alpha: Double){}Copy the code
2.3 mutating keyword

When I modify an attribute value in a struct method, I prefix the method with the mutating keyword. When mutating is added, Swift implicitly marks self as inout so that it can modify attribute values in struct methods.

mutating func modifyWith(alpha: Double) {
    self.alpha = alpha
}
Copy the code
2.4, modify,structvalue

To get a better view of what happens when the variable is changed, add a didSet clousre to it and see what is printed whenever the value of color changes:

var color = Color(red: 200, green: 200, blue: 200) {
    didSet {
        print("color============\(color)")}}Copy the code

Now I change the value of color and look at the printed result.

let colorB = Color(red: 100, green: 100, blue: 100)
color = colorB
// Print the result
color= = = = = = = = = = = =Color(red: 100.0, green: 100.0, blue: 100.0, alpha: Optional(1.0))
Copy the code

I’m changing the value of color so I’m firing the didSet method.

If I change only one of the color attributes, if I want to change the red attribute to 110,

color.red = 110
// Print the result
color= = = = = = = = = = = =Color(red: 110.0, green: 100.0, blue: 100.0, alpha: Optional(1.0))
Copy the code

As you can see, it still prints, which means that if you change any of the color properties, the entire color variable is changed.

3. Reference typesclass

3.1 Class definition and initialization

When I define a class MyColor that represents a color, I set four properties for it:

class MyColor {
    var red: Double
    var green: Double
    var blue: Double

    var alpha: Double? = 1
}
Copy the code

So I’m going to leave this class without a parent, but I can set its parent as appropriate. If there is no FU in the Class, the compiler will tell you that Class ‘MyColor’ has no initializers. This is because classes are reference types and must have a full life cycle; they must be explicitly initialized, used, and finally released. So when I define a class I have to explicitly build init methods. This is one of the differences between a class and a struct.

3.1.1. Default init

The simplest initialization method in general is to call the init method directly

let color = MyColor(a)Copy the code

If I want to initialize a color object like this, I can use the class default initialization method. There are two default initialization methods for class:

  • Set default values for each property

    class MyColor {
        var red: Double = 0.0
        var green: Double = 0.0
        var blue: Double = 0.0
    
        var alpha: Double? = 1
    }
    Copy the code

    Run the code again, and it compiles successfully. This does solve the compiler error problem. If I wanted to set a default value for each property of the class at initialization, I would get an error. So setting a default value for each property is only good for classes that are ideologically simple and have fixed initials or assigned values inside them. If I want to set a property value outside of the class, I can initialize it in another way:

    let color = MyColor(red: 100, green: 100, blue: 100) // Argument passed to call that takes no arguments
    Copy the code
  • Set a default value for each property in the init method

init(_ red: Double = 0._ green: Double = 0._ blue: Double = 0._ alpha: Double? = 1) {
     self.red = red
     self.green = green
     self.blue = blue
     self.alpha = alpha
}
/ / initialization
let mycolorA = MyColor(a)let mycolorB = MyColor(100.100.100)
Copy the code

This allows you to initialize the MyColor class as needed. In Swift, the init method that initializes a class must be defined inside the class, not in extension, otherwise it will cause a compilation error. Struct init methods can be defined in extension, which is one of the differences between class and struct.

3.2、 Convenience init

A convenience constructor is called if it is not preceded by the convenience keyword. If not, it is called the specified constructor.

  • Convenience constructor: before the initialization methodconvenienceKeyword, instead of initializing all properties, because the convenience constructor depends on the specified constructor. If you want to provide a quick method creation for system-provided classes, you can customize a convenience constructor
  • Specify constructor: all attributes must be initialized
convenience init(at: (Double.Double.Double.Double?). ) {
    self.init(at.0, at.1, at.2, at.3)}Copy the code
3.3, Failable init

Most of the time, when interacting with the server, all data will be in String format for consistency and convenience, which requires some processing during initialization:

convenience init?(at: (String.String.String.String?). ) {
     guard let red = Double(at.0), let green = Double(at.1), let blue = Double(at.2) else {
          return nil
     }
     self.init(red, green, blue)
}
Copy the code

Since String init may fail, it is defined in an optional form. In its implementation, if the String to Double fails, nil is returned, indicating that initialization failed.

3, comparison,structandclass

Struct (struct) and class (struct)

3.1 structandclassIn common with
  • Attributes can be defined and used to hold values
  • Can build methods
  • Can set the initial value of each of its properties to set its initial state
  • Can access its value by subscript
  • You can do itextensionAction to extend its functionality beyond the default implementation
  • Can follow a protocol to provide some standard functionality
3.2, structandThe difference between the class `
  • Structs generate init methods by default, and classes must specify init methods explicitly

  • Structs cannot be inherited (but can follow protocols), classes can be inherited

  • Struct is more about its value, and when I change any property value in a struct the whole struct is changed again, class is more about the object itself

4, structandThe choice of the class

Structs and classes have a lot in common in Swift. How to use structs and classes in real development? As a developer, you need to choose which type to use according to the current use time:

4.1. Used by defaultstruct

Structs are typically created and stored on the stack because structs don’t involve heap memory allocation and are very fast to create, trace, and destroy, so they are preferred by default.

4.2 whether inheritance is required orProtocol
  • If inheritance and protocol are not required, use them firststruct
  • If you need inheritance, use itclass
4.3. Needs andObjective-CWhen usingclass

When Swift interacts with Objective-C, you can add @objcMembers before the class, or @objc before the methods and variables to be called, To call the Swift class, import #import “project name-swift.h” into the Objecrtive -c file to call.

// in the Swift file
@objcMembers class Model {
    func coverModel(a){}}Copy the code
#import "project name -swift. h" Model * Model = [[Model alloc] init]; [model coverModel];Copy the code
4.4. Used when identity control is requiredclass
  • When needed= = =When comparing consistency between two instances.= = =The two objects are automatically checked to see if they are identical, including the memory address where the data is stored
let mycolorA = MyColor(a)let mycolorB = mycolorA
if mycolorA = = = mycolorB {
    
}
Copy the code
  • When you need to create data that can be shared and changed
4.5. Used when identity is not controlledstruct
  • When needed= =Compare instance data, used to compare whether two values are equal
var colorA = Color(red: 200, green: 200, blue: 200)
let colorB = Color(red: 100, green: 100, blue: 100)
if colorA.alpha = = colorB.alpha {
    
}
Copy the code
  • This is preferred when you need to modify values in multiple threadsstructBecause thestructThread-safe
var colorArray = [colorA, colorB, colorC, colorD, colorE]
let queue = DispatchQueue.global()
let count = colorArray.count
queue.async { [colorArray] in
    for index in 0..<colorArray.count {
        print("index=========\(colorArray[index])")
        Thread.sleep(forTimeInterval: 1)
    }
}
queue.async {
    Thread.sleep(forTimeInterval: 0.5)
    colorArray.remove(at: 2)
    print("-- -- -- -- -- -- --\(colorArray.count)")}Copy the code

The above code works fine.

Of course, the so-called thread safety is also relatively, modify the following code:

var colorArray = [colorA, colorB, colorC, colorD, colorE]
let queue = DispatchQueue.global()
let count = colorArray.count
queue.async {
    for index in 0..<count {
        print("index=========\(colorArray[index])")
        Thread.sleep(forTimeInterval: 1)
    }
}
queue.async {
    Thread.sleep(forTimeInterval: 0.5)
    colorArray.removeLast()
    print("-- -- -- -- -- -- --\(colorArray.count)")}Copy the code

Running the code again would Crash and print an error:

Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
Copy the code

This is because I removed the last element of the array. When I need to print the last element (i.e. the fifth element), the array actually does not have this element, so the array will Crash. The reason the first code doesn’t Crash is that it copies a set of contents in the new thread. So when a new thread manipulates data, it is thread-safe to copy the value type to the new thread.

This article mainly introduces struct and class and their similarities and differences, and briefly analyzes how to choose struct or class in development. If there is something wrong with my understanding, please point it out.


Reference for this article:

Apple Developer: Choosing Between Structures and Classes

The Swift Programming Language: Structures and Classes

Swift progression: Structures and classes