属性的分类
struct Circle {
var half:Double
var all:Double {
set {
half = newValue/2
}
get{
half*2
}
}
}
复制代码
-
在这边的half就是存储属性,all就是计算属性,存储属性占用空间大小,而计算属性不会占用,所以打印出这个结构体的大小是8.
-
注意:枚举中不能定义存储属性,但可以定义计算属性,计算属性的本质就是方法。枚举的空间是给他定义的枚举值和关联值。
-
newValue是默认的,可以直接使用。
-
所有存储属性在创建实例的时候,都必须初始化保证有值。
-
如果只写了get没有写set,那么就是只读计算属性,初始化的时候不能给他赋值,当然后续也不能赋值。因为计算属性不占用内存,所以初始化的时候不用给他赋值。
-
不能只写set不写get,会报错。
关于枚举原始值的本质
我们知道枚举每个枚举值占一个字节,虽然是原始值是int类型,但是还是只是占一个字节,那么很明显不是存储属性了,因为如果是存储属性,必然是八个字节,那么可以猜测是不是计算属性? 写个例子,且不能被重新赋值,也就是说他是只读的计算属性,不占用内存。
延迟存储属性
第一次用到属性的时候才会初始化,正常在初始化的时候,为了保证实例变量有值,肯定会走init方法,加了lazy以后,就可以在第一次使用该实例变量的时候进入初始化。像下面的,初始化pview并不会初始化里面的myLabel,只有你加入到view的时候才会走label的初始化,类似的还有图片的url加载。
注意:
-
lazy必须是var,不能是let
-
如果是多条线程访问,无法保证只初始化一次。
属性观察器
可以为非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
总结
-
首先调用函数,复制实参的值
-
传入函数地址,修改副本值
-
函数返回以后,覆盖实参值
类型属性
上面说道的都是实例属性,实例属性分为存储(实例)属性
和计算(实例)属性
,类型属性只能通过类
访问,也分为存储类型属性
和计算类型属
性,存储类型属性整个程序运行中,只有一份
,可以通过static
或者class
关键字定义。
存储类型属性
1.必须在定义的时候就给定初始值。因为它不像存储实例属性,没有init函数。
2.默认加了lazy,只有在第一次使用的时候才会初始化。
3.可以是let修饰,参考第一点,它不像存储实例属性那样可以延迟赋值。
4.枚举可以定义存储类型属性(不能定义存储实例属性)。它的内存不在枚举变量中,他是有一个单独的空间存放。
5.线程安全,只会保证初始化一次。
关于存储属性和计算属性的建议
一般来说,只有当两个属性有关系的时候,比如说某个属性需要通过另一个属性计算出来,那么可以设计成计算属性,这样两者就关联了,如果都设计成存储属性,那么必然要增加一个或者两个方法,如果都是计算属性,那么值无法保存下来也不好。