First, storage properties

1.1 Storage Properties Overview

A storage property is a constant or variable that is part of an instance of a particular class and structure. A store property is either a variable store property (introduced by the var keyword) or a constant store property (introduced by the let keyword). There’s nothing special about storage properties, because they’re everywhere, right

class SSLPerson {
    var age: Int
    var name: String
}
Copy the code

For example, age and name are stored attributes. We need to distinguish between let and var:

  • letUsed to declare constants whose value, once set, cannot be changed
  • varUsed to declare variables whose values can be set to different values in the future.

1.2 Let and VAR cases

A class case:

Struct case:

1.3 Comparison between LET and VAR

1.3.1 Compile Angle analysis

Create the code first:

var age = 18
let x = 20
Copy the code

Assembly debugging:

From the point of view of assembly debugging, there is no difference, is to store the value in the register, the following LLDB debugging analysis

LLDB debugging looks no different, both are stored in __data.__common, and are adjacent addresses.

1.3.2 Sil Angle analysis

To compile main.swift to main.sil:

@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue let x: Int { get }
...
Copy the code
  • We can see from sil that var modifiers have get and set methods
  • All let-modified properties cannot be modified except for the GET method.

2. Calculate attributes

2.1 Overview of Computing Attributes

Stored properties are the most common. In addition to storing properties, classes, structures, and enumerations can also define computed properties. Computed properties do not store values; they provide getters and setters to modify and retrieve values. A stored property can be a constant or variable, but a calculated property must be defined as a variable. At the same time we must include the type when we write the computed property, because the compiler needs to know what the expected return value is.

The following area is the calculated attribute:

Struct Square {var width: Double let height: Double var area: Double {get {return width * height} set {self.width = newValue // newValue: 10, height: 20) s.area = 30Copy the code

2.2 Calculation of attribute SIL analysis

To compile main.swift to main.sil:

struct Square {
  @_hasStorage var width: Double { get set }
  @_hasStorage let height: Double { get }
  var area: Double { get set }
}
Copy the code

You can see that the calculated property does not have the @_hasstorage flag

2.3 private (set) analysis

Change area to private(set)

Struct Square {var width: Double let height: Double private(set) var area: Double = 40}Copy the code

Generate sil files:

struct Square {
  @_hasStorage var width: Double { get set }
  @_hasStorage let height: Double { get }
  @_hasStorage @_hasInitialValue private(set) var area: Double { get set }
}
Copy the code

As you can see from sil, the private(set) modifier is still a stored property, but the set method is private.

Attribute observer

3.1 Attribute observer analysis

The property observer will look for changes to the property value, a willSet is called when the property is about to change, even if the value is the same as the original value, whereas didSet is called after the property has changed. Their syntax is similar to getters and setters.

Look at the following code where willSet and didSet will be called:

class SubjectName { var subjectName: String = ""{ willSet{ print("subjectName will set value \(newValue)") } didSet{ print("subjectName has been changed \(oldValue)")}}} let s = SubjectName() s.subjectName = "Swift"  subjectName will set value Swift subjectName has been changedCopy the code

To do this internally, compile the above code into sil:

class SubjectName {
  @_hasStorage @_hasInitialValue var subjectName: String { get set }
}

// SubjectName.subjectName.setter
sil hidden @$s4main11SubjectNameC07subjectC0SSvs : $@convention(method) (@owned String, @guaranteed SubjectName) -> () {
   ...
  // function_ref SubjectName.subjectName.willset
  ...
  // function_ref SubjectName.subjectName.didset
  ...
} 
Copy the code

So you can see that both willset and didSet are called in setter methods.

3.2 Setting properties during initialization

  • One thing to note when using the property viewer here is that it is not called when the property is set during initializationwillSetdidSetThe observer;
  • They are called only when a new value is assigned to a fully initialized instance.
  • Run the following code and you will see that there is no output at the moment.
class SubjectName { var subjectName: String = ""{ willSet{ print("subjectName will set value \(newValue)") } didSet{ print("subjectName has been changed \(oldValue)") } } init(subjectName: String) { self.subjectName = subjectName; }} Let s = SubjectName(SubjectName: Swift)Copy the code

To see why this might be the case, look at the SIL file:

// SubjectName.init(subjectName:)
sil hidden @$s4main11SubjectNameC07subjectC0ACSS_tcfc : $@convention(method) (@owned String, @owned SubjectName) -> @owned SubjectName {
  ...
  %13 = ref_element_addr %1 : $SubjectName, #SubjectName.subjectName // user: %14
  %14 = begin_access [modify] [dynamic] %13 : $*String // users: %16, %15, %18
  %15 = load %14 : $*String                       // user: %17
  ...
}
Copy the code

Through the sil file, we see that the setter method is not called when initialized, but the address of the property is assigned directly, so the listener method is not called.

3.3 Calculate attribute observer

The above property observer only works on stored properties, what if we want to work on calculated properties? It’s as simple as adding the relevant code to the setter for the property. So let’s look at this code

class Square {
    var width: Double
    var area: Double {
        get {
            return width * width
        }
        set {
            self.width = sqrt(newValue)
        }
    }
    init(width: Double) {
        self.width = width
    }
}
Copy the code

3.4 Inheriting attribute observers

An observer under an inherited attribute looks like this:

class SSLTeacher {
    var age: Int {
        willSet{
            print("age will set value \(newValue)")
        } didSet{
            print("age has been changed \(oldValue)")
        }
    }
    var height: Double
    
    init(_ age: Int, _ height: Double) {
        self.age = age
        self.height = height
    }
}

class SSLParTimeTeacher: SSLTeacher {
    
    override var age: Int {
        willSet{
            print("override age will set value \(newValue)")
        } didSet{
            print("override age has been changed \(oldValue)")
        }
    }
    
    var subjectName: String
    
    init(_ subjectName: String) {
        self.subjectName = subjectName
        super.init(10, 180)
        self.age = 20
    }
}

var t = SSLParTimeTeacher("Swift")
Copy the code

Run the program to get the order of execution of methods under inherited attributes:

Override age will set value 20 // Subclass will set value 20 // parent class will age has been changed 10 // parent class did override Age has been changed 10 // Subclass didCopy the code

4. Deferred storage properties

4.1 Use of deferred storage attributes

  • The lazy keyword is used to identify a lazy storage attribute, which must have an initial value:

    class Person {
        lazy var age: Int = 18
    }
    Copy the code
  • The initial value of a deferred storage attribute is not evaluated until it is first used

    • Age has no value after initialization, as shown below:

    • After calling the getter method for age, we can see that age already has a value:

4.2 Sil principle exploration

4.2.1 Initialization Process

First, generate the sil file and observe whether the age declaration has a. Symbol indicating that age is an optional type

So the age initialization, by default, is assigned Optional. None, which is 0

4.2.2 Call Procedure

Look at the getter method for age and see how it is called:

// Person.age.getter sil hidden [lazy_getter] [noinline] @$s4main6PersonC3ageSivg : $@convention(method) (@guaranteed Person) -> Int { // %0 "self" // users: %14, %2, %1 bb0(%0 : $Person): debug_value %0 : $Person, let, name "self", argno 1 // id: %1 %2 = ref_element_addr %0 : $Person, #Person.$__lazy_storage_$_age // user: %3 %3 = begin_access [read] [dynamic] %2 : $*Optional<Int> // users: %4, %5 %4 = load %3 : $*Optional<Int> // user: %6 end_access %3 : $*Optional<Int> // id: %5 switch_enum %4 : $Optional<Int>, case #Optional.some! enumelt: bb1, case #Optional.none! enumelt: bb2 // id: %6 // %7 // users: %9, %8 bb1(%7 : $Int): // Preds: bb0 debug_value %7 : $Int, let, name "tmp1" // id: %8 br bb3(%7 : $Int) // id: %9 bb2: // Preds: bb0 %10 = integer_literal $Builtin.Int64, 18 // user: %11 %11 = struct $Int (%10 : $Builtin.Int64) // users: %18, %13, %12 debug_value %11 : $Int, let, name "tmp2" // id: %12 %13 = enum $Optional<Int>, #Optional.some! enumelt, %11 : $Int // user: %16 %14 = ref_element_addr %0 : $Person, #Person.$__lazy_storage_$_age // user: %15 %15 = begin_access [modify] [dynamic] %14 : $*Optional<Int> // users: %16, %17 store %13 to %15 : $*Optional<Int> // id: %16 end_access %15 : $*Optional<Int> // id: %17 br bb3(%11 : $Int) // id: %18Copy the code
  • The getter calls get the address of age to see if it has a value
  • If there are no valuesOptional.none, will be calledbb2That gets the value and assigns it to the address space of age
  • If it has a value, it will be calledbb1, returns the value directly
  • So deferred storage properties are not thread-safe.

5. Type attributes

5.1 Preliminary study on type attributes

  • A type attribute is really just a global variable
  • Type attributes are initialized only once
Class SSLTeacher {// only initialized once static var age: Int = 18} // Can modify sslteacher.age = 30Copy the code

5.2 SIL & Source code analysis

The sil file is generated and the builtin “once” call is seen when the call is initialized

Builtin “once” (swift_once); builtin “once” (swift_once);

void swift::swift_once(swift_once_t *predicate, void (*fn)(void *),
                       void *context) {
#ifdef SWIFT_STDLIB_SINGLE_THREADED_RUNTIME
  if (! *predicate) {
    *predicate = true;
    fn(context);
  }
#elif defined(__APPLE__)
  dispatch_once_f(predicate, context, fn);
#elif defined(__CYGWIN__)
  _swift_once_f(predicate, context, fn);
#else
  std::call_once(*predicate, [fn, context]() { fn(context); });
#endif
}
Copy the code

Source code implementation can see dispatch_once_F, this is not our GCD it!!

5.3 Implementation of singleton

Class SSLTeacher {static let sharedInstance = SSLTeacher(); Private outside access is less than the init () {}} SSLTeacher. SharedInstanceCopy the code

Attributes and Mach-O

6.1 fieldDescriptor && FieldRecord

In the last article exploring method scheduling we learned about Type Descriptors, which is where v-tables are recorded, and then we need to learn about field Descriptors in Type Descriptors

struct TargetClassDescriptor { 
    var flags: UInt32
    var parent: UInt32
    var name: Int32
    var accessFunctionPointer: Int32
    var fieldDescriptor: Int32
    var superClassType: Int32
    var metadataNegativeSizeInWords: UInt32 
    var metadataPositiveSizeInWords: UInt32 
    var numImmediateMembers: UInt32
    var numFields: UInt32
    var fieldOffsetVectorOffset: UInt32 
    var Offset: UInt32
    var size: UInt32
    //V-Table
}
Copy the code

FieldDescriptor records the current property information, where the structure of the fieldDescriptor in the source code is as follows:

struct FieldDescriptor {
    MangledTypeName int32
    Superclass      int32
    Kind            uint16
    FieldRecordSize uint16
    NumFields       uint32
    FieldRecords    [FieldRecord]
}
Copy the code

NumFields represents how many attributes there are, and FieldRecords records information about each attribute. The structure of FieldRecords is as follows:

struct FieldRecord {
    Flags uint32 
    MangledTypeName int32 
    FieldName int32
}
Copy the code

6.2 Location information of attributes in the Mach-O file

Start by creating a class and compiling a Mach-O file

class SSLTeacher {
    var age = 18
    var age2 = 20
}
Copy the code

Next in the Mach-O file, calculate and look for information about the attributes

  • First compute the address of the typeDescriptor in Mach-O

    FFFFFF2C + 3F40 = 0x100003E6C
    0x100003E6C - 0x100000000 = 3E6C
    Copy the code

  • Positioning to 3 e6c

  • Shift 4 4 bytes, find the fieldDescriptor, and compute the address of the fieldDescriptor

    3E7C + 9C = 3F18
    Copy the code
  • Locate the fieldDescriptor and offset 4 4-bytes to find the FieldRecords

  • Computes the address of age’s FieldName in Mach-O

    3F30 + FFFFFFDF = 0x100003F0F
    0x100003F0C - 0x1000000000 = 3F0F
    Copy the code
  • Successfully located age’s FieldName in Mach-O!!