In swift, attributes are mainly divided into the following categories
-
Storage properties
-
Calculation properties
-
Deferred storage properties
-
Type properties
Storage properties
There are two kinds of storage attributes:
-
Or it is a constant storage attribute, that is, let modification
-
Or variable storage attributes, namely var modification
The following codes are defined
class CJLTeacher{ var age: Int = 18 var name: String = "CJL" } let t = CJLTeacher()
age and name in the code are variable storage attributes, which can be reflected in SIL
class CJLTeacher { //_ hasStorage indicates that it is a storage attribute @_hasStorage @_hasInitialValue var age: Int { get set } @_hasStorage @_hasInitialValue var name: String { get set } @objc deinit init() }
Storage attribute characteristics: it will occupy the memory space of the allocated instance object
Let's use breakpoint debugging to verify
-
po t
-
x/8g memory address, that is, the address stored in HeapObject
Property - 1
Attribute - 2
Calculation properties
Calculation attribute: refers to the attribute that does not occupy memory space. It is essentially a set/get method attribute
Let's illustrate with a demo. Is the following correct?
class CJLTeacher{ var age: Int{ get{ return 18 } set{ age = newValue } } }
In actual programming, the compiler will report the following warning, which means that age is called again in the set method of age set
Attribute - 3
Then the operation found that the crash was caused by calling age. in the set method of age.set causes a circular reference, that is, recursion
Properties - 4
Verification: no memory
For the feature that it does not occupy memory space, we can verify it by printing the memory size of the following classes through the following cases
class Square{ var width: Double = 8.0 var area: Double{ get{ //The return here can be omitted, and the compiler will deduce it automatically return width * width } set{ width = sqrt(newValue) } } } print(class_getInstanceSize(Square.self)) //*********Print results********* 24
It can be seen from the results that the memory size of class Square is 24, which is equal to 16 bytes + width (8 bytes) = 24 of class (metadata + refCounts). Area is not added. It can be proved that the area attribute does not occupy memory space.
Verification: the essence is set/get method
-
Set main Convert swift to SIL file: swift C - emit SIL main swift >> ./ main. sil
-
View the SIL file. For storage properties, there are_ Identifier of the hasStorage
class Square { @_hasStorage @_hasInitialValue var width: Double { get set } var area: Double { get set } @objc deinit init() }
-
For calculated attributes, there are only setter and getter methods in SIL
Attribute - 5
Attribute observer (didSet, willSet)
-
willSet: call newValue before new value store.
-
didSet: after the new value is stored, call oldValue.
verification
-
It can be verified by demo
class CJLTeacher{ var name: String = "test"{ //The new value is called before storage. willSet{ print("willSet newValue \(newValue)") } //The new value is called after storage. didSet{ print("didSet oldValue \(oldValue)") } } } var t = CJLTeacher() t.name = "CJL" //**********Print results********* willSet newValue CJL didSet oldValue test
-
It can also be verified by compiling, and main Swift is compiled into mail sil, find the set method of name in the sil file
Properties - 6
Question 1: will the attribute observer be triggered in the init method?
In the following code, if the name is set in the init method, will the attribute observer be triggered?
class CJLTeacher{ var name: String = "test"{ //The new value is called before storage. willSet{ print("willSet newValue \(newValue)") } //The new value is called after storage. didSet{ print("didSet oldValue \(oldValue)") } } init() { self.name = "CJL" } }
The operation results show that the printing methods in willSet and didSet are not followed, so the following conclusions are drawn:
-
In the init method, if the attribute is called, the attribute observer will not be triggered
-
init mainly initializes the current variable. In addition to the default first 16 bytes, other attributes will call memset to clean up the memory space (because it may be dirty data, i.e. used by others), and then assign values
[summary] initializer (that is, init method setting) and setting default value (that is to call other attribute values in didSet) will not trigger.
Question 2: where can I add an attribute observer?
There are three main places to add:
-
1. Class
-
2. Storage properties inherited through classes
class CJLMediumTeacher: CJLTeacher{ override var age: Int{ //The new value is called before storage. willSet{ print("willSet newValue \(newValue)") } //The new value is called after storage. didSet{ print("didSet oldValue \(oldValue)") } } }
-
3. Calculated properties inherited through class
class CJLTeacher{ var age: Int = 18 var age2: Int { get{ return age } set{ self.age = newValue } } } var t = CJLTeacher() class CJLMediumTeacher: CJLTeacher{ override var age: Int{ //The new value is called before storage. willSet{ print("willSet newValue \(newValue)") } //The new value is called after storage. didSet{ print("didSet oldValue \(oldValue)") } } override var age2: Int{ //The new value is called before storage. willSet{ print("willSet newValue \(newValue)") } //The new value is called after storage. didSet{ print("didSet oldValue \(oldValue)") } } }
Question 3: when the calculated attributes of the subclass and parent class exist didset and willset at the same time, what is the calling order?
What is the calling order of the following code?
class CJLTeacher{ var age: Int = 18{ //The new value is called before storage. willSet{ print("Parent class willSet newValue \(newValue)") } //The new value is called after storage. didSet{ print("Parent class didSet oldValue \(oldValue)") } } var age2: Int { get{ return age } set{ self.age = newValue } } } class CJLMediumTeacher: CJLTeacher{ override var age: Int{ //The new value is called before storage. willSet{ print("Subclass newValue \(newValue)") } //The new value is called after storage. didSet{ print("Subclass didSet oldValue \(oldValue)") } } } var t = CJLMediumTeacher() t.age = 20
The operation results are as follows:
Properties - 7
Conclusion: for the same attribute, both subclass and parent have attribute observers. The order is: first subclass willset, then parent willset, in parent didset, the didset of subclass, that is, child parent and child
Question 4: if the child class calls the init of the parent class, will the observation attribute be triggered?
On the basis of question 3, modify the CJLMediumTeacher class
class CJLMediumTeacher: CJLTeacher{ override var age: Int{ //The new value is called before storage. willSet{ print("Subclass willSet newValue \(newValue)") } //The new value is called after storage. didSet{ print("Subclass didSet oldValue \(oldValue)") } } override init() { super.init() self.age = 20 } } //******Print results****** Subclass willSet newValue 20 Parent class willSet newValue 20 Parent class didSet oldValue 18 Subclass didSet oldValue 18
It is found from the print results that the attribute observer will be triggered, mainly because the subclass has called the init of the parent class and has been initialized, and the initialization process ensures that all attributes have values (that is, super.init ensures that the variable initialization is completed), so the attribute can be observed
Delay attribute
The delay attribute mainly includes the following descriptions:
-
1. Storage properties decorated with lazy
-
2. The delay property must have a default initial value
-
3. Deferred storage is not assigned until the first access
-
4. Deferred storage properties do not guarantee thread safety
-
5. Effect of deferred storage properties on the size of instance objects
Let's analyze them one by one
1. Storage properties decorated with lazy
class CJLTeacher{ lazy var age: Int = 18 }
2. The delay property must have a default initial value
If it is defined as an optional type, an error will be reported, as shown below
Properties - 8
3. Deferred storage is not assigned until the first access
You can view the memory changes of instance variables through debugging
-
Memory condition before the first access of age: at this time, age has no value, which is 0x0
Properties - 9
-
Memory condition after the first access of age: at this time, age has a value of 30
Properties - 10
Thus, it can be verified that the lazy load storage attribute is assigned only on the first access
We can also view it through the SIL file. Here, when generating the SIL file, we can add the command to restore the confused name in swift (i.e. xcrun swift demand): swift C - emit SIL main swift | xcrun swift-demangle >> ./ main. sil && code main. SIL, demo code is as follows
class CJLTeacher{ lazy var age: Int = 18 } var t = CJLTeacher() t.age = 30
-
The storage attribute modified by class + main: lazy is an optional type at the bottom
Properties - 11
-
setter+getter: it can be verified from the getter method that the operation with no value becomes the operation with value at the first access
Properties - 12
Through sil, the following two points are explained:
-
1. The attribute modified by lazy is optional by default at the bottom level. When it is not accessed, it is nil by default. The performance in memory is 0x0. During the first visit, the getter method of attribute is invoked, and its internal implementation is an assignment operation through the branch of the current enum.
-
2. Is the optional type 16 bytes? You can print through MemoryLayout
-
Size: actual size
-
Stripe: allocated size (mainly due to memory alignment)
-
print(MemoryLayout<Optional<Int>>.stride) print(MemoryLayout<Optional<Int>>.size) //***********Print results*********** 16 9
Why is the actual size 9? Optional is essentially an enum, in which Int occupies 8 bytes, and the other byte is mainly used to store case values (this will be explained in detail later)
4. Deferred storage properties do not guarantee thread safety
Continue to analyze the sil file in 3, mainly to view the getter method of age. If there are two threads at this time:
-
Thread 1 accesses age at this time. Its age has no value and enters the bb2 process
-
Then, the time slice allocates the CPU to thread 2. For optional, it is still none. You can also go to bb2 process
-
Therefore, at this time, thread 1 will go through the assignment, and thread 2 will also go through the assignment, which does not guarantee that the attribute is initialized only once
5. Effect of deferred storage properties on the size of instance objects
Next, let's continue to see if there are any changes between the memory that does not use lazy and the memory that uses lazy?
-
Without using the lazy modifier, the memory size of the class is 24
-
With the lazy modifier, the memory size of the class is 32
Thus, it can be proved that the memory size of its instance object is different with and without lazy
Type properties
Type attribute mainly includes the following descriptions:
-
1. It is decorated with the keyword static and is a global variable
-
2. Type properties must have a default initial value
-
3. Type properties are initialized only once
1. Use keyword static modifier
class CJLTeacher{ static var age: Int = 18 } // ****Use**** var age = CJLTeacher.age
Generate SIL file
-
Check the definition and find one more global variable. In other words, the type attribute is a global variable
Properties - 15
-
View the get of age in the entry function
Properties - 16
-
View getter method of age
-
Where: globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0 is a global variable initialization function
-
builtin "once". Through breakpoint debugging, it is found that Swift is called_ Once, indicating that the property is initialized only once
-
-
Search swift in the source code_ Once, which is the internal dispatch through GCD_ once_ F single example implementation. From here you can verify point 3 above
void swift::swift_once(swift_once_t *predicate, void (*fn)(void *), void *context) { #if 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 }
2. Type properties must have a default initial value
As shown in the figure below, if the default initial value is not given, an error will be reported
Properties - 20
Therefore, for type attributes, one is global variables, which are initialized only once, and the other is thread safe
Singleton creation
//******Swift single case****** class CJLTeacher{ //1. Use static + let to create and declare an instance object static let shareInstance = CJLTeacher.init() //2. Add private access to the current init private init(){ } } //Use (only through singleton, not init) var t = CJLTeacher.shareInstance //******OC single case****** @implementation CJLTeacher + (instancetype)shareInstance{ static CJLTeacher *shareInstance = nil; dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shareInstance = [[CJLTeacher alloc] init]; }); return shareInstance; } @end
summary
-
Storing properties takes up memory space of instance variables, and
-
Computing properties does not occupy memory space. Its essence is set/get methods
-
Attribute observer
-
Before the new value of willset: is stored, call the sub class first, then notify the parent class (because the parent class may need to do some extra operations), that is, the child parent.
-
didSet: after the new value is stored, tell the parent class first, and then notify the child class (the operation of the parent class takes precedence over the child class), that is, the parent and child
-
The assignment of init method in class does not trigger attribute observation
-
The attribute can be added to the storage attribute, inherited storage attribute and inherited calculation attribute defined by the} class
-
When a subclass calls the init method of the parent class, the observation attribute will be triggered
-
-
Deferred storage properties
-
Use lazy to decorate the storage attribute, and there must be a default value
-
It is only assigned when it is accessed for the first time, and it is thread unsafe
-
Using lazy and not using lazy will affect the memory size of the instance object, mainly because lazy is an optional type at the bottom. The essence of optional is enum. In addition to storing the memory size of the attribute itself, a byte is also required to store case
-
-
Type properties
-
Create instance variables using static + let
-
The access permission of init method is private
-
Use static} decoration, and there must be a default initial value
-
Is a global variable that can only be initialized once. It is thread safe
-
To create a singleton object:
-