attribute

struct Circle {
    // Store attributes
    var radius: Double
    // Calculate attributes
    var diamiter: Double {
        set { 
            radius = newValue / 2
        }
        get {
            radius * 2}}}Copy the code
  • SwiftInstance-related attributes in the
    • Storage properties (Stored Property)
      • Similar to the concept of a member variable
      • Stored in the instance’s memory
      • Structures and classes can define storage properties
      • The enumerationCan not beDefining storage properties

We know that enumeration memory can hold all of themcaseAs well asThe associated valuesThere is no such thing as a member variable, and therefore no such thing as a member variableStorage properties* Calculate attributes (Computed Property+ The essence is the method (function) This can also be proved by assembly + does not occupy the memory of the instanceEnumerations, structs, and classes can all define computed properties

Storage properties

  • For storage properties,SwiftThere is a clear rule
    • When creating aclassThe structure of the bodyWhen the class/structure creates an instance, all its memory must be initialized. The storage properties are stored in the instance’s memory, so all the storage properties must be initialized.
      1. You can set an initial value for the storage property in the initializer
      2. You can assign a default property value as part of the property definition

Calculate attribute

  • setThe new value passed in is called by defaultnewValue, can also be customized
  • Defining computed attributes can only be usedvarAnd don’t uselet
    • letStands for constant, which means the value is immutable
    • The value of a calculated property can change (even if it is a read-only calculated property)
  • Read-only computed property: onlyget, there is noset

Enumerate rawValue principle

  • Enumerate raw valuesrawValueThe essence of:Read-only computed propertyYou can prove it by looking directly at the assembly

Lazy Stored Property

Look at this code

class Car {
    init(a) {
        print("Car init")}func run(a) {
        print("Car is running!")}}class Person {
    var car = Car(a)init(a) {
        print("Person init")}func  goOut(a) {
        car.run()
    }
}

let p = Person(a)print("-- -- -- -- -- -- -- -- -- -- -")
p.goOut()
Copy the code

The result is as follows

Car init
Person init
-----------
Car is running!
Program ended with exit code: 0
Copy the code

We add a lazy modifier to the car attribute of the code above

class Car {
    init(a) {
        print("Car init")}func run(a) {
        print("Car is running!")}}class Person {
    lazy var car = Car(a)init(a) {
        print("Person init")}func  goOut(a) {
        car.run()
    }
}

let p = Person(a)print("-- -- -- -- -- -- -- -- -- -- -")
p.goOut()
Copy the code

Now let’s look at the results

Person init
-----------
Car init
Car is running!
Program ended with exit code: 0
Copy the code

Lazy delays the initialization of var car until it is first used. In this case, the code p.goout () is executed before the car is initialized

Modified by lazy keywords storage should be delayed storage attribute, the benefits of this feature is obvious, because some properties initialized may need to spend a lot of resources, and probably in some rare cases will be triggered to use, so lazy keyword can be used in this case, make the core object initialization quick and light weight. Take this example

class PhotoView {
    lazy var image: Image = {
        let url = "https://www.520it.com/xx.png"
        let data = Data(url: url)
        return Image(dada: data)
    }()
}
Copy the code

Network image loading is always need some time, in the above example image loading process of encapsulated inside the closure expressions, and the return value as the initialization of an image attribute assignment, through the lazy, just tell the load back to the process of image in the actual was used to carry out, so that it can promote the app smooth degree, Improve the stuck situation.

  • uselazyYou can define a deferred storage property that is initialized only when the property is first used
  • lazyThe property must bevar, not alet
    • This requirement is easy to understand,letMust be inThe instanceHas values before the initialization method oflazyIt happens to initialize one of its properties at some point after the instance is created and initialized, solazyScope-onlyvarattribute
  • If multiple threads are accessing for the first timelazyProperty, there is no guarantee that the property will only be initialized1time

Note on deferred storage properties

  • Only when the structure contains a deferred storage propertyvarTo access the deferred storage properties

This is because delayed initialization requires a change in the structure’s memoryIn this case, becausepIs constant, so the contents of memory cannot change after initialization, but p.z will make the structurePointthelazy var zProperty is initialized because the members of the structure are in the structure’s memory, so the structure’s memory needs to be changed, hence the following error.

Property Observer

  • Can I do forThe lazythevarStore properties set property viewer
  • willSetWill pass the new value, default is callednewValue
  • didSetWill pass the old value, default is calledoldValue
  • Setting a property value in an initializer does not startwillSetanddidSet
  • Setting the initial value at the time of the property definition also does not startwillSetanddidSet
struct Circle {
    var radius: Double {
        willSet {
            print("willSet", newValue)
        }

        didSet {
            print("didSet", oldValue, radius)
        }
    }

    init(a) {
        self.radius = 1.0
        print("Circle init!")}}var circle = Circle()
circle.radius = 10.5
print(circle.radius)
Copy the code

The results

Circle init!
willSet 10.5
didSet 1.0 10.5
10.5
Program ended with exit code: 0
Copy the code

Global variable, local variable

Attribute observer and attribute calculation functions can also be applied to global and local variables

var num: Int {
   get {
       return 10
   }
   set {
       print("setNum", newValue)
   }
}
num = 12
print(num)


func test(a) {
   var age = 10 {
       willSet {
           print("willSet", newValue)
       }
       didSet {
           print("didSet", oldValue, age)
       }
   }

   age = 11
}
test()

Copy the code

inoutA restudy of

First look at the following code

func test(_ num: inout Int) {
    num = 20
}

var age = 10
test(&age) // Add a breakpoint here
Copy the code

Run the program to the breakpoint and observe assembly

SwiftTest`main:
    0x1000010b0 <+0>:  pushq  %rbp
    0x1000010b1 <+1>:  movq   %rsp, %rbp
    0x1000010b4 <+4>:  subq   $0x30, %rsp
    0x1000010b8 <+8>:  leaq   0x6131(%rip), %rax        ; SwiftTest.age : Swift.Int
    0x1000010bf <+15>: xorl   %ecx, %ecx
    0x1000010c1 <+17>: movq   $0xa, 0x6124(%rip)        ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
    0x1000010cc <+28>: movl   %edi, -0x1c(%rbp)
->  0x1000010cf <+31>: movq   %rax, %rdi
    0x1000010d2 <+34>: leaq   -0x18(%rbp), %rax
    0x1000010d6 <+38>: movq   %rsi, -0x28(%rbp)
    0x1000010da <+42>: movq   %rax, %rsi
    0x1000010dd <+45>: movl   $0x21, %edx
    0x1000010e2 <+50>: callq  0x10000547c               ; symbol stub for: swift_beginAccess
    0x1000010e7 <+55>: leaq   0x6102(%rip), %rdi        ; SwiftTest.age : Swift.Int
    0x1000010ee <+62>: callq  0x100001110               ; SwiftTest.test(inout Swift.Int) -> () at main.swift:658
    0x1000010f3 <+67>: leaq   -0x18(%rbp), %rdi
    0x1000010f7 <+71>: callq  0x10000549a               ; symbol stub for: swift_endAccess
    0x1000010fc <+76>: xorl   %eax, %eax
    0x1000010fe <+78>: addq   $0x30, %rsp
    0x100001102 <+82>: popq   %rbp
    0x100001103 <+83>: retq  
Copy the code

We can see the functiontestBefore the call, the parameters are passed as followsIn the simpler case, we know thatinoutIs essentially passing references. Next, let’s consider some more complicated cases

struct Shape {
    var width: Int
    var side: Int {
        willSet {
            print("willSetSide", newValue)
        }
        didSet {
            print("didSetSide", oldValue, side)
        }
    }

    var girth: Int {
        set {
            width = newValue / side
            print("setGirth", newValue)
        }
        get {
            print("getGirth")
            return width * side
        }
    }

    func show(a) {
        print("width= \(width), side= \(side), girth= \(girth)")}}func test(_ num: inout Int) {
    num = 20
}



var s = Shape(width: 10, side: 4)
test(&s.width)	/ / breakpoint 1
s.show()
print("-- -- -- -- -- -- -- -- -- -- -- -- --")
test(&s.side)   2 / / points
s.show()
print("-- -- -- -- -- -- -- -- -- -- -- -- --")
test(&s.girth)  / / breakpoint 3
s.show()
print("-- -- -- -- -- -- -- -- -- -- -- -- --")
Copy the code

In the above case, the type of global variable S is Struct Shape, which stores two storage properties width and side, in which side has an attribute observer, and Shape also has a calculation attribute girth. We first run the program without breakpoints and observe the results

getGirth
width= 20, side= 4, girth= 80
-------------
willSetSide 20
didSetSide 4 20
getGirth
width= 20, side= 20, girth= 400
-------------
getGirth
setGirth 20
getGirth
width= 1, side= 20, girth= 20
-------------
Program ended with exit code: 0
Copy the code

Inout works on all three attributes, so how is it handled and implemented? We still have to find out through compilation. To facilitate assembly analysis, we intercept part of the code to compile and run





Let’s start with the general properties

struct Shape {
    var width: Int
    var side: Int {
        willSet {
            print("willSetSide", newValue)
        }
        didSet {
            print("didSetSide", oldValue, side)
        }
    }

    var girth: Int {
        set {
            width = newValue / side
            print("setGirth", newValue)
        }
        get {
            print("getGirth")
            return width * side
        }
    }

    func show(a) {
        print("width= \(width), side= \(side), girth= \(girth)")}}func test(_ num: inout Int) {
    num = 20
}

var s = Shape(width: 10, side: 4)
test(&s.width) // At the breakpoint, pass the general property width as the inout argument to test
Copy the code

The results are as follows

SwiftTest`main:
    0x100001310 <+0>:   pushq  %rbp
    0x100001311 <+1>:   movq   %rsp, %rbp
    0x100001314 <+4>:   subq   $0x30, %rsp
    0x100001318 <+8>:   movl   $0xa, %eax
    0x10000131d <+13>:  movl   %edi, -0x1c(%rbp)
    0x100001320 <+16>:  movq   %rax, %rdi
    0x100001323 <+19>:  movl   $0x4, %eax
    0x100001328 <+24>:  movq   %rsi, -0x28(%rbp)
    0x10000132c <+28>:  movq   %rax, %rsi
    0x10000132f <+31>:  callq  0x100001d60               ; SwiftTest.Shape.init(width: Swift.Int, side: Swift.Int) - >SwiftTest.Shape at main.swift:630
    0x100001334 <+36>:  leaq   0x6ebd(%rip), %rcx        ; SwiftTest.s : SwiftTest.Shape
    0x10000133b <+43>:  xorl   %r8d, %r8d
    0x10000133e <+46>:  movl   %r8d, %esi
    0x100001341 <+49>:  movq   %rax, 0x6eb0(%rip)        ; SwiftTest.s : SwiftTest.Shape
    0x100001348 <+56>:  movq   %rdx, 0x6eb1(%rip)        ; SwiftTest.s : SwiftTest.Shape + 8
->  0x10000134f <+63>:  movq   %rcx, %rdi
    0x100001352 <+66>:  leaq   -0x18(%rbp), %rax
    0x100001356 <+70>:  movq   %rsi, -0x30(%rbp)
    0x10000135a <+74>:  movq   %rax, %rsi
    0x10000135d <+77>:  movl   $0x21, %edx
    0x100001362 <+82>:  movq   -0x30(%rbp), %rcx
    0x100001366 <+86>:  callq  0x100006312               ; symbol stub for: swift_beginAccess
    0x10000136b <+91>:  leaq   0x6e86(%rip), %rdi        ; SwiftTest.s : SwiftTest.Shape
    0x100001372 <+98>:  callq  0x100001d70               ; SwiftTest.test(inout Swift.Int) -> () at main.swift:658
    0x100001377 <+103>: leaq   -0x18(%rbp), %rdi
    0x10000137b <+107>: callq  0x100006330               ; symbol stub for: swift_endAccess
    0x100001380 <+112>: xorl   %eax, %eax
    0x100001382 <+114>: addq   $0x30, %rsp
    0x100001386 <+118>: popq   %rbp
    0x100001387 <+119>: retq
Copy the code

The parameter transfer process is shown as follows

So for a normal storage property, the test function simply passes in its address value.

Now, for an intuitive comparison, let’s look at the case of calculating properties

struct Shape {
    var width: Int
    var side: Int {
        willSet {
            print("willSetSide", newValue)
        }
        didSet {
            print("didSetSide", oldValue, side)
        }
    }

    var girth: Int {
        set {
            width = newValue / side
            print("setGirth", newValue)
        }
        get {
            print("getGirth")
            return width * side
        }
    }

    func show(a) {
        print("width= \(width), side= \(side), girth= \(girth)")}}func test(_ num: inout Int) {
	print("Start test function")
    num = 20
}

var s = Shape(width: 10, side: 4)
test(&s.girth)
Copy the code

The breakpoints are compiled as follows

SwiftTest`main:
    0x1000012f0 <+0>:   pushq  %rbp
    0x1000012f1 <+1>:   movq   %rsp, %rbp
    0x1000012f4 <+4>:   pushq  %r13
    0x1000012f6 <+6>:   subq   $0x38, %rsp
    0x1000012fa <+10>:  movl   $0xa, %eax
    0x1000012ff <+15>:  movl   %edi, -0x2c(%rbp)
    0x100001302 <+18>:  movq   %rax, %rdi
    0x100001305 <+21>:  movl   $0x4, %eax
    0x10000130a <+26>:  movq   %rsi, -0x38(%rbp)
    0x10000130e <+30>:  movq   %rax, %rsi
    0x100001311 <+33>:  callq  0x100001d60               ; SwiftTest.Shape.init(width: Swift.Int, side: Swift.Int) - >SwiftTest.Shape at main.swift:630
    0x100001316 <+38>:  leaq   0x6edb(%rip), %rcx        ; SwiftTest.s : SwiftTest.Shape
    0x10000131d <+45>:  xorl   %r8d, %r8d
    0x100001320 <+48>:  movl   %r8d, %esi
    0x100001323 <+51>:  movq   %rax, 0x6ece(%rip)        ; SwiftTest.s : SwiftTest.Shape
    0x10000132a <+58>:  movq   %rdx, 0x6ecf(%rip)        ; SwiftTest.s : SwiftTest.Shape + 8
->  0x100001331 <+65>:  movq   %rcx, %rdi
    0x100001334 <+68>:  leaq   -0x20(%rbp), %rax
    0x100001338 <+72>:  movq   %rsi, -0x40(%rbp)
    0x10000133c <+76>:  movq   %rax, %rsi
    0x10000133f <+79>:  movl   $0x21, %edx
    0x100001344 <+84>:  movq   -0x40(%rbp), %rcx
    0x100001348 <+88>:  callq  0x100006312               ; symbol stub for: swift_beginAccess
    0x10000134d <+93>:  movq   0x6ea4(%rip), %rdi        ; SwiftTest.s : SwiftTest.Shape
    0x100001354 <+100>: movq   0x6ea5(%rip), %rsi        ; SwiftTest.s : SwiftTest.Shape + 8
    0x10000135b <+107>: callq  0x1000016d0               ; SwiftTest.Shape.girth.getter : Swift.Int at main.swift:646
    0x100001360 <+112>: movq   %rax, -0x28(%rbp)
    0x100001364 <+116>: leaq   -0x28(%rbp), %rdi
    0x100001368 <+120>: callq  0x100001d70               ; SwiftTest.test(inout Swift.Int) -> () at main.swift:658
    0x10000136d <+125>: movq   -0x28(%rbp), %rdi
    0x100001371 <+129>: leaq   0x6e80(%rip), %r13        ; SwiftTest.s : SwiftTest.Shape
    0x100001378 <+136>: callq  0x100001820               ; SwiftTest.Shape.girth.setter : Swift.Int at main.swift:642
    0x10000137d <+141>: leaq   -0x20(%rbp), %rdi
    0x100001381 <+145>: callq  0x100006330               ; symbol stub for: swift_endAccess
    0x100001386 <+150>: xorl   %eax, %eax
    0x100001388 <+152>: addq   $0x38, %rsp
    0x10000138c <+156>: popq   %r13
    0x10000138e <+158>: popq   %rbp
    0x10000138f <+159>: retq 
Copy the code

This time from the amount of assembly code can be judged, for the calculation of attributes must be more complex than the storage of attributes, or through the legend to show the whole process

As you can see, because of the properties of calculation in the instance there is no corresponding internal memory space, the compiler through opening a local variable in the inside of the function of stack method, using it as a temporary host, calculate the value of the attribute and the address of the local variable as inout parameters passed in the test function, so in essence, is still a reference.

Before the test function is called, the calculated property value is copied to the local variable, and after the test function is called, the value of the local variable is passed to the setter function. These two processes are called Copy In Copy Out by Apple, and the running result of the above case code also verifies this conclusion

GetGirth starts the test function setGirth20
Program ended with exit code: 0
Copy the code





Finally, let’s look at some of the idiosyncratic processes for stored properties with attribute observers

struct Shape {
    var width: Int
    var side: Int {
        willSet {
            print("willSetSide", newValue)
        }
        didSet {
            print("didSetSide", oldValue, side)
        }
    }

    var girth: Int {
        set {
            width = newValue / side
            print("setGirth", newValue)
        }
        get {
            print("getGirth")
            return width * side
        }
    }

    func show(a) {
        print("width= \(width), side= \(side), girth= \(girth)")}}func test(_ num: inout Int) {
    num = 20
}

var s = Shape(width: 10, side: 4)
test(&s.side) // Side is the storage property with the attribute observation period, the breakpoint is here
Copy the code

The breakpoint assembles the following results

SwiftTest`main:
    0x100001230 <+0>:   pushq  %rbp
    0x100001231 <+1>:   movq   %rsp, %rbp
    0x100001234 <+4>:   pushq  %r13
    0x100001236 <+6>:   subq   $0x38, %rsp
    0x10000123a <+10>:  movl   $0xa, %eax
    0x10000123f <+15>:  movl   %edi, -0x2c(%rbp)
    0x100001242 <+18>:  movq   %rax, %rdi
    0x100001245 <+21>:  movl   $0x4, %eax
    0x10000124a <+26>:  movq   %rsi, -0x38(%rbp)
    0x10000124e <+30>:  movq   %rax, %rsi
    0x100001251 <+33>:  callq  0x100001ca0               ; SwiftTest.Shape.init(width: Swift.Int, side: Swift.Int) - >SwiftTest.Shape at main.swift:630
    0x100001256 <+38>:  leaq   0x6f9b(%rip), %rcx        ; SwiftTest.s : SwiftTest.Shape
    0x10000125d <+45>:  xorl   %r8d, %r8d
    0x100001260 <+48>:  movl   %r8d, %esi
    0x100001263 <+51>:  movq   %rax, 0x6f8e(%rip)        ; SwiftTest.s : SwiftTest.Shape
    0x10000126a <+58>:  movq   %rdx, 0x6f8f(%rip)        ; SwiftTest.s : SwiftTest.Shape + 8
->  0x100001271 <+65>:  movq   %rcx, %rdi
    0x100001274 <+68>:  leaq   -0x20(%rbp), %rax
    0x100001278 <+72>:  movq   %rsi, -0x40(%rbp)
    0x10000127c <+76>:  movq   %rax, %rsi
    0x10000127f <+79>:  movl   $0x21, %edx
    0x100001284 <+84>:  movq   -0x40(%rbp), %rcx
    0x100001288 <+88>:  callq  0x100006302               ; symbol stub for: swift_beginAccess
    0x10000128d <+93>:  movq   0x6f6c(%rip), %rax        ; SwiftTest.s : SwiftTest.Shape + 8
    0x100001294 <+100>: movq   %rax, -0x28(%rbp)
    0x100001298 <+104>: leaq   -0x28(%rbp), %rdi
    0x10000129c <+108>: callq  0x100001cb0               ; SwiftTest.test(inout Swift.Int) -> () at main.swift:658
    0x1000012a1 <+113>: movq   -0x28(%rbp), %rdi
    0x1000012a5 <+117>: leaq   0x6f4c(%rip), %r13        ; SwiftTest.s : SwiftTest.Shape
    0x1000012ac <+124>: callq  0x100001350               ; SwiftTest.Shape.side.setter : Swift.Int at main.swift:632
    0x1000012b1 <+129>: leaq   -0x20(%rbp), %rdi
    0x1000012b5 <+133>: callq  0x100006320               ; symbol stub for: swift_endAccess
    0x1000012ba <+138>: xorl   %eax, %eax
    0x1000012bc <+140>: addq   $0x38, %rsp
    0x1000012c0 <+144>: popq   %r13
    0x1000012c2 <+146>: popq   %rbp
    0x1000012c3 <+147>: retq   
Copy the code

This time, we find some similar to calculate attribute, here also stack using the function of the local variable, the function of it is used to load the value of the attribute, and then passed the test function of the same is the address of the local variable (reference), but I wonder why even bother, calculate attribute because itself has no fixed memory, So it makes sense that we have to use local face changes as temporary hosts, but we have fixed memory for evaluating properties, and as you can guess, the reason for this has to do with the property viewer, but the current code is not enough to explain the purpose of this design, but we see here in the last step, we call the side.setter function, 🤔️side is a store property, how can there be a setter function? Let’s take a look inside it. Its compilation is as follows

SwiftTest`Shape.side.setter:
->  0x100001350 <+0>:  pushq  %rbp
    0x100001351 <+1>:  movq   %rsp, %rbp
    0x100001354 <+4>:  pushq  %r13
    0x100001356 <+6>:  subq   $0x28, %rsp
    0x10000135a <+10>: movq   $0x0, -0x10(%rbp)
    0x100001362 <+18>: movq   $0x0, -0x18(%rbp)
    0x10000136a <+26>: movq   %rdi, -0x10(%rbp)
    0x10000136e <+30>: movq   %r13, -0x18(%rbp)
    0x100001372 <+34>: movq   0x8(%r13), %rax
    0x100001376 <+38>: movq   %rax, %rcx
    0x100001379 <+41>: movq   %rdi, -0x20(%rbp)
    0x10000137d <+45>: movq   %r13, -0x28(%rbp)
    0x100001381 <+49>: movq   %rax, -0x30(%rbp)
    0x100001385 <+53>: callq  0x1000013b0               ; SwiftTest.Shape.side.willset : Swift.Int at main.swift:633
    0x10000138a <+58>: movq   -0x28(%rbp), %rax
    0x10000138e <+62>: movq   -0x20(%rbp), %rcx
    0x100001392 <+66>: movq   %rcx, 0x8(%rax)
    0x100001396 <+70>: movq   -0x30(%rbp), %rdi
    0x10000139a <+74>: movq   %rax, %r13
    0x10000139d <+77>: callq  0x1000014d0               ; SwiftTest.Shape.side.didset : Swift.Int at main.swift:636
    0x1000013a2 <+82>: movq   -0x30(%rbp), %rax
    0x1000013a6 <+86>: addq   $0x28, %rsp
    0x1000013aa <+90>: popq   %r13
    0x1000013ac <+92>: popq   %rbp
    0x1000013ad <+93>: retq 
Copy the code

Turns out, thissideTwo property observers ofwillSetanddidSetIt was wrapped in thissetterFunction, and, for propertiessideThat’s where the assignment really happenssetterInside the function.

So we see one detail, the point at which the value of the property side is changed in memory, is after the test function, which is in this setter function, which is the test function is not actually changing the value of side.

Because the test function takes a chunk of memory and changes its values, if we present the side address to Test, it will not trigger the side property viewer, except to change the values in the side memory. So you can see that the local variables and the setter functions are there in order to be able to fire the property observer on the property side. Because we are using local variables, inout can also Copy In Copy Out for stored properties with property observers.

Through the output results after the program runs, we can also verify the conclusion we have

Start the test function willSetSide20
didSetSide 4 20
Program ended with exit code: 0
Copy the code


inoutEssential Summary

  • If the argument has a physical memory address and no property viewer is set

Pass the memory address of the argument directly to the function (the argument is passed by reference)

  • If the argument is a evaluated property or a property observer is set

Copy In Copy Out – when the function is called, the value of the argument is copied to produce a Copy – the memory address of the Copy is passed to the function (the Copy is passed by reference), and the value of the Copy can be changed inside the function – after the function returns, Overwrites the value of the copy over the value of the argument.

Conclusion:inoutThe essence ofreference(Address transfer)


- Type Property (**Type Property**) : can only be accessed by Type + Stored Type Property (**Stored Type Property**) : For the entire program, there is only one piece of memory, and its essence is global variables + Computed Type properties (**Computed Type Property**)Copy the code
  • Can be achieved bystaticDefine type attributes and, in the case of classes, keywordsclass

Type attribute details

  • Unlike store instance properties, you must set initial values for store type properties

Because there are no types like instancesinitInitializer to initialize storage properties

  • The storage type attribute is lazy by default and is initialized the first time it is used

    • Even if it is accessed by multiple threads at the same time, it will only be initialized once to ensure thread safety (the system will lock at the bottom).
    • Storage Type PropertiesWhen you canletBecause there is no instance initialization at all
  • Enumerated types can also define type properties (storage type properties, computed type properties)

The singleton pattern

public class FileManager {
    
    public static let shared = FileManager(a)private init(a){}}Copy the code
  • public static let shared = FileManager()
    • throughstaticDefines aType storage properties.
    • publicMake sure that in any scenario, the outside world can access it,
    • letTo ensure theFileManager()It just gets assigned tosharedOnce, and it’s thread safe, that isinit()The method will only be called once to ensure thatFileManagerThere would only be one instance, and that would beSwiftIn theThe singleton.
  • private init():privateEnsures that the outside world cannot be called manuallyFileManager()To create an instance, so passsharedAttribute derivedFileManagerInstances are always the same, which also meets our requirement for singletons.

Type (static) Stores the nature of the property

Static is a global variable. Static is a global variable. Static is a global variable

var num1 = 10 // Add a breakpoint here
var num2 = 11
var num3 = 12
Copy the code

Run to the breakpoint as follows

SwiftTest`main:
    0x100001120 <+0>:  pushq  %rbp
    0x100001121 <+1>:  movq   %rsp, %rbp
    0x100001124 <+4>:  xorl   %eax, %eax
->  0x100001126 <+6>:  movq   $0xa, 0x60af(%rip)        ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
    0x100001131 <+17>: movq   $0xb, 0x60ac(%rip)        ; SwiftTest.num1 : Swift.Int + 4
    0x10000113c <+28>: movq   $0xc, 0x60a9(%rip)        ; SwiftTest.num2 : Swift.Int + 4
    0x100001147 <+39>: popq   %rbp
    0x100001148 <+40>: retq
Copy the code

Num1, num2, num3

So let’s figure out their actual memory address

  • &num1 = 0x60af + 0x100001131 = 0x1000071E0
  • &num2 = 0x60ac + 0x10000113c = 0x1000071E8
  • &num3 = 0x60a9 + 0x100001147 = 0x1000071F0

They are three contiguous memory segments on the global data segment. Next we add the static storage property as follows

var num1 = 10 / / break point

class Car {
    static var num2 = 1
}

Car.num2 = 11

var num3 = 12
Copy the code

Open assembly at breakpoint

SwiftTest`main:
    0x100000d80 <+0>:  pushq  %rbp
    0x100000d81 <+1>:  movq   %rsp, %rbp
    0x100000d84 <+4>:  subq   $0x30, %rsp
->  0x100000d88 <+8>:  movq   $0xa, 0x6595(%rip)        ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
    0x100000d93 <+19>: movl   %edi, -0x1c(%rbp)
    0x100000d96 <+22>: movq   %rsi, -0x28(%rbp)
    0x100000d9a <+26>: callq  0x100000e40               ; SwiftTest.Car.num2.unsafeMutableAddressor : Swift.Int at main.swift
    0x100000d9f <+31>: xorl   %ecx, %ecx
    0x100000da1 <+33>: movq   %rax, %rdx
    0x100000da4 <+36>: movq   %rdx, %rdi
    0x100000da7 <+39>: leaq   -0x18(%rbp), %rsi
    0x100000dab <+43>: movl   $0x21, %edx
    0x100000db0 <+48>: movq   %rax, -0x30(%rbp)
    0x100000db4 <+52>: callq  0x1000053a2               ; symbol stub for: swift_beginAccess
    0x100000db9 <+57>: movq   -0x30(%rbp), %rax
    0x100000dbd <+61>: movq   $0xb, (%rax)
    0x100000dc4 <+68>: leaq   -0x18(%rbp), %rdi
    0x100000dc8 <+72>: callq  0x1000053c6               ; symbol stub for: swift_endAccess
    0x100000dcd <+77>: xorl   %eax, %eax
    0x100000dcf <+79>: movq   $0xc, 0x655e(%rip)        ; static SwiftTest.Car.num2 : Swift.Int + 4
    0x100000dda <+90>: addq   $0x30, %rsp
    0x100000dde <+94>: popq   %rbp
    0x100000ddf <+95>: retq 
Copy the code

As shown in the picture above, first we can quickly locatenum1andnum3We can record their memory address first

  • &num1 = 0x6595 + 0x100000d93 = 0x100007328
  • &num3 = 0x655e + 0x100000dda = 0x100007338

Among num1 and num2, we found a call Car. Num2. UnsafeMutableAddressor function is invoked, and through its return values as address to access a section of memory space, and assign a value to its 11, From the Car. Num2. UnsafeMutableAddressor this name, we can see that this function returns the address, is the Car. The num2 address, first we run x100000dbd 0 < 61 > + : Movq $0xb, (%rax) movq $0xb, (%rax

(lldb) register read rax
     rax = 0x0000000100007330  SwiftTest`static SwiftTest.Car.num2 : Swift.Int
Copy the code

As you can see, this is exactly the addressnum1andnum3Between that space, so althoughnum2As aCarthestaticStatic stores properties, but its location in memory is no different from that of ordinary global variables, so we can say that static stores properties are essentially global variables.

The code has been tweaked slightly

var num1 = 10

class Car {
    static var num2 = 1
}
//Car. Num2 = 11
var num3 = 12


* * * * * * * * * * * * * * * * * * * * * *Corresponding to the assembly* * * * * * * * * * * * * * * * * * * * * * *
SwiftTest`main:
    0x100000dc0 <+0>:  pushq  %rbp
    0x100000dc1 <+1>:  movq   %rsp, %rbp
    0x100000dc4 <+4>:  xorl   %eax, %eax
->  0x100000dc6 <+6>:  movq   $0xa, 0x6557(%rip)        ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
    0x100000dd1 <+17>: movq   $0xc, 0x655c(%rip)        ; static SwiftTest.Car.num2 : Swift.Int + 4
    0x100000ddc <+28>: popq   %rbp
    0x100000ddd <+29>: retq
Copy the code

Num2 is not initialized if it is not used, so we say static stores are lazy by default.

Let’s restore the code and follow the assembly process further again

var num1 = 10 / / break point
class Car {
    static var num2 = 1
}
Car.num2 = 11
var num3 = 12


* * * * * * * * * * * * * * * * * * * * * *Corresponding to the assembly* * * * * * * * * * * * * * * * * * * * * * *
SwiftTest`main:
    0x100000d80 <+0>:  pushq  %rbp
    0x100000d81 <+1>:  movq   %rsp, %rbp
    0x100000d84 <+4>:  subq   $0x30, %rsp
->  0x100000d88 <+8>:  movq   $0xa, 0x6595(%rip)        ; demangling cache variable for type metadata for Swift.Array<Swift.UInt8> + 4
    0x100000d93 <+19>: movl   %edi, -0x1c(%rbp)
    0x100000d96 <+22>: movq   %rsi, -0x28(%rbp)
    0x100000d9a <+26>: callq  0x100000e40               ; SwiftTest.Car.num2.unsafeMutableAddressor : Swift.Int at main.swift
    0x100000d9f <+31>: xorl   %ecx, %ecx
    0x100000da1 <+33>: movq   %rax, %rdx
    0x100000da4 <+36>: movq   %rdx, %rdi
    0x100000da7 <+39>: leaq   -0x18(%rbp), %rsi
    0x100000dab <+43>: movl   $0x21, %edx
    0x100000db0 <+48>: movq   %rax, -0x30(%rbp)
    0x100000db4 <+52>: callq  0x1000053a2               ; symbol stub for: swift_beginAccess
    0x100000db9 <+57>: movq   -0x30(%rbp), %rax
    0x100000dbd <+61>: movq   $0xb, (%rax)
    0x100000dc4 <+68>: leaq   -0x18(%rbp), %rdi
    0x100000dc8 <+72>: callq  0x1000053c6               ; symbol stub for: swift_endAccess
    0x100000dcd <+77>: xorl   %eax, %eax
    0x100000dcf <+79>: movq   $0xc, 0x655e(%rip)        ; static SwiftTest.Car.num2 : Swift.Int + 4
    0x100000dda <+90>: addq   $0x30, %rsp
    0x100000dde <+94>: popq   %rbp
    0x100000ddf <+95>: retq
Copy the code

This time we started fromunsafeMutableAddressorSo let’s see what this function is

SwiftTest`Car.num2.unsafeMutableAddressor:
->  0x100000e40 <+0>:  pushq  %rbp
    0x100000e41 <+1>:  movq   %rsp, %rbp
    0x100000e44 <+4>:  cmpq   $-0x1.0x64f4(%rip)       ; SwiftTest.num3 : Swift.Int + 7
    0x100000e4c <+12>: sete   %al
    0x100000e4f <+15>: testb  $0x1, %al
    0x100000e51 <+17>: jne    0x100000e55               ; <+21> at main.swift:719:16
    0x100000e53 <+19>: jmp    0x100000e5e               ; <+30> at main.swift
    0x100000e55 <+21>: leaq   0x64d4(%rip), %rax        ; static SwiftTest.Car.num2 : Swift.Int
    0x100000e5c <+28>: popq   %rbp
    0x100000e5d <+29>: retq   
    0x100000e5e <+30>: leaq   -0x45(%rip), %rax         ; globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0 at main.swift
    0x100000e65 <+37>: leaq   0x64d4(%rip), %rdi        ; globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_token0
    0x100000e6c <+44>: movq   %rax, %rsi
    0x100000e6f <+47>: callq  0x1000053fc               ; symbol stub for: swift_once
    0x100000e74 <+52>: jmp    0x100000e55               ; <+21> at main.swift:719:16
Copy the code

See, in the end, calls the swift_once function, we know a dispatch_once inside the GCD, whether there is a correlation, we enter the function

libswiftCore.dylib`swift_once:
->  0x7fff73447820 <+0>:  pushq  %rbp
    0x7fff73447821 <+1>:  movq   %rsp, %rbp
    0x7fff73447824 <+4>:  cmpq   $-0x1, (%rdi)
    0x7fff73447828 <+8>:  jne    0x7fff7344782c            ; <+12>
    0x7fff7344782a <+10>: popq   %rbp
    0x7fff7344782b <+11>: retq   
    0x7fff7344782c <+12>: movq   %rsi, %rax
    0x7fff7344782f <+15>: movq   %rdx, %rsi
    0x7fff73447832 <+18>: movq   %rax, %rdx
    0x7fff73447835 <+21>: callq  0x7fff7349c19c            ; symbol stub for: dispatch_once_f
    0x7fff7344783a <+26>: popq   %rbp
    0x7fff7344783b <+27>: retq   
    0x7fff7344783c <+28>: nop    
    0x7fff7344783d <+29>: nop    
    0x7fff7344783e <+30>: nop    
    0x7fff7344783f <+31>: nop
Copy the code

“Swift_once” (dispatch_once_f, dispatch_once) “swift_once” (dispatch_once_f, dispatch_once) “swift_once” (dispatch_once_f, dispatch_once) “swift_once” (dispatch_once_f, dispatch_once) “swift_once” (dispatch_once_f, dispatch_once) Static var num2 = 1 static var num2 = 1

How do you prove that? I’m going to run assembly tocallq 0x7fff7349c19c ; symbol stub for: dispatch_once_fBecause at this point,dispatch_once_fThe parameters required for the function are already in place, as assembly convention dictatesrsi,rdxOnce stored inside, we can look at the contents of these two registers at this time

(lldb) register read rsi
     rsi = 0x00007ffeefbff598
(lldb) register read rdx
     rdx = 0x0000000100000e20  SwiftTest`globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0 at main.swift
(lldb) 
Copy the code

You can seerdxAt this time store is a heelglobalinit(global initialization) related functionsfunc0, the address is0x0000000100000e20The function is zerodispatch_once_facceptableblock. So let’s go back toSwiftSource code, add a breakpoint below

So we continue to run the program, the breakpoint will stop in the code above, if we’re guessing correctly, so at this time the assembly should be within globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0 this function, we run the program after the assembly is as follows

SwiftTest`globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0:
    0x100000e20 <+0>:  pushq  %rbp
    0x100000e21 <+1>:  movq   %rsp, %rbp
->  0x100000e24 <+4>:  movq   $0x1, 0x6501(%rip)        ; SwiftTest.num1 : Swift.Int + 4
    0x100000e2f <+15>: popq   %rbp
    0x100000e30 <+16>: retq   
Copy the code

Is really within globalinit_33_B9B0E304FD1668A20F6C95C54E9E2F7A_func0 function, and here is initialized memory address 0 x100000e2f + 0 x6501 = 0 x100007330, It is clear from the initial value that this memory segment is NUM2, and it is the same value as we recorded in the return of the unsafeMutableAddressor function.

At the bottom of Swift, unsafeMutableAddressor -> libswiftCore.dylib-swift_once -> libswiftcore. dylib-dispatch_once_f: ———-> static var num2 = 1; static storage properties are thread-safe and can only be initialized once.

methods

methods

class Car {
    static var count = 0  
    init(a) {
        Car.count + = 1
    }
    // Type Method
    static func getCount(a) -> Int {
    	The following methods of accessing count are equivalent
    	count + = 1
    	self.count + = 1
    	Car.self.count + = 1
    	Car.count + = 1
     	return count 
     }
}

let c0 = Car(a)let c1 = Car(a)let c2 = Car(a)print(Car.getCount()) // call by class name
Copy the code

Enumerations, structs, and classes can all define instance methods and type methods

  • Instance methods(Instance Method) : calls from instance objects
  • Type method(Type Method) : called by type, withstaticorclassKeyword to define

self

  • Represents the instance object in the instance method
  • Represents a type in a type method

In the static func getCount method, the following are equivalent

  • count
  • self.count
  • Car.count
  • Car.self.count

mutating

The Swift syntax states that structs and enumerations are value types whose properties cannot be modified by their own instance methods by default (there is no such rule for classes).

  • infuncBefore the keywordmutatingThis modification behavior can be allowed as follows
struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(deltaX: Double.deltaY: Double) {
        x + = deltaX
        y + = deltaY
    }
}

enum StateSwitch {
    case low, middle, high
    mutating func next(a) {
        switch self {
        case .low:
            self = .middle
        case .middle:
            self = .high
        case .high:
            self = .low
        }
    }
}
Copy the code

@discardableResult

Use @discardableresult in front of func to eliminate the warning message ️ that the return value after the function call is not used

struct Point {
    var x = 0.0, y = 0.0
    @discardableResult mutating
    func moveX(deltaX: Double) -> Double {
        x + = deltaX
        return x
    }
}
var p = Point()
p.moveX(deltaX: 10)
Copy the code

The subscript

Subscript allows you to add the following table functionality to any type (enumeration, class, structure). Subscript syntax is similar to instance methods, computed properties, and is essentially a method (function).

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        set {
            if index = = 0 {
                x = newValue
            } else if index = = 1 {
                y = newValue
            }
        }

        get {
            if index = = 0 {
                return x
            } else if index = = 1 {
                return y
            }
            return 0}}}var p = Point()
p[0] = 11.1
p[1] = 22.2
print(p.x)  / / 11.1
print(p.y)  / / 22.2
print(p[0]) / / 11.1
print(p[1]) / / 22.2

Copy the code

From the above example, subscript provides access to member variables via [I], like arrays/dictionaries. Subscripts differ superficially from functions except that func funcName is replaced by subscript when defined and funcName(arg) is replaced by [arg] when called. Subscript contains get and set inside, much like computed properties.

Let’s simplify our code

class Point {
    var x = 0, y = 0
    subscript(index: Int) -> Int {
        set {
            if index = = 0 {
                x = newValue
            } else if index = = 1 {
                y = newValue
            }
        }

        get {
            if index = = 0 {
                return x
            } else if index = = 1 {
                return y
            }
            return 0}}}var p = Point()
p[0] = 10 // 0xa puts a breakpoint here ️
p[1] = 11 // 0xb
Copy the code

Run the program to the breakpoint as follows

We find the code in the green box based on the immediate numbers 10 and 11. The function marked in red is obviously not a subscript call. We follow the indirect function call in the two green boxes

0x1000016b1 <+145>: callq  *0x98(%rcx) ---Go into that function-->

SwiftTest`Point.subscript.setter:
->  0x100001c10 <+0>:   pushq  %rbp
    0x100001c11 <+1>:   movq   %rsp, %rbp
    0x100001c14 <+4>:   pushq  %r13
    0x100001c16 <+6>:   subq   $0x48, %rsp
    0x100001c1a <+10>:  xorl   %eax, %eax
    0x100001c1c <+12>:  leaq   -0x10(%rbp), %rcx
    0x100001c20 <+16>:  movq   %rdi, -0x28(%rbp)
    .
    .
    .
Copy the code
0x100001715 <+245>: callq  *0x98(%rcx) ---Go into that function-->

SwiftTest`Point.subscript.setter:
->  0x100001c10 <+0>:   pushq  %rbp
    0x100001c11 <+1>:   movq   %rsp, %rbp
    0x100001c14 <+4>:   pushq  %r13
    0x100001c16 <+6>:   subq   $0x48, %rsp
    0x100001c1a <+10>:  xorl   %eax, %eax
    0x100001c1c <+12>:  leaq   -0x10(%rbp), %rcx
    0x100001c20 <+16>:  movq   %rdi, -0x28(%rbp)
     .
    .
    .
Copy the code

Setset.setpoint (% RCX) = setpoint (% RCX) = setpoint (% RCX) = setpoint (% RCX) = setpoint (% RCX) = setpoint (% RCX) = setpoint (% RCX) = setpoint (% RCX)

P is not a function name, p is a variable, so you want to call the subscript function, so you must be calling it indirectly. Direct call: callq function address Indirect call: callq * memory address

Note that some ️

  • subscriptThe return value type defined in:
    • getMethod return value type
    • setMethod ChinanewValueThe type of
  • subscriptMultiple arguments can be accepted and of any type

Subscript details

Subscripts may not have set methods, but they must have get methods. If they only have get methods, they can be read only

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        get {
            if index = = 0 {
                return x
            } else if index = = 1 {
                return y
            }
            return 0}}}Copy the code

If you only have the get method, you can also omit get

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        if index = = 0 {
            return x
        } else if index = = 1 {
            return y
        }
        return 0}}Copy the code

You can also set parameter labels

class Point {
    var x = 0.0, y = 0.0
    subscript(index i: Int) -> Double {
        if i = = 0 {
            return x
        } else if i = = 1 {
            return y
        }
        return 0}}var p = Point()
p.y = 22.2
print(p[index: 1]) // If there is a label, always wear it when using it
Copy the code

The subscripts we saw above are equivalent to instance methods (the default), and can also be subscripts of type methods

class Sum {
    static subscript(v1: Int.v2: Int) -> Int {
        return v1 + v2
    }
}

print(Sum[10.20])

Copy the code

Struct and class as return values

struct Point {
    var x = 0
    var y = 0
}

class PointManager {
    var point = Point(a)subscript(index: Int) -> Point {
        set { point = newValue }  // If a heap point is assigned, the set method must be added.
        get { point }
    }
}

var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
print(pm[0])
print(pm.point)
Copy the code

In the example above, the PointManager class has a subscript that returns a struct Point. Note that the subscript returns a struct Point variable, regardless of what the subscript value is

set { point = newValue }
Copy the code

If PM [0].x = 11 or PM [0].y = 22, how do we know if the newValue in the set method is for.x or.y? In fact, you should notice that newValue should be of type struct Point, and if so, PM [0]. X = 11 –> newValue = (11, PM [0]. Y) –> set {point = newValue = (11, pm[0].y) } pm[0].y = 22 —> newValue = (pm[0].x, 22) —> set { point = newValue = (pm[0].x, 22) }

If you replace STRTCT Point with class Point, the set method can be omitted

class Point {
    var x = 0
    var y = 0
}

class PointManager {
    var point = Point(a)subscript(index: Int) -> Point {
        get { point }
    }
}

var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
print(pm[0])
print(pm.point)
Copy the code

PM [0].x is equivalent to point. X, so point. X = 11 is in compliance with the specification.

Subscripts accept multiple arguments

class Grid {
    var data =[[0.1.2],
        [3.4.5],
        [6.7.8]]subscript( row: Int.column: Int) -> Int {
        set {
            guard row > = 0 && row < 3 && column > = 0 && column < 3 else {
                return
            }
            data[row][column] = newValue
        }
        
        get  {
            guard row > = 0 && row < 3 && column > = 0 && column < 3 else {
                return 0
            }
            return data[row][column]
        }
    }
    
    
    
}

var grid = Grid()
grid[0.1] = 77
grid[1.2] = 88
grid[2.0] = 99
print(grid.data)

* * * * * * * * * * * * * * * * * * * * *Running results [[0.77.2], [3.4.88], [99.7.8]]
Program ended with exit code: 0

Copy the code

Okay, properties and methods, I’m going to leave you there for a moment, period!