Swift基础7(汇编分析属性)

2019-11-18  本文已影响0人  SunshineBrother

Swift中跟实例相关的属性可以分为2大类

struct Circle {
    var radius:Double
    var diameter:Double{
        set{
            radius = newValue / 2
        }
        get{
            radius * 2
        }
    }
}
 

print(MemoryLayout<Double>.size) //8
print(MemoryLayout<Circle>.size) //8

radius是存储属性,diameter是计算属性,我们打印Circle的内存大小发现和Double一样

存储属性

关于存储属性,Swift有一个明确的规定

计算属性

除了存储属性,类、结构体和枚举也能够定义计算属性,而它实际并不存储值。相反,他们提供一个读取器和一个可选的设置器来间接得到和设置其他的属性和值

struct Circle {
    var radius:Double
    var diameter:Double{
        set{
            radius = newValue / 2
        }
        get{
            radius * 2
        }
    }
}
 

diameter就是一个简单的计算属性,如果一个计算属性的设置器没有为将要被设置的值定义一个名字,那么他将被默认命名为 newValue

只读计算属性:只有get方法,没有set方法

struct Circle {
    var radius:Double
    var diameter:Double{
        get{
            radius * 2
        }
    }
}

var c = Circle(radius: 10)  
print(c.diameter)   //20
c.radius = 5
print(c.diameter) //10

枚举rawValue的原理

我们研究发现,其实枚举原始值中rawValue的本质就是:只读计算属性

enum TestEnum:Int {
    case test1 = 1, test2 = 2, test3 = 3
}

print(TestEnum.test1.rawValue) //1

对于这样的我们打印就是1

enum TestEnum:Int {
    case test1 = 1, test2 = 2, test3 = 3
    var rawValue:Int{
        switch self {
        case .test1:
            return 10
        case .test2:
            return 20
        case .test3:
            return 30
        }
    }
}
print(TestEnum.test1.rawValue)  //10

对于这个打印就是10

延迟存储属性

使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化

你必须把延迟存储属性声明为变量(使用 var 关键字),因为它的初始值可能在实例初始化完成之前无法取得。常量属性则必须在初始化完成之前有值,因此不能声明为延迟

如果多条线程同时第一次访问lazy属性,无法保证属性只能被初始化一次

class PhotoView {
    lazy var image:UIImage = {
        let url = "xxx.png"
        let data = Data(url)
        return UIImage(data:data)
    }
}

当结构体包含一个延迟存储属性时,只有var才能访问演出属性

延迟属性.png

属性观察器(Property Observer)

属性观察者会观察并对属性值的变化做出回应。每当一个属性的值被设置时,属性观察者都会被调用,即使这个值与该属性当前的值相同

你可以为你定义的任意存储属性添加属性观察者,除了延迟存储属性。你也可以通过在子类里重写属性来为任何继承属性(无论是存储属性还是计算属性)添加属性观察者

你不需要为非重写的计算属性定义属性观察者,因为你可以在计算属性的设置器里直接观察和相应它们值的改变。

如果你实现了一个 willSet 观察者,新的属性值会以常量形式参数传递。你可以在你的 willSet 实现中为这个参数定义名字。如果你没有为它命名,那么它会使用默认的名字newValue

同样,如果你实现了一个 didSet观察者,一个包含旧属性值的常量形式参数将会被传递。你可以为它命名,也可以使用默认的形式参数名 oldValue 。如果你在属性自己的 didSet观察者里给自己赋值,你赋值的新值就会取代刚刚设置的值。

struct Circle {
    var radius:Double{
        willSet{
            print("willSet",newValue)
        }
        didSet{
            print("didSet",oldValue)
        }
    }
    
}

inout再次研究

struct Shape {
    var width:Int
    var side:Int {
        willSet{
            print("willSet",newValue)
        }
        didSet{
            print("didSet",oldValue)
        }
    }
    
    var grith:Int{
        set{
            width = newValue / side
            print("setGrith",newValue)
        }
        get{
            print("getGrith")
            return width * side
        }
    }
    
    func show() {
        print("width:\(width)--side:\(side)--grith:\(grith)")
    }
    
}



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

var s = Shape(width: 10, side: 4)
s.show()
print("-------")

test(&s.width)
s.show()
print("-------")


test(&s.grith)
s.show()
print("-------")

test(&s.side)
s.show()
print("-------")

我们对这个进行分析

1、在test(&s.width)处打断点

struct Shape {
    var width:Int
    var side:Int {
        willSet{
            print("willSet",newValue)
        }
        didSet{
            print("didSet",oldValue)
        }
    }
    
    var grith:Int{
        set{
            width = newValue / side
            print("setGrith",newValue)
        }
        get{
            print("getGrith")
            return width * side
        }
    }
    
    func show() {
        print("width:\(width)--side:\(side)--grith:\(grith)")
    }
    
}

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

var s = Shape(width: 10, side: 4)
test(&s.width)

想要具体的分析,我们可以查看汇编实现。提前需要了解的知识点

2、在test(&s.grith)处打断点

struct Shape {
    var width:Int
    var side:Int {
        willSet{
            print("willSet",newValue)
        }
        didSet{
            print("didSet",oldValue)
        }
    }
    
    var grith:Int{
        set{
            width = newValue / side
            print("setGrith",newValue)
        }
        get{
            print("getGrith")
            return width * side
        }
    }
    
    func show() {
        print("width:\(width)--side:\(side)--grith:\(grith)")
    }
    
}

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

var s = Shape(width: 10, side: 4)
test(&s.grith)

我们知道inout本质就是引用传递,而在上面我们知道计算属性本质就是一个函数s.grith没有内存地址,这里为什么不报错呢?我们先来看一下打印结果

getGrith
test
setGrith 20

我们先大概猜测一下实现原理

我们来看一下汇编实现

【需要了解的小知识】

3、在test(&s.side)处打断点

struct Shape {
    var width:Int
    var side:Int {
        willSet{
            print("willSet",newValue)
        }
        didSet{
            print("didSet",oldValue)
        }
    }
    
    var grith:Int{
        set{
            width = newValue / side
            print("setGrith",newValue)
        }
        get{
            print("getGrith")
            return width * side
        }
    }
    
    func show() {
        print("width:\(width)--side:\(side)--grith:\(grith)")
    }
    
}

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

var s = Shape(width: 10, side: 4)
test(&s.side)
inout3.png inout4.png

inout本质总结

**inout的本质就是引用传递(地址传递)**

类型属性

例属性是属于特定类型实例的属性。每次你创建这个类型的新实例,它就拥有一堆属性值,与其他实例不同。

你同样可以定义属于类型本身的属性,不是这个类型的某一个实例的属性。这个属性只有一个拷贝,无论你创建了多少个类对应的实例。这样的属性叫做类型属性。

类型属性在定义那些对特定类型的所有实例都通用的值的时候很有用,比如实例要使用的常量属性(类似 C 里的静态常量),或者储存对这个类型的所有实例全局可见的值的存储属性(类似 C 里的静态变量)。

存储类型属性可以是变量或者常量。计算类型属性总要被声明为变量属性,与计算实例属性一致。

注意
不同于存储实例属性,你必须总是给存储类型属性一个默认值。这是因为类型本身不能拥有能够在初始化时给存储类型属性赋值的初始化器。
存储类型属性是在它们第一次访问时延迟初始化的。它们保证只会初始化一次,就算被多个线程同时访问,他们也不需要使用 lazy 修饰符标记。

严格来说,属性可以分为

类属性可以通过static定义属性,

类型属性细节

下面我们证明两个问题

类属性是全局变量

测试1

我们先来研究直接赋值的 num1 num2 num3

var num1 = 10
var num2 = 11
var num3 = 12
print("-------")

我们在print出打断点,然后进入汇编

类属性是全局变量1.png

我们计算出地址值:

num1  0x100001188
num2  0x100001190
num3  0x100001198

是一段连续的栈空间地址。

测试2

接下来我们把num2设置为类属性

struct Test {
    static var num = 11
}
var num1 = 10
var num2 = Test.num
var num3 = 12
print("----")

我们在print出打断点,然后进入汇编

类属性是全局变量.png

我们计算出地址值:

num1 0x1000021D0
num2 0x1000021D8
num3 0x1000021E0

也是一段连续的栈空间地址。

struct Test {
     var num = 11
}
var num1 = 10
var num2 = Test().num
var num3 = 12
print("----")

测试3

我们把num2设置为实例属性

struct Test {
     var num = 11
}
var num1 = 10
var num2 = Test().num
var num3 = 12
print("----")
类属性是全局变量3.png

我们来打印一下num2 num3的地址值

num2 0x000000000000000b
num3 0x1000021A8 

我们可以看出实例属性是在堆空间

类属性实例化的时候调用了dispath_once

struct Test {
     static var num = 11
}
var num1 = Test.num

我们在static var num = 11处打断点

dispath_once1.png

我们在swift_once处打断点,然后si进入,一直si,知道进入到

dispath_once2.png

在上面汇编中的最后一句话打一个断点,继续si

dispath_once3.png

最终找到了dispath_once,类属性保持只创建了一次就是调用了dispath_once方法

上一篇 下一篇

猜你喜欢

热点阅读