Swift - 属性 相关

一、存储属性

概念:存储属性就是存储在特定类或结构体实例里的一个常量或变量。

注意:
1 不能在枚举中使用
2 不能被子类重写,但是可以在子类中进行属性观察

1.1 常量存储属性 (let 定义)

let id: Int

1.2 变量存储属性 (var 定义)

var name: String

1.3 延迟存储属性 (lazy 定义,懒加载)

lazy var变量: 类型 = { 创建变量代码 }()


二、计算属性

概念:计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。

注意: 不能用let修饰计算属性

2.1 完整写法

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

一个矩形的中心点可以从原点(origin)和大小(size)算出,所以不需要将它以显式声明的 Point 来保存。Rect 的计算属性 center 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。

2.2 简化setter写法

如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 (newValue)。

struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

2.3 只读计算属性

只有 getter ,没有 setter 的计算属性就是只读计算属性。不能设置新的值。

var name: String {
    return  “Rose"
}


三、属性观察器

概念:每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}

当然,willSet后面的参数名可以省略,用默认的 (newValue) 代替

四、相关问题

4.1 set和willSet、didSet共存?

我们知道,在同一个类型中,属性观察和计算属性是不能同时共存的。也就是说,想在一个属性定义中同时出现 set 和 willSet 或 didSet 是一件办不到的事情。计算属性中我们可以通过改写 set 中的内容来达到和 willSet 及 didSet 同样的属性观察的目的。如果我们无法改动这个类,又想要通过属性观察做一些事情的话,可能就需要子类化这个类,并且重写它的属性了。重写的属性并不知道父类属性的具体实现情况,而只从父类属性中继承名字和类型,因此在子类的重载属性中我们是可以对父类的属性任意地添加属性观察的,而不用在意父类中到底是存储属性还是计算属性:

class A {
    var number :Int {
        get {
            print("get")
            return 1
        }
        set {print("set")}
    }
}
class B: A {
    override var number: Int {
        willSet {print("willSet")}
        didSet {print("didSet")}
    }
}

调用 number 的 set 方法可以看到工作的顺序

let b = B()
b.number = 0

// 输出
// get
// willSet
// set
// didSet

set 和对应的属性观察的调用都在我们的预想之中。这里要注意的是 get 首先被调用了一次。这是因为我们实现了 didSet,didSet 中会用到 oldValue,而这个值需要在整个 set 动作之前进行获取并存储待用,否则将无法确保正确性。如果我们不实现 didSet 的话,这次 get 操作也将不存在。

4.2 类型属性(静态属性)

在Swift中,类型属性是类型定义的一部分,写在类型最外层花括号内,作用域就是在该类型支持的范围内。用关键字static来定义类型属性

struct AssociatedKeys {
    static var managerKey = “key"
}

4.3 static和class

对类来说,使用static或者class修饰符,都是可以的。它们使用后的效果是一样的,但是本质上是不同的:
1 class只能在类中使用,static可以在类、结构体或枚举中使用
2 static在类中应用时,其实是class final的别名
3 static修饰的属性或函数都不可以重写。但class修饰就可以重写
4 class不能修饰存储属性

class MyClass {
    class var bar: Int = 0
}
// 编译时会报错
class variables not yet supported

4.4 属性观察和inout

class Circle{
   lazy var area: Double = 0.0
    var r:Double = 0.0 {
        willSet{
            print("有新值")
            area = Double.pi*newValue*newValue
        }
    }
}
let circle = Circle()
func calcalate(r:inout Double){
   print("函数执行开始")
   r = 2.0
  print("函数执行结束")
}
calcalate(r: &circle.r)

// 运行结果:
// 函数执行开始
// 函数执行结束
// 有新值
此时r的值为2,area的值为12.5663

结论:如果函数的参数是inout修饰的,将监测的属性传入这个函数的时候,此时会将属性的值拷贝一份,在函数结束的时候,将值重新付给属性,所以函数执行完毕后,会触发监测函数

4.5 参数初始化

4.5.1 参数是闭包执行的结果:声明常量后,在一个紧接着的闭包中进行初始化

let purpleView: UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.purple
    return view
}()
// 等价于
let purpleView: UIView = iniPurpleView()
    
func iniPurpleView() -> UIView {
    let view = UIView()
    view.backgroundColor = UIColor.purple
    return view
}

4.5.2 让人眼前一亮的写法

let purpleView: UIView = {
    $0.backgroundColor = UIColor.purple
    return $0
}(UIView())

这种写法就可以省去在闭包中给变量命名的苦恼了,代码也更加紧凑。当然后面还有一些可读性更高的写法,需要对NSObject写extension,有兴趣的可以了解SwiftGG-让人眼前一亮的初始化方式

4.6 懒加载和参数初始化,计算属性 区分

注意三者写法的不同和调用的时机

class Person {
    var name: String = {
        print("闭包初始化")
        return "YY"
    }()
    lazy var lastName: String = {
        print("懒加载")
        return "YANG"
    }()
    var age: Int { // 计算属性
        print("AGE")
        return 20
    }
}

下面我们实例化一个Person变量

let p = Person()
// 闭包初始化在创建变量的同时就执行了,懒加载和计算属性都要等实例去调用属性时才会执行



结语:之后遇到属性相关的问题会继续补充,有错误请指出^^

参考文献:
Swift官方文档
Swift3.0 - 属性-酷走天涯
属性观察-王巍 (@ONEVCAT)

你可能感兴趣的:(Swift - 属性 相关)