Swift-属性(Properties)(九)

来源:互联网 发布:jdbc 删除数据 编辑:程序博客网 时间:2024/04/28 08:26

前言

我们从接触开发就已经接触属性这个东西了,它是我们开发中的基本元素,只是我们很少去系统的了解属性的细节罢了。而Swift的属性和Object-C有着较大的差异,同时自己也是第一次更加深入的去了解属性的一些基本特性。
小节包含下面的知识点:

  • 存储属性
  • 计算属性
  • 属性观察器
  • 全局变量和局部变量

分条详述

  1. 存储属性

    简单来说,一个存储属性就是存储在特定类或者结构体的实例里的一个常量或变量,再简单点,我们每次创建的一个属性,使用var或者let修饰,就是存储属性。如果对一个结构体使用let修饰,那么即使它的属性使用var修饰,在设置初始值后也是不能改变的。

    //  结构体内部属性没有默认值struct Rectangle1 {var width: Intlet height: Int}var rect1 = Rectangle1(width: 20, height: 10)rect1.width = 11//  有默认值struct Rectangle2 {var width = 55let height = 66}var rect2 = Rectangle2()rect2.width = 212//  设置结构体实例为常量struct Rectangle3 {var width = 55let height = 66}let rect3 = Rectangle3()

    结合上面简单地几行代码,我有两点要说明:其一,结构体定义时如果内部属性赋值,那么对结构实例化的时候直接在结构体名字后面添加括号即可,如果仅仅是定义了属性的类型为没有默认值,那么实例化结构体的时候必须赋值,不然会直接编译错误。其二,如果实例化结构体为常量,那么不论结构体内部属性如何定义,都是不能在改变的。这是因为结构体是值类型,当值类型的实例被声明为常量的时候,它的所有属性也就成了常量,而属于引用类型的类则不一样,把一个引用类型的值赋给一个常量后,仍然可以修改该实例的变量属性,这点在上篇文章中有比较详细的说明。

    这里说一个比较陌生的存储类型:延迟存储类型。延迟存储类型是指当第一次被调用的时候才会计算其初始值的属性,在属性声明前使用关键字 lazy 来标识一个延迟的存储属性。显然,必须将延迟存储属性声明成变量,因为属性的初始值可能在实例化构造完成后才会得到,而常量属性在构造过程完成前必须要有初始值,是无法声明称延迟属性的。
    延迟存储属性有点类似OC中的懒加载,当属性的值依赖于实例的构造过程结束后才会知道具体值的外部因素时,或者当获得属性的初始值需要复杂或大量的计算时,延迟属性会很有用。看示例代码:

    //  导入数据类class DataImporter {// DataImporter 是将一个外部文件中的数据导入的类,初始化时会消耗不少的时间和资源var fileName = "data.txt"    //  文件名//  数据导入方法func importDateFromFile() {    //  实现数据导入功能}}//  管理数据类class DataManager {//  延迟存储属性,代码运行到这里时并不会真的去执行,类似做了个标记,用的时候再过来执行这句代码lazy var importer = DataImporter()var data = [String]()      //  存储字符串的数组//  管理数据func mangerData() {    //  实现管理数据的功能}init() {    //  此时,DataImporter 类第一次被创建及初始化    print(importer.fileName)}}

    如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。

  2. 计算属性

    除存储属性外,类、结构体和枚举都可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个 可选 的 * setter* ,来间接获取和设置其他属性或者变量的值。教材中的例子是描述一个几何体,感觉很形象的描述了问题。即给出一个起点,给出长和宽,计算一个矩形的中心点坐标,起点默认为矩形的左上角。

    struct StartPoint {//  起点坐标var x = 0.0, y = 0.0}struct Size {//  矩形长宽var width = 0.0, height = 0.0}//  矩形所在区域struct Rect {var origin = StartPoint()var size = Size()//  中心点var center: StartPoint {    get {        let centerX = origin.x + size.width / 2        let centerY = origin.y - size.height / 2        return StartPoint(x: centerX, y: centerY)    }    set {/----------  setter方法中新值默认名称是 newValue  ---------- /        origin.x = newValue.x - size.width / 2        origin.y = newValue.y + size.height / 2    }}}/*****************  具体分析  *****************///  创建一个矩形并赋初始值var firstRect = Rect(origin: StartPoint(x: 20, y: 30), size: Size(width: 300, height: 400))//  值拷贝一份,此时 firstRect.center 属性已经有值,因为初始化的时候会执行 getter 方法let originalCenter = firstRect.center//  重新给中心点赋值,赋值时会执行 setter 方法firstRect.center = StartPoint(x: 10, y: 10)firstRect.center.x      //  10firstRect.center.y      //  10//  执行 setter 方法,此时firstRect.origin的属性值已经被改变firstRect.origin.x      //  -140firstRect.origin.y      //  210//  初始默认值originalCenter.x        //  170originalCenter.y        //  -170

    上面已经说过,对于计算属性, setter 方法是可选的,仅仅有 getter 方法的计算属性是只读的,即智能通过外部属性值来计算自身的值,而不能去修改其他的值。同事,只读计算属性的声明可以去掉 get 关键字和花括号,如下:

    //  给出长宽高,计算一个立方体的面积struct Cuboid {var width = 0.0, height = 0.0, depth = 0.0var volume: Double {    return width * height * depth}}//  初始化一个立方体let someCuboid = Cuboid(width: 3, height: 4, depth: 5)someCuboid.volume       //  60

    必须使用 var 关键字定义计算属性,包括只读计算属性,因为他们的值不是固定的。其实当你尝试使用 let 定义计算属性的时候,会编译错误的。

  3. 属性观察器

    属性观察器监控和响应属性值的变化,每次属性设置值得时候都会调用属性观察器,即使新值和现在的值一样。在斯坦福公开课iOS8的前几节绘制笑脸的Demo中,属性观察器有比较充分的使用。其中,延迟属性不可添加观察器,非重写计算属性不必添加观察器,因为每次值的改变都会调用setter方法。

    可以为属性添加如下一个或者全部观察器(其实大都用的didSet这个方法,就好像页面加载时,默认都会在 viewDidLoad() 方法中实现操作,而viewWillLoad()方法只在需要处理某些特殊操作的时候才使用):

    • willSet 在新的值被设置之前调用
    • didSet 在新的值被设置之后立即调用

    willSet 观察器会将新的属性值作为常量参数传入,在willSet方法的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可以使用,默认名称是newValue。didSet 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认名称 oldValue。其实在didSet中是可以取到newValue的,名字就是属性名,而不是newValue罢了。顺便说一句,willSet、didSet在输入的时候都是不提示的,所以必须手动完整输入。看下面简单的代码示例:

    class StepCounter {var totolSteps: Int = 0 {    willSet {        print("willSet: ", newValue)    }    didSet {        print("didSet: ", oldValue, "    ", totolSteps)    }}}let steps = StepCounter()steps.totolSteps = 100//  输出:willSet:  100           didSet:  0      100steps.totolSteps = 333//  输出:willSet:  333           didSet:  100      333

    如果在一个属性的didSet观察器里给它赋值,这个值会替换改观察器之前设置的值,看几行代码的效果就知道了:

    class StepCounter {var totolSteps: Int = 0 {    willSet {        print("willSet: ", newValue)    }    didSet {        totolSteps = 999        print("didSet: ", oldValue, "    ", totolSteps)    }}}let steps = StepCounter()steps.totolSteps = 100//  输出:willSet:  100           didSet:  0      999steps.totolSteps = 333//  输出:willSet:  333           didSet:  999    999
  4. 全部变量和局部变量

    这个,就不多说了吧。。。全局变量实在函数、方法、闭包或者任何类型之外定义的变量,局部变量实在函数、方法或闭包内部定义的变量,它们的使用区间就好像它们的名字一样。有特别需要注意的地方:全局的常量或变量都是延迟计算的,和延迟存储属性相似,不同的地方在于,全局的常量或变量不需要标记 lazy 特性。局部范围的常量或变量不会延迟计算的。

结束语

作为最常用的属性,其实除了计算属性、延迟加载、属性观察器之外,我们都是经常用的(好像也没剩下啥了),而这些并不需要可以的去记,一旦自己开始写Swift代码,开始用到项目中去开发,慢慢的就都会用了,就好像我们不用想就知道牙在嘴里一样,只需要刚开始用的时候思考几次就好。

吐个槽,昨天晚上回到住的地方实在无聊,就想找部电影看看,感觉卧虎藏龙之青冥宝剑应该还不不错,毕竟武侠一直是很喜欢的,但是那分分钟能出戏几百次的台词和表情是闹哪样,这已经不是狗尾续貂,简直是狗尾巴草续貂!真的没坚持下去,换一个,看我的特工爷爷吧,只能说,青冥宝剑和特工爷爷之间,根本没有比较性!从个人体会来说,我的特工爷爷是我今年看到的最好的国产电影,没有之一。既有感动,也有最后几分钟精彩至极的打斗,不燃,但是却能荷尔蒙飙升。还有一群老头“干哈呢”的调侃。挺后悔当时没去电影院贡献一张票房。

总有一刻会打动我

0 0