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:
let
Used to declare constants whose value, once set, cannot be changedvar
Used 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 initialization
willSet
和didSet
The 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 values
Optional.none
, will be calledbb2
That gets the value and assigns it to the address space of age - If it has a value, it will be called
bb1
, 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!!