First, storage properties

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 LGTeacher{ 
    var age: Int
    var name: String 
}
Copy the code

Let is used to declare constants. The value of a constant cannot be changed once it is set. Var is used to declare variables whose values can be set to different values in the future.

1 letvarThe difference between:

var age = 18
let x = 20
Copy the code

1.1 assemblyAngle analysis of

Is whether or notletorvarIn terms of compilation, essentiallyletandvarThey’re addresses. There’s no difference between the two

1.2 SILAngle analysis of

@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue let x: Int { get }
Copy the code

Var x: Only get methods

They’re all store properties, they all have an initial value and the default store properties are all pretty composite get/set methods store properties access is GET, assignment is set, let doesn’t generate set methods; When used, the let modifier will not change the value, and the var modifier will change the value.


Ii. Calculating attributes (Computed Property)

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.

Struct square{struct square; struct square; struct square; Double { get{ return width * width } set{ self.width = newValue } } }Copy the code

2.1 read-onlyCalculate attribute

There is no set, only get

  • A model:
struct square{
    var width: Double = 30
    var area: Double {
        get{
            return width * width
        }
    }

}
Copy the code

Area does not assign to either internal or external values, but only reads data

  • Model 2:
struct square{
    var width: Double = 30
    private(set) var area: Double = 40
    func test() {
        self.area
    }
}
Copy the code

SIL analysis

struct square {
  @_hasStorage @_hasInitialValue var width: Double { get set }
  @_hasStorage @_hasInitialValue private(set) var area: Double { get set }
  func test()
  init()
  init(width: Double = 30, area: Double = 40)
}
Copy the code

Externally, there is only the get attribute, and no assignment; It can be read and assigned internally

Attribute observer

3.1 the observerwillSet && didSet

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.

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

SIL analysis


3.2 The observerInitialization operation

One thing to note here when we use the property viewer is that the willSet and didSet observers are not called when the property is set during initialization; 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 chaged \(oldValue)") } } init(subjectName: String) {// Initialize operation self.subjectName = subjectName}} Let s = subjectName (subjectName: "Swift advanced ")Copy the code

SIL analysis

Class to copy the value directly from memory to the property

3.3 The observer -> Calculate attribute

Just add the relevant code to the setter for the property without writing the observe property, as shown in the following example:

3.4 The observer– > inheritance

I’m going to write an LGTeacher class, and then I’m going to inherit LGTeacher, and I’m going to change the value of the inherited class

class LGTeacher { 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 LGParTimeTeacher: LGTeacher { 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(18, 180.0) self.age = 20}} let t = LGParTimeTeacher("Swift")Copy the code

Execution Result:

override age will set value 20
age will set value 20
age has been changed 18
override age has been changed 18
Program ended with exit code: 0
Copy the code

It can be concluded that the call order is:

Override willSet -> willSet -> didSet -> override didSet

SIL analysis:


4. Deferred storage properties

  • The initial value of a deferred storage attribute is not evaluated until it is first used.
  • With keywordslazyTo identify a deferred storage property
class Subject {
    lazy var age: Int = 18
}
var s = Subject()
print(s.age)

print("end")
Copy the code
  • LLDBDebugging analysis:

As shown in the figure above: Unused Used changed from 0x0000000000000000 to 0x0000000000000012, so the calculation is performed when it is used for the first time.

  • SILAnalysis:

Lazy is initialized, and it is optional, not nil by default; Final modifier, cannot be overridden.


Observe the implementation of subject.age.getter

First access to __lazy_storage_ address and memory address value to register %4 enumerated pattern matching, have value go bb1 code block, no value go BB2 code block

bb2Module, no value before the address assignment operation

Bb1 module, directly return the original value to out

Lazy is an optional value that is assigned to the attribute the first time it is used

5. Type attributes

5.1 Type Attributes

  • A type attribute is really just a global variable
  • Type attributes are initialized only once
class LGTeacher{
    static var age: Int = 18
}
LGTeacher.age = 30
Copy the code

SIL analysis

// one-time initialization token for age
sil_global private @$s4main9LGTeacherC3age_Wz : $Builtin.Word
// static LGTeacher.age
sil_global hidden @$s4main9LGTeacherC3ageSivpZ : $Int
Copy the code

Age becomes a global variable, the global declares age; Static is essentially a global variable

5.2 Singleton Mode

class LGTeacher{
    static let sharedInstance = LGTeacher()
    private init(){}
}
Copy the code

Initialize privatized, externally accessible only through sharedInstance


The location of attributes in Mahco files

6.1 Information structure analysis of attributes

In the first article, we reviewed the Metadata structure:

struct Metadata{ 
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer 
    var iVarDestroyer: UnsafeRawPointer
}
Copy the code

In the last article, you learned about Type Descriptors during method scheduling, which recorded information about V-tables. Now you 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

Flags: flag bit. MangledTypeName: Indicates the type of the current attribute. FieldName Attribute name

6.2 Finding attribute Information in a Mach-O file

Define an attribute structure and compile to generate a Mach-O file

class LGTeacher {
    var age  = 18
    var age1 = 20
}
Copy the code

How do I find a Mach-O file?

  • First display the project Products directory

  • withMachOViewOpen theMach-oExecutable file.

Find the addresses of Descriptor in the Mach-o file, add them, subtract 0x100000000(the base address of virtual memory), and get a new address: 0x3D74

0x3EFC + 0xFFFFFE78 = 0x100003D74
0x100003D74 - 0x100000000 = 0x3D74
Copy the code

0x3D74The location of the file in Mach-o is as follows

0x3D74 In the Mach-o file where the const address is 50 00 00 80, according to the TargetClassDescriptor class, find the fieldDescriptor by offsetting 4 member sizes (4 4-bytes).

50 01 00 00 is the offset information of a fieldDescriptor. To find the address of a fieldDescriptor, add the offset information. The calculation process is as follows (in small-endior-mode, read from right to left) :

0x3D80 + 0x4 + 0x0150 = 0x3ED4
Copy the code

According to the calculated result 0x3ED4, the location of the Mach-O file is as follows:

0x3ED4 is the start address of the FieldDescriptor. To find the address of the FieldRecords, offset the size of the member attribute before the FieldRecords. MangledTypeName 2 bytes, Superclass 2 bytes, Kind 2 bytes, FieldRecordSize 4 bytes, NumFields 4 bytes


FieldName address calculation process:

0x3EE4 + 0x4 + 0x4 + 0xFFFFDF = 0x100003ECB // Minus the base address of virtual memory 0x100003ECB-0x100000000 = 0x3ECBCopy the code

The calculated result is 0x3ECB, where Mach-o is as follows:

As shown, 0x3ECB is at reflStr in the Mach-o file

  • 00, 61, 67, 65: age
  • 31, 61, 67, 65: age1