美文网首页
iOS-Swift-方法、下标、继承

iOS-Swift-方法、下标、继承

作者: Imkata | 来源:发表于2020-01-09 15:32 被阅读0次

    一. 方法

    枚举、结构体、类都可以定义实例方法、类型方法。
    定义类方法:枚举、结构体使⽤static,类使⽤static或者class。

    class Car {
        static var count = 0
        init() {
            Car.count += 1
        }
        static func getCount() -> Int { count }
    }
        
    let c0 = Car()
    let c1 = Car()
    let c2 = Car()
    print(Car.getCount()) // 3
    
    1. 在实例方法和类型方法中都有个self,在实例方法中self代表实例对象,在类型方法中self代表类型
    2. 在上面类型方法static func getCount中:count等价于self.count、Car.count、Car.self.count

    1. mutating

    枚举和结构体是值类型,默认情况下,值类型的属性不能被自身的实例方法修改,在func关键字前加mutating可以允许这种修改行为(类本来就可以改,就不⽤管了)

    枚举:

    enum StateSwitch {
        case low, middle, high
        mutating func next() {
            switch self {
            case .low:
                self = .middle
            case .middle:
                self = .high
            case .high:
                self = .low
            }
        }
    }
    

    结构体:

    struct Point {
        var x = 0.0, y = 0.0
        mutating func moveBy(deltaX: Double, deltaY: Double) {
            x += deltaX
            y += deltaY
            // self = Point(x: x + deltaX, y: y + deltaY) 也是修改自己内存
        }
    }
    

    2. @discardableResult

    在func前面加个@discardableResult,可以消除函数调用后返回值未被使用的警告⚠️

    struct Point {
        var x = 0.0, y = 0.0
        @discardableResult mutating
        func moveX(deltaX: Double) -> Double {
            x += deltaX
            return x
        }
    }
    var p = Point()
    p.moveX(deltaX: 10)
    
    
    @discardableResult
    func get() -> Int {
        return 10
    }
    get()
    

    3. 将方法赋值给var、let

    方法也可以像函数那样,赋值给var、let

    //如果run类方法、run实例方法的参数和返回值是一样的,那么怎么知道拿到的是哪个方法呢?
    struct Person {
        var age: Int
        func run(_ v: Int) { print("func run", age, v) }
        static func run(_ v: Int) { print("static func run", v) }
    }
    
    let fn1 = Person.run //默认拿到的是类方法
    fn1(10) //调用类方法,打印:static func run 10
    
    let fn2: (Int) -> () = Person.run //也可以指定类型
    fn2(20) //调用类方法,打印:static func run 20
    
    let fn3: (Person) -> ((Int) -> ()) = Person.run //指定类型后
    fn3(Person(age: 18))(30) //调用实例方法,打印:func run 18 30
    
    如果fn3看不懂可以看下面一步一步的解释:
    var fn1 = Person.run //fn1是(Person) -> ((Int) -> ())类型的。接收一个person实例,返回一个方法
    var fn2 = fn1(Person(age: 18)) //fn2是(Int) -> ()类型的。fn2就是fn1接收一个实例返回的一个方法
    fn2(30) //调用实例方法,打印:func run 18 30
    

    二. 下标

    使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
    subscript的语法类似于实例方法、计算属性,本质就是方法(函数)

    1. 实例方法下标

    class Point {
        var x = 0.0, y = 0.0
        subscript(index: Int) -> Double {
            set {
                if index == 0 {
                    x = newValue
                } else if index == 1 {
                    y = newValue
                }
            }
            get {
                if index == 0 {
                    return x
                } else if index == 1 {
                    return y
                }
                return 0
            }
        }
    }
        
    var p = Point()
    p[0] = 11.1
    p[1] = 22.2
    print(p.x) // 11.1
    print(p.y) // 22.2
    print(p[0]) // 11.1
    print(p[1]) // 22.2
    
    1. subscript中定义的返回值类型决定了:
      get方法的返回值类型
      set方法中newValue的类型
      (比如,上面subscript返回Double,那么subscript的get方法的返回值是Double,subscript的set方法的newValue也是Double)
    2. subscript可以接受多个参数,并且类型任意

    2. 类型方法下标

    class Sum {
        static subscript(v1: Int, v2: Int) -> Int {
            return v1 + v2
        }
    }
    print(Sum[10, 20]) // 30
    

    3. 下标的细节

    1. subscript可以没有set方法,但必须要有get方法,如果只有get方法,可以省略get(和计算属性的规定一样)

    只有get:

    class Point {
        var x = 0.0, y = 0.0
        subscript(index: Int) -> Double {
            get {
                if index == 0 {
                    return x
                } else if index == 1 {
                    return y
                }
                return 0
            }
        }
    }
    

    省略get:

    class Point {
        var x = 0.0, y = 0.0
        subscript(index: Int) -> Double {
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
            return 0
        }
    }
    
    2. subscript可以设置参数标签
    class Point {
        var x = 0.0, y = 0.0
        subscript(index i: Int) -> Double {
            if i == 0 {
                return x
            } else if i == 1 {
                return y
            }
            return 0
        }
    }
        
    var p = Point()
    p.y = 22.2
    print(p[index: 1]) // 22.2
    
    3. 结构体、类作为返回值对比

    结构体作为返回值:

    struct Point {
        var x = 0, y = 0
    }
    class PointManager {
        var point = Point()
        subscript(index: Int) -> Point {
            set { point = newValue }
            get { point }
        }
    }
    
    var pm = PointManager() 
    pm[0].x = 11
    pm[0].y = 22
    // Point(x: 11, y: 22)
    print(pm[0])
    // Point(x: 11, y: 22)
    print(pm.point)
    

    如果是结构体,想要给PointManager里面的结构体赋值,就必须实现set方法,如上,否则报错。
    pm[0].x = 11等价于pm[0] = Point( x: 11 , y: pm[0].y )
    pm[0].y = 22等价于pm[0] = Point( x: pm[0].x , y: 22 )
    为什么结构体就必须实现set方法?因为结构体是值传递,PointManager里面的和传到PointManager外面的肯定不是同一个结构体,想要改PointManager里面的结构体肯定要有set方法。

    类作为返回值:

    class Point {
        var x = 0, y = 0
    }
    class PointManager {
        var point = Point()
        subscript(index: Int) -> Point {
            get { point }
        }
    }
    
    var pm = PointManager() 
    pm[0].x = 11
    pm[0].y = 22
    // Point(x: 11, y: 22)
    print(pm[0])
    // Point(x: 11, y: 22)
    print(pm.point)
    

    如果是类,对于pm[0].x = 11,pm[0]获取的就是point,是个指针,当然可以直接通过point访问x然后赋值11 (pm[0].x = 11相当于point.x = 11),所以如果是类不需要写set方法。

    其实他们俩的区别就是值类型和引用类型的区别。

    4. 接收多个参数的下标
    class Grid {
        var data = [
                    [0, 1, 2],
                    [3, 4, 5],
                    [6, 7, 8]
                    ]
        subscript(row: Int, column: Int) -> Int {
            set {
                guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                    return
                }
                data[row][column] = newValue
            }
            get {
                guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                    return 0
                }
                return data[row][column]
            }
        }
    }
    
    var grid = Grid() 
    grid[0, 1] = 77
    grid[1, 2] = 88
    grid[2, 0] = 99
    print(grid.data)
    // [0, 77, 2],
    // [3, 4, 88],
    // [99, 7, 8]
    

    三. 继承

    1. 继承简介

    值类型(枚举、结构体)不支持继承,只有类支持继承

    没有父类的类称为基类
    Swift并没有像OC、Java那样的规定:任何类最终都要继承自某个基类

    没规定.png

    2. 继承的类内存结构分析

    创建三个类,继承关系如下:

    class Animal {
        var age = 0
    }
    class Dog : Animal {
        var weight = 0
    }
    class ErHa : Dog {
        var iq = 0
    }
    

    Animal:

    let a = Animal()
    a.age = 10
    // 系统实际分配32字节
    print(Mems.size(ofRef: a))
    /*
    0x00000001000073e0 类型相关
    0x0000000000000002 引用计数
    0x000000000000000a 10
    0x0000000000000000
    */
    print(Mems.memStr(ofRef: a))
    

    如上,a对象系统实际分配32字节,前8字节存放类型相关,后8字节存放引用计数,再后8字节存放10,(8 + 8 + 8 = 24字节,因为要是16倍数,所以实际分配32)。

    Dog继承于Animal:

    let d = Dog()
    d.age = 10
    d.weight = 20
    // 系统实际分配32字节
    print(Mems.size(ofRef: d))
    /*
    0x0000000100007490 类型相关
    0x0000000000000002 引用计数
    0x000000000000000a 10
    0x0000000000000014 20
     */
    print(Mems.memStr(ofRef: d))
    

    如上,Dog继承于Animal,所以d对象也有age属性,d对象系统实际分配32字节,前8字节存放类型相关,后8字节存放引用计数,再后8字节存放10,最后8字节存放20。(8 + 8 + 8 + 8 = 32字节)

    ErHa继承于Dog:

    let e = ErHa()
    e.age = 10
    e.weight = 20
    e.iq = 30
    // 系统实际分配48字节
    print(Mems.size(ofRef: e))
    /*
    0x0000000100007560 引用计数
    0x0000000000000002 类型相关
    0x000000000000000a 10 
    0x0000000000000014 20
    0x000000000000001e 30
    0x0000000000000000
     */
    print(Mems.memStr(ofRef: e))
    

    如上,ErHa继承于Dog,所以e对象也有weight、age属性,e对象系统实际分配48字节,前8字节存放类型相关,后8字节存放引用计数,再后8字节存放10,再后8字节存放20,再后8字节存放30,最后8字节存放0。(8 + 8 + 8 + 8 + 8 = 40字节,因为要是16倍数,所以实际分配48)

    3. 重写

    子类可以重写父类的方法、下标、属性(属性只能重写为计算属性),重写必须加上override关键字

    ① 重写方法、下标

    • 重写实例方法、下标

    父类:

    class Animal {
        func speak() {
            print("Animal speak")
        }
        subscript(index: Int) -> Int {
            return index
        }
    }
    
    var anim: Animal
    anim = Animal()
    
    // Animal speak
    anim.speak()
    
    // 6
    print(anim[6])
    

    子类重写:

    class Cat : Animal {
        override func speak() {
            super.speak()
            print("Cat speak")
        }
        override subscript(index: Int) -> Int {
            return super[index] + 1
        }
    }
    
    anim = Cat() // 多态
    
    // Animal speak
    // Cat speak
    anim.speak()
    
    // 7
    print(anim[6])
    

    上面的anim = Cat()是父类指针指向子类对象,就是多态。
    关于多态:anim.speak(),在编译的时候anim并不知道要调用的是父类还是子类的speak方法,运行的时候才会根据实际类型调用子类的speak方法。

    • 重写类型方法、下标

    被class修饰的类型方法、下标,允许被子类重写
    被static修饰的类型方法、下标,不允许被子类重写
    (比如,父类使用class修饰,子类重写然后用static修饰,那么子类的子类就不能再重写这个类型方法了)

    父类:

    class Animal {
        class func speak() {
            print("Animal speak")
        }
        class subscript(index: Int) -> Int {
            return index
        }
    }
    
    // Animal speak
    Animal.speak()
    
    // 6
    print(Animal[6])
    

    子类重写:

    class Cat : Animal {
        override class func speak() {
            super.speak()
            print("Cat speak")
        }
        override class subscript(index: Int) -> Int {
            return super[index] + 1
        }
    }
    
    // Animal speak
    // Cat speak
    Cat.speak()
    
    // 7
    print(Cat[6])
    

    ② 重写属性

    1. 子类可以将父类的var属性(存储、计算)重写为计算属性
      子类不可以将父类属性重写为存储属性
      只能重写var属性,不能重写let属性
      重写时,属性名、类型要一致

    2. 子类重写后的属性权限不能小于父类属性的权限:
      如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
      如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的

    • 重写实例属性

    父类:

    class Circle {
        var radius: Int = 0
        var diameter: Int {
            set {
                print("Circle setDiameter")
                radius = newValue / 2
            }
            get {
                print("Circle getDiameter")
                return radius * 2
            }
        }
    }
    
    var circle: Circle
    circle = Circle()
    circle.radius = 6
    // Circle getDiameter
    // 12
    print(circle.diameter)
    // Circle setDiameter
    circle.diameter = 20
    // 10
    print(circle.radius)
    

    子类重写:

    class SubCircle : Circle {
        override var radius: Int {
            set {
                print("SubCircle setRadius")
                super.radius = newValue > 0 ? newValue : 0
            }
            get {
                print("SubCircle getRadius")
                return super.radius
            }
        }
        override var diameter: Int {
            set {
                print("SubCircle setDiameter")
                super.diameter = newValue > 0 ? newValue : 0 }
            get {
                print("SubCircle getDiameter")
                return super.diameter }
        }
    }
    
    circle = SubCircle()
    // SubCircle setRadius
    circle.radius = 6
    // SubCircle getDiameter
    // Circle getDiameter
    // SubCircle getRadius
    // 12
    print(circle.diameter)
    // SubCircle setDiameter
    // Circle setDiameter
    // SubCircle setRadius
    circle.diameter = 20
    // SubCircle getRadius
    // 10
    print(circle.radius)
    

    上面为什么要使用super.diameter?
    如果不使用super,执行circle.diameter就会造成死循环,所以如果想要访问父类的属性请使用super。

    • 重写类型属性

    被class修饰的计算类型属性,可以被子类重写(class不能修饰存储类型属性)
    被static修饰的类型属性(存储、计算),不可以被子类重写

    父类:

    class Circle {
        static var radius: Int = 0
        class var diameter: Int {
            set {
                print("Circle setDiameter")
                radius = newValue / 2
            }
            get {
                print("Circle getDiameter")
                return radius * 2
            }
        }
    }
    
    Circle.radius = 6
    // Circle getDiameter
    // 12
    print(Circle.diameter)
    // Circle setDiameter
    Circle.diameter = 20
    // 10
    print(Circle.radius)
    

    子类重写:

    class SubCircle : Circle {
        override static var diameter: Int {
            set {
                print("SubCircle setDiameter")
                super.diameter = newValue > 0 ? newValue : 0
            }
            get {
                print("SubCircle getDiameter")
                return super.diameter
            }
        }
    }
    
    SubCircle.radius = 6
    // SubCircle getDiameter
    // Circle getDiameter
    // 12
    print(SubCircle.diameter)
    // SubCircle setDiameter
    // Circle setDiameter
    SubCircle.diameter = 20
    // 10
    print(SubCircle.radius)
    

    4. 属性观察器

    可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器

    • 存储实例属性
    class Circle {
        var radius: Int = 1
    }
    class SubCircle : Circle {
        override var radius: Int {
            willSet {
                print("SubCircle willSetRadius", newValue)
            }
            didSet {
                print("SubCircle didSetRadius", oldValue, radius)
            }
        }
    }
    
    var circle = SubCircle()
    // SubCircle willSetRadius 10
    // SubCircle didSetRadius 1 10
    circle.radius = 10
    
    • 添加属性观察器的存储实例属性
    class Circle {
        var radius: Int = 1 {
            willSet {
                print("Circle willSetRadius", newValue)
            }
            didSet {
                print("Circle didSetRadius", oldValue, radius)
            }
        }
    }
    class SubCircle : Circle {
        override var radius: Int {
            willSet {
                print("SubCircle willSetRadius", newValue)
            }
            didSet {
                print("SubCircle didSetRadius", oldValue, radius)
            }
        }
    }
    
    var circle = SubCircle()
    // SubCircle willSetRadius 10
    // Circle willSetRadius 10
    // Circle didSetRadius 1 10
    // SubCircle didSetRadius 1 10
    circle.radius = 10
    
    • 计算实例属性
    class Circle {
        var radius: Int {
            set {
                print("Circle setRadius", newValue)
            }
            get {
                print("Circle getRadius")
                return 20
            }
        }
    }
    class SubCircle : Circle {
        override var radius: Int {
            willSet {
                print("SubCircle willSetRadius", newValue)
            }
            didSet {
                print("SubCircle didSetRadius", oldValue, radius)
            }
        }
    }
    
    var circle = SubCircle()
    // Circle getRadius
    // SubCircle willSetRadius 10
    // Circle setRadius 10
    // Circle getRadius
    // SubCircle didSetRadius 20 20
    circle.radius = 10
    
    • 计算类型属性
    class Circle {
        class var radius: Int {
            set {
                print("Circle setRadius", newValue)
            }
            get {
                print("Circle getRadius")
                return 20
            }
        }
    }
    class SubCircle : Circle {
        override static var radius: Int {
            willSet {
                print("SubCircle willSetRadius", newValue)
            }
            didSet {
                print("SubCircle didSetRadius", oldValue, radius)
            }
        }
    }
    
    // Circle getRadius
    // SubCircle willSetRadius 10
    // Circle setRadius 10
    // Circle getRadius
    // SubCircle didSetRadius 20 20
    SubCircle.radius = 10
    
    补充:final关键字

    被final修饰的方法、下标、属性,禁止被重写
    被final修饰的类,禁止被继承

    相关文章

      网友评论

          本文标题:iOS-Swift-方法、下标、继承

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