美文网首页
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-方法、下标、继承

    一. 方法 枚举、结构体、类都可以定义实例方法、类型方法。定义类方法:枚举、结构体使⽤static,类使⽤stat...

  • 方法 下标 继承

    方法 在类型方法中 不能直接修改实例属性 mutating 默认情况下 值类型的属性不能被自身的实例方法修改 di...

  • Swift ~ 方法、下标、继承

    本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗...

  • 十一、 方法、下标、继承

    方法 什么是方法? 方法是关联了特定类型的函数类、结构体以及枚举都能定义实例方法,同时也都能定义类型方法 实例方法...

  • 10-方法、下标、继承

  • 09-方法、下标、继承

    一、方法(Method) mutating @discardableResult 二、下标(subscript) ...

  • swift中的继承

    只有类支持继承 重写实例方法、下标 重写类型方法、下标 用class修饰的可以被重写 重写属性 1.重写实例属性 ...

  • interface List

    它继承了Collection接口并且扩展了基于下标的随机访问的方法 基于下标的添加 public void add...

  • Swift学习_继承、重写的综合例子

    分别继承属性、下标脚本、方法、属性带观察器、还有增加新属性和方法 使用方法 定义一个学生继承人类 定义了一个继承人...

  • Swift之继承、构造过程

    继承 在Swift中 ,类可以调用和访问超类的方法,属性和下标脚本,并且可以重写这些方法,属性和下标脚本来优化或修...

网友评论

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

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