经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
Swift系列九 - 属性
来源:cnblogs  作者:1024星球  时间:2021/5/31 9:06:15  对本文有异议

任何一门语言都有属性的概念。Swift中的属性是怎么的呢?

一、属性

Swift中跟实例相关的属性可以分为2大类:存储属性和计算属性。

1.1. 存储属性(Stored Property

特点:

  • 类似于成员变量的概念;
  • 存储在实例的内存中;
  • 结构体、类可以定义存储属性;
  • 枚举不可以定义存储属性。

示例代码:

  1. struct Circle {
  2. var radius: Double
  3. }
  4. class classCircle {
  5. var radius: Double
  6. }

关于存储属性,Swift有个明确的规定:
在创建类或结构体的实例时,必须为所有的存储属性设置一个合适的初始值。

  • 可以在初始化器里为存储属性设置一个初始值;
  • 可以分配一个默认的属性值作为属性定义的一部分。

1.2. 计算属性(Computed Property

特点:

  • 本质就是方法(函数);
  • 不占用实例的内存;
  • 枚举、结构体、类都可以定义计算属性。

示例代码:

  1. struct Circle {
  2. // 存储属性
  3. var radius: Double
  4. // 计算属性
  5. var diameter: Double {
  6. set {
  7. print("set")
  8. radius = newValue / 2
  9. }
  10. get {
  11. print("get")
  12. return radius * 2
  13. }
  14. }
  15. }
  16. var c = Circle(radius: 10)
  17. c.radius = 11
  18. print("--1--")
  19. c.diameter = 40
  20. print("--2--")
  21. print(c.diameter)
  22. /*
  23. 输出:
  24. set
  25. get
  26. 20.0
  27. */

输出分析: 上面代码如果执行c.diameter = 40radius的值就会变为20。因为这样会执行diameterset方法(40作为参数),上面的案例看到变量newValue,但是代码中没有定义这个变量,其实newValueset方法提供的形参,只不过省略没有写而已,完整的set方法代码应该是set(newValue) {...}newValue是默认值,可以按照自己的规范修改(建议使用默认的形参命名)。c.diameter调用的是diameterget方法。

内存分析:
上面示例代码中结构体Circle占用多少内存呢?

  1. print(MemoryLayout<circle>.stride)
  2. // 输出:8

结果显示占用8个字节。因为计算属性的本质是方法。

补充说明:

  1. set传入的新值默认叫做newValue,也可以自定义。

    1. struct Circle {
    2. var radius: Double
    3. var diameter: Double {
    4. set(newDiameter) {
    5. radius = newDiameter / 2
    6. }
    7. get {
    8. return radius * 2
    9. }
    10. }
    11. }
  2. 只读计算属性:只有get,没有set

    如果是只读属性,get可以省略不写:

    1. struct Circle {
    2. var radius: Double
    3. var diameter: Double { radius * 2 }
    4. }
  3. 定义计算属性只能用var,不能用let

  4. set就必须有get

扩展: 枚举rawValue的本质就是只读的计算属性。

1.3. 属性观察器(Property Observer)

通过名字就可以联想到OC中的KVO,是的,两者确实有相似之处。在Swift中可以为非lazyvar存储属性 设置属性观察器。

示例代码:

  1. struct Circle {
  2. var radius: Double {
  3. willSet {
  4. print("willSet", newValue)
  5. }
  6. didSet {
  7. print("didSet", oldValue, radius)
  8. }
  9. }
  10. init() {
  11. self.radius = 2.0
  12. print("Circle Init")
  13. }
  14. }
  15. var c = Circle()
  16. // 输出:Circle Init
  17. c.radius = 3.0
  18. /*
  19. 输出:
  20. willSet 3.0
  21. didSet 2.0 3.0
  22. */

分析:

  • willSet会传递新值,默认叫做newValue
  • didSet会传递旧值,默认叫做oldValue
  • 在初始化器中设置属性值不会触发willSetdidSet。同样在属性定义时设置初始值也不会触发。

二、延迟存储属性(Lazy Stored Property)

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

特点:

  • lazy属性必须是var,不能是let(let必须在实例的初始化方法完成之前就拥有值);
  • 如果多条线程同时第一次访问lazy属性,无法保证属性只被初始化1次(非线程安全)。

示例代码:

  1. class Car {
  2. init() {
  3. print("Car init")
  4. }
  5. func run() {
  6. print("car run")
  7. }
  8. }
  9. class Person {
  10. lazy var car = Car()
  11. init() {
  12. print("Person init")
  13. }
  14. func goOut() {
  15. print("Person goOut")
  16. car.run()
  17. }
  18. }
  19. var p = Person()
  20. // 输出:Person init
  21. p.goOut()
  22. /*
  23. 输出:
  24. Person goOut
  25. Car init
  26. car run
  27. */

分析: 如果Person中的存储属性car没有lazy修饰,在创建Person对象p的时候就会调用存储属性car的初始化方法。添加lazy修饰后,只会在第一次使用car属性(对象)时进行初始化。

注意点: 当结构体包含一个延迟存储属性时,只有var才能访问延迟存储属性。因为延迟属性初始化时需要改变结构体的内存,而结构体如果使用let修饰后就不能修改所在内存。

三、类型属性(Type Property)

严格来说,属性可以分为:

  • 实例属性(Instance Property):只能通过实例去访问

    • 存储实例属性(Stored Instance Property):存储在实例的内存中,每个实例都有1份;
    • 计算实例属性(Computed Instance Property)
  • 类型属性(Type Property):只能通过类型去访问

    • 存储类型属性(Stored Type Property):整个程序运行过程中,就只有1份内存(类似于全局变量)
    • 计算实例属性(Computed Type Property)

可以通过static定义类型属性。如果是类,也可以用关键字class

示例代码:

  1. struct Shape {
  2. var width: Int
  3. static var count: Int = 30
  4. }
  5. var s = Shape(width: 10)
  6. s.width = 20
  7. print("before count:\(Shape.count)") // 输出:before count:30
  8. Shape.count = 40
  9. print("after count:\(Shape.count)") // 输出:after count:40

3.1. 类型属性细节

  1. 不同于存储实例属性,存储类型属性必须进行初始化,否则报错(因为类型没有像实例那样的init初始化器来初始化存储属性):

  2. 存储类型属性默认就是lazy,会在第一次使用的时候才初始化,就算被多个线程同时访问,保证只会初始化一次(线程安全)。

  3. 存储类型属性可以是let

  4. 枚举类型也可以定义类型属性(存储类型属性,计算类型属性)。

3.2. 单例模式

使用类型属性可以创建单例模式。
示例代码:

  1. class FileManager {
  2. public static let shared = FileHandle()
  3. private init() {}
  4. }
  5. var f1 = FileManager.shared;

把初始化器设为private,这样就无法让外界使用init创建实例。把类型属性设为public,在其他文件中也可以访问,存储类型属性再用let修饰,这样就能保证实例只能指向一块固定内存。

3.2. 类型存储属性的本质

第一步:示例代码

第二步:查看全局变量内存地址

分析:
num1内存地址:0x1000013f1 + 0x5df7 = 0x1000071E8
num2内存地址:0x1000013fc + 0x5df4 = 0x1000071F0
num3内存地址:0x100001407 + 0x5df1 = 0x1000071F8

结论:
num1,num2,num3三个变量的内存地址是连续的。

第三步:查看类型存储属性地址

分析:
num1内存地址:0x100001013 + 0x631d = 0x100007330
Car.count内存地址:0x100007338
num3内存地址:0x10000105c + 0x62e4 = 0x100007340

结论:
num1,Car.count,num3三个变量的内存地址是连续的。

从内寸角度看,类型存储属性写在外面和里面没有什么区别,写在类里面只是代表该属性有一定访问权限。

类型存储属性默认是lazy,所以在第一次访问的时候做了很多操作。而且只被初始化一次。

通过汇编查看类型存储属性初始化:

发现,类型属性初始化最终调用的是GCD中的dispatch_once,这样就保证了属性只被初始化一次。

原文链接:http://www.cnblogs.com/idbeny/p/swift-syntax-property.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号