美文网首页iOS程序猿
Swift语法 Swift5 【08 - 属性】

Swift语法 Swift5 【08 - 属性】

作者: Liwx | 来源:发表于2020-05-08 17:18 被阅读0次

    • 作者: Liwx
    • 邮箱: 1032282633@qq.com
    • 源码: 需要源码的同学, 可以在评论区留下您的邮箱

    iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

    00 - 汇编
    01 - 基础语法
    02 - 流程控制
    03 - 函数
    04 - 枚举
    05 - 可选项
    06 - 结构体和类
    07 - 闭包
    08 - 属性
    09 - 方法
    10 - 下标
    11 - 继承
    12 - 初始化器init
    13 - 可选项


    目录

    • 01-属性
    • 02-存储属性
    • 03-计算属性
    • 04-枚举rawValue原理
    • 05-延迟存储属性(Lazy Stored Property)
    • 06-延迟存储属性注意点
    • 07-属性观察器(Property Observer)
    • 08-全局变量、局部变量
    • 09-inout的再次研究
    • 10-inout的本质总结
    • 11-类型属性(Type Property)
    • 12-类型属性细节
    • 13-单例模式

    01-属性

    • Swift中跟实例相关的属性可以分为2大类型: 存储属性(Stored Property), 计算属性(Computed Property)

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

    • 计算属性
      • 本质就是方法(函数)
      • 不占用实例的内存
      • 枚举、结构体、类可以定义计算属性
    struct Circle {
        // 存储属性 占用内存
        var radius: Double
        // 计算属性 不占用内存
        var diameter: Double {
            set {
                radius = newValue / 2
            }
            get {
                radius * 2   // 如果只有单一表达式,可以省略return
            }
            /*
             也可以这样写
             set(newDiameter) {
             radius = newDiameter / 2
            }
             */
        }
    }
    
    var circle = Circle(radius: 5)
    print(circle.radius)        // 5.0
    print(circle.diameter)      // 10.0
    
    circle.diameter = 12
    print(circle.radius)        // 6.0
    print(circle.diameter)      // 12.0
    
    print(MemoryLayout<Circle>.stride)  // 8
    

    02-存储属性

    • 关于存储属性, Swift有个明确的规定
    • 在创建或者结构体的实例时,必须为所有的存储属性设置一个合适的初始值
      • 可以在初始化器里为存储属性设置初始值
      • 可以分配一个默认的属性值作为属性定义的一部分

    • 在初始化器里为存储属性设置一个初始值
    struct Point {
        var x: Int
        var y: Int
        init() {
            x = 11
            y = 22
        }
    }
    

    • 分配一个默认的属性值作为属性定义的一部分
    struct Point {
        var x: Int = 11
        var y: Int = 22
    }
    var p = Point()
    

    03-计算属性

    • set传入的新增默认叫做newValue,也可以自定义
    struct Circle {
        var radius: Double
        var diameter: Double {
    //        set(newDiameter) {    // 自定义
    //            radius = newDiameter / 2
    //        }
            set {
                radius = newValue / 2
            }
            get {
                radius * 2
            }
        }
    }
    

    • 定义计算属性只能用var, 不能用let
      • let代表常量: 值是一成不变
      • 计算属性的值是可能发生变化的(即使是只读计算属性)

    • 只读计算属性: 只有get, 没有set
    struct Circle {
        var radius: Double
        var diameter: Double {
            get {
                radius * 2
            }
        }
    }
    

    • 只读计算属性, 只有get方法,可以省略get
    struct Circle {
        var radius: Double
        var diameter: Double { radius * 2 }
    }
    
    • 注意: 不能只有set方法, 没有get方法

    04-枚举rawValue原理

    • 枚举原始值rawValue的本质是: 只读计算属性
    enum TestEnum : Int {
        case test1 = 1, test2 = 2, test3 = 3
        var rawValue: Int {
            switch self {
            case .test1:
                return 10
            case .test2:
                return 11
            case .test3:
                return 12
            }
        }
    }
    
    print(TestEnum.test3.rawValue)  // 12
    

    05-延迟存储属性(Lazy Stored Property)

    • 使用lazy可以定义一个延迟存储属性, 在第一次用到属性的时候才会进行初始化
    • lazy属性必须是var, 不能是let
      • let必须在实例的初始化方法完成之前就拥有值
    • 如果多条线程同时第一次访问lazy属性
      • 无法保证属性只被初始化1次 (不是线程安全)
    class Car {
        init() {
            print("Car init!")
        }
        func run() {
            print("Car is running!")
        }
    }
    
    class Person {
        lazy var car = Car()
    //    lazy let car1 = Car()   // 延迟存储属性必须用var,  error: 'lazy' cannot be used on a let
        init() {
            print("Person init!")
        }
        func goOut() {
            car.run()
        }
    }
    
    let p = Person()    // 还没用到car延迟属性,因此此次不会创建car属性
    //Person init!
    print("------")
    p.goOut()    // 用到car延迟属性,此时才创建Car实例对象,调用Car init 方法
    //Car init!
    //Car is running
    

    • 应用场景
    class PhotoView {
        lazy var image: Image = {
           let url = "https://img.haomeiwen.com/i1253159/f30fb4cd197ff37d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
            let data = Data(url: url)
            return Image(data: data)
        }()
    }
    

    06-延迟存储属性注意点

    • 结构体包含一个延迟存储属性时, 只有var才能访问延迟存储属性
      • 因为延迟存储属性初始化需要改变结构体的内存
    struct Point {
        var x = 0
        var y = 0
        lazy var z = 0
    }
    
    /*
    let p = Point() // 此处用let,不能访问延迟存储属性p.z
    print(p.z)  // error: cannot use mutating getter on immutable value: 'p' is a 'let' constant
     */
    
    var p = Point() // 此处需用var, 才能正常访问p.z
    print(p.z)
    

    07-属性观察器(Property Observer)

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

    • 可以为非lazyvar 存储属性设置属性观察器
    struct Circle {
        var radius: Double {
            willSet {
                print("willSet", newValue)
            }
            didSet {
                print("didSet", oldValue, radius)
            }
        }
        init() {
            self.radius = 1.0
            print("Circle init!")
        }
    }
    
    // Circle init!
    var circle = Circle()
    
    // willSet 10.5
    // didSet 1.0 10.5
    circle.radius = 10.5
    
    // 10.5
    print(circle.radius)
    

    08-全局变量、局部变量

    • 属性观察器、计算属性的功能,同样可以应用在全局变量、局部变量身上

    • 全局变量 计算属性

    // 全局变量 计算属性 属性观察器应用
    var num: Int {
        get {
            return 10
        }
        set {
            print("setNum", newValue)
        }
    }
    
    num = 11    // setNum 11
    print(num)  // 10
    

    • 局部变量 存储属性 属性观察器应用
    // 局部变量 存储属性 属性观察器应用
    func test() {
        
        var age = 10 {
            willSet {
                print("willSet", newValue)
            }
            didSet {
                print("didSet", oldValue, age)
            }
        }
        
        age = 11
        // willSet 11
        // didSet 10 11
    }
    test()
    

    09-inout的再次研究

    struct Shape {
        var width: Int
        var side: Int {
            willSet {
                print("willSetSide", newValue)
            }
            didSet {
                print("didSetSide", oldValue, side)
            }
        }
        var girth: Int {
            set {
                width = newValue / side
                print("setGirth", newValue)
            }
            get {
                print("getGirth")
                return width * side
            }
        }
        func show() {
            print("width=\(width), side=\(side), girth=\(girth)")
        }
    }
    
    func test(_ num: inout Int) {
        num = 20
    }
    
    var s = Shape(width: 10, side: 4)
    test(&s.width)
    s.show()
    print("---------")
    test(&s.side)
    s.show()
    print("---------")
    test(&s.girth)
    s.show()
    
    // 运行结果:
    getGirth
    width=20, side=4, girth=80
    ---------
    willSetSide 20
    didSetSide 4 20
    getGirth
    width=20, side=20, girth=400
    ---------
    getGirth
    setGirth 20
    getGirth
    width=1, side=20, girth=20
    
    image.png

    10-inout的本质总结

    • 如果实参有物理内存地址,且没有设置属性观察器
      • 直接将实参的内存地址传入函数(实参进行引用传递)
    • 如果实参是计算属性 或者 设置了属性观察器
      • 采取了Copy In Copy Out的做法
        • 调用该函数是,先复制实参的值,产生副本 [get]
        • 副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
        • 函数返回后,再将副本的值覆盖实参的值[set]
    • 总结: inout的本质就是引用传递(地址传递)

    11-类型属性(Type Property)

    • 严格来说, 属性可以分为
      • 实例属性(Instance Property): 只能通过实例去访问
        • 存储实例属性(Stored Instance Property): 存储在实例的内存中,每个实例都有1份
        • 计算实例属性(Computed Instance Property)
      • 类型属性(Type Property): 只能通过类型去访问
        • 存储类型属性(Stored Type Property): 整个程序运行过程中,就只有1份内存(类似于全局变量)
        • 计算类型属性(Computed Type Property)
      • 可以通过static定义类型属性
        • 如果是,也可以用关键字class

    struct Car {
        static var count: Int = 0
        init() {
            Car.count += 1
            // 构造器,类似实例方法,实例方法不能调用使用 count 或者self.count 调用类型属性
    //        count += 1  // Static member 'count' cannot be used on instance of type 'Car'
        }
    }
    
    let c1 = Car()
    let c2 = Car()
    let c3 = Car()
    print(Car.count) // 3
    

    12-类型属性细节

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

    • 枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
    enum TestEnum : Int {
        static var count: Int = 0   // 存储类型属性、计算类
        case test1 = 1, test2 = 2
        func plus() {
            TestEnum.count += 1
        }
        static func getCount() -> Int { // 计算类型属性
            return count
        }
    }
    
    var t1 = TestEnum.test1
    t1.plus()
    print(TestEnum.count)
    t1.plus()
    print(TestEnum.count)
    t1.plus()
    print(TestEnum.count)
    print(TestEnum.getCount())
    

    13-单例模式

    public class FileManager {
        public static let shared = FileManager()    // 默认是lazy 就算被多个线程同时访问,保证`只会初始化一次`(线程安全)
        private init() {  }
    }
    
    public class FileManager {
        public static let shared = { // 默认是lazy 就算被多个线程同时访问,保证`只会初始化一次`(线程安全)
            // do something
            return FileManager()
        }()
        private init() {  }
    }
    
    let manager = FileManager.shared
    

    iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

    下一篇: 09 - 方法
    上一篇: 07 - 闭包


    相关文章

      网友评论

        本文标题:Swift语法 Swift5 【08 - 属性】

        本文链接:https://www.haomeiwen.com/subject/kpxqnhtx.html