从零开始的SWIFT开发记录之属性

属性的分类

        struct Circle {
            var half:Double
            var all:Double {
                set {
                    half = newValue/2
                }
                get{
                    half*2
                }
            }
        }
复制代码
  1. 在这边的half就是存储属性,all就是计算属性,存储属性占用空间大小,而计算属性不会占用,所以打印出这个结构体的大小是8.

  2. 注意:枚举中不能定义存储属性,但可以定义计算属性,计算属性的本质就是方法。枚举的空间是给他定义的枚举值和关联值。

  3. newValue是默认的,可以直接使用。

  4. 所有存储属性在创建实例的时候,都必须初始化保证有值。

  5. 如果只写了get没有写set,那么就是只读计算属性,初始化的时候不能给他赋值,当然后续也不能赋值。因为计算属性不占用内存,所以初始化的时候不用给他赋值。

  6. 不能只写set不写get,会报错。

关于枚举原始值的本质

我们知道枚举每个枚举值占一个字节,虽然是原始值是int类型,但是还是只是占一个字节,那么很明显不是存储属性了,因为如果是存储属性,必然是八个字节,那么可以猜测是不是计算属性? 写个例子,且不能被重新赋值,也就是说他是只读的计算属性,不占用内存。

截屏2022-06-14 16.55.35.png

延迟存储属性

第一次用到属性的时候才会初始化,正常在初始化的时候,为了保证实例变量有值,肯定会走init方法,加了lazy以后,就可以在第一次使用该实例变量的时候进入初始化。像下面的,初始化pview并不会初始化里面的myLabel,只有你加入到view的时候才会走label的初始化,类似的还有图片的url加载。

截屏2022-06-14 17.50.29.png

注意:

  1. lazy必须是var,不能是let

  2. 如果是多条线程访问,无法保证只初始化一次。

属性观察器

可以为非Lazy的var属性设置属性观察器,计算属性不能添加属性观察器。

class Circle{

        var dis:Double{

            willSet{

                print("willSet",newValue)

            }

            didSet{

                print("didSet",oldValue,dis)

            }

        }

        init(dis:Double){

            self.dis = dis

        }

    }

复制代码

属性观察器,计算属性的功能,可以作用在局部变量和全局变量。



    var number:Double{
    
        set{

            print(newValue)

        }

        get{

            return 10

        }

        

        

    }

    

    var newNumber:Double = 0.0{

       

        willSet{

            print("newNumber",newValue)

        }

        didSet{

            print("oldValue",oldValue,**self**.newNumber)

        }

    }
复制代码

copyIn & copyOut

关于inout关键字传递计算属性的值

我们知道inout传递是负责地址传递,那么在计算属性改变值得时候,会不会传递本身计算属性的地址呢?不会。在调用修改的方法之前,首先会调用计算属性的get方法,获取这块临时的空间,然后把这块临时的空间地址作为参数传递给修改方法,根据地址直接修改内存空间,最后再调用set方法。

A -> get(A的计算属性没有成员地址只能通过调用get方法拿到这个值 并且copy) -> TEST(A的copy传进来并且修改这块地址) -> set (覆盖实参)

关于inout关键字在带有属性观察器的存储属性修改

为了满足能够调用willSet和didSet,test函数是通用函数,只负责修改引用传递修改这块内存的数据,不存在判断。首先拷贝生成一个这个变量的局部变量,然后把局部变量传到test函数修改完了这个局部变量,然后赋值给这个变量,触发willset,willset内存修改完了,再触发didset。

A -> A(A本身就是存储属性有地址所以可以直接copy) -> TEST (A->B) -> B (因为B可以拿到A的地址,所以可以直接赋值给A) -> willSet(A->B) -> didSet

总结

  1. 首先调用函数,复制实参的值

  2. 传入函数地址,修改副本值

  3. 函数返回以后,覆盖实参值

类型属性

上面说道的都是实例属性,实例属性分为存储(实例)属性计算(实例)属性,类型属性只能通过访问,也分为存储类型属性计算类型属性,存储类型属性整个程序运行中,只有一份,可以通过static或者class关键字定义。

存储类型属性

1.必须在定义的时候就给定初始值。因为它不像存储实例属性,没有init函数。

2.默认加了lazy,只有在第一次使用的时候才会初始化。

3.可以是let修饰,参考第一点,它不像存储实例属性那样可以延迟赋值。

4.枚举可以定义存储类型属性(不能定义存储实例属性)。它的内存不在枚举变量中,他是有一个单独的空间存放。

5.线程安全,只会保证初始化一次。

关于存储属性和计算属性的建议

一般来说,只有当两个属性有关系的时候,比如说某个属性需要通过另一个属性计算出来,那么可以设计成计算属性,这样两者就关联了,如果都设计成存储属性,那么必然要增加一个或者两个方法,如果都是计算属性,那么值无法保存下来也不好。

猜你喜欢

转载自juejin.im/post/7110154864340140040
今日推荐