Swift底层原理探索----属性 & 方法

目录

【返回目录】

属性

struct Circle {
   
    //存储属性
    var radius: Double
    //计算属性
    var diamiter: Double {
   
        set {
    
            radius = newValue / 2
        }
        get {
   
            radius * 2
        }
    }
}
  • Swift中跟实例相关的属性可以分为2大类
    • 存储属性(Stored Property
      • 类似于成员变量这个概念
      • 存储在实例的内存中
      • 结构体、类可以定义存储属性
      • 枚举不可以定义存储属性
        我们知道枚举的内存里面可以存放的是所有的case以及关联值,并没有所谓的成员变量概念,可因此也不存在所谓的存储属性
    • 计算属性(Computed Property
      • 本质就是方法(函数)这个也可以通过汇编来证明一下
      • 不占用实例的内存
      • 枚举、结构体、类都可以定义计算属性

存储属性

  • 关于存储属性, Swift有个明确的规定
    • 在创建结构体的时候,必须为所有的存储属性设置一个合适的初始值,也就是要求类/结构体创建实例后,它的全部内存要得到初始化,而存储属性正好就是放在实例的内存里面的,所以需要将所有的存储属性设置初始值。
      1. 可以在初始化器里为存储属性设置一个初始值
      2. 可以分配一个默认的属性值作为属性定义的一部分

计算属性

  • set传入的新值默认叫做newValue,也可以自定义
  • 定义计算属性只能用var, 不能用let
    • let代表常量,也就是值是一成不变的
    • 计算属性的值是可能发生变化的(即使是只读计算属性)
  • 只读计算属性:只有get, 没有set

枚举rawValue原理

  • 枚举原始值rawValue的本质是:只读计算属性,直接看汇编就可以证明

延迟存储属性(Lazy Stored Property)

看现这段代码

class Car {
   
    init() {
   
        print("Car init")
    }
    func run() {
   
        print("Car is running!")
    }
}

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

let p = Person()
print("-----------")
p.goOut()

运行结果如下

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

我们给上面代码的car属性增加一个关键字lazy修饰

class Car {
   
    init() {
   
        print("Car init")
    }
    func run() {
   
        print("Car is running!")
    }
}

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

let p = Person()
print("-----------")
p.goOut()

再看下现在的运行结果

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

可以看出,lazy的作用,是将属性var car的初始化延迟到了它首次使用的时候进行,例子中也就是p.goOut()这句代码执行的时候,才回去初始化属性car

通过lazy 关键字修饰的存储属性就要做延迟存储属性,这个功能的好处是显而易见的,因为有些属性可能需要花费很多资源进行初始化,而很可能在某些极少情况下才会被触发使用,所以lazy关键字就可以用在这种情况下,让核心对象的初始化变得快速而轻量。比如下面这个例子

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

网络图片的加载往往是需要一些时间的,上面例子里面图片的加载过程封装在闭包表达式里面,并且将其返回值作为了image属性的初始化赋值,通过lazy,就讲这个加载的过程推迟到了image在实际被用到的时候去执行,这样就可以提升app顺滑度,改善卡顿情况。

  • 使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化
  • lazy属性必须是var, 不能是let
    • 这个要求很容易理解,let必须在实例的初始化方法完成之前就拥有值,而lazy恰好是为了在实例创建并初始化之后的某个时刻对其某个属性进行初始化赋值,所以lazy只能作用域var属性
  • 如果多线程同时第一次访问lazy属性,无法保证属性只被初始化1

延迟存储属性注意点

  • 当结构体包含一个延迟存储属性时,只有var才能访问延迟存储属性
    因为延迟属性初始化时需要改变结构体的内存

    案例中,因为p是常量,所以内存的内容初始化之后不可以变化,但是p.z会使得结构体Pointlazy var z属性进行初始化,因为结构体的成员是在结构体的内存里面的,因此就需要改变结构体的内存,因此便产生了后面的报错。

属性观察器(Property Observer)

  • 可以为非lazyvar存储属性设置属性观察器
  • willSet会传递新值,默认叫做newValue
  • didSet会传递旧值,默认叫做oldValue
  • 在初始化器中设置属性值不会出发willSetdidSet
  • 在属性定义时设置初始值也不会出发willSetdidSet
struct Circle {
   
    var radius: Double {
   
        willSet {
   
            print("willSet", newValue)
        }

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

    init() {
   
        self.radius = 1.0
        print("Circle init!")
    }
}

var circle = Circle()
circle.radius = 10.5
print(circle.radius)

运行结果

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

全局变量、局部变量

属性观察器、计算属性的功能,同样可以应用在全局变量、局部变量身上

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


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

   age = 11
}
test()

inout的再次研究

首先看下面的代码

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

var age = 10
test(&age) // 此处加断点

将程序运行至断点处,观察汇编

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  

我们可以看到函数test调用之前,参数的传递情况如下

对于上述比较简单的情况,我们知道inout的本质就是进行引用传递,接下来,我们考虑一些更加复杂的情况

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() {
   
        print("width= \(width), side= \(side), girth= \(girth)")
    }
}


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



var s = Shape(width: 10, side: 4)
test(&s.width)	// 断点1
s.show()
print("-------------")
test(&s.side)   //断点2
s.show()
print("-------------")
test(&s.girth)  //断点3
s.show()
print("-------------")

上述案例里面,全局变量s的类型是结构体 Struct Shape,它的内存放的是两个存储属性widthside,其中side带有属性观察器,另外Shape还有一个计算属性girth,我们首先不加断点运行一下程序,观察一下运行结果

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

看得出来,inout对于三种属性都产生了作用,那么它的底层到底是如何处理和实现的呢?我们还是要通过汇编来一探究竟。便于汇编分析,我们截取部分代码进行编译运行



首先看普通的属性👇👇👇👇

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() {
   
        print("width= \(width), side= \(side), girth= \(girth)")
    }
}


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

var s = Shape(width: 10, side: 4)
test(&s.width) // 断点处,传入普通属性width作为test的inout参数

汇编结果如下

SwiftTest`main:
    0x100001310 <+0>:   pushq  %rbp
    0x100001311 <+1>:   movq   %rsp, 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值