Swift:面向对象(二)

一、继承(针对类)
二、多态(针对类)


三、协议(枚举、结构体、类都可以)
四、扩展(枚举、结构体、类都可以)


五、访问控制(枚举、结构体、类都可以)
六、内存管理(针对类)

大部分情况下,我们可以优先考虑使用(值类型 + 协议)的方式来代替直接使用引用类型,即面向协议编程占了主导,面向对象编程反而成了辅助。

  • 优先考虑使用协议,而不是父类(基类)。
  • 优先考虑值类型(structenum),而不是引用类型(class)。
  • 巧用协议的扩展功能。


一、继承(针对类)


继承是指一个类拥有了另一个类的属性和方法。枚举和结构体不支持继承,只有类才支持继承,用:表示。

// 基类,Animal类
class Animal {
    ...
}

// Dog类继承自Animal类
class Dog: Animal {
    ...
}

继承里一个很重要的关键词就是重写,无论是重写父类的属性还是方法,我们都必须显式地写上override关键字,否则会报错。

1、重写属性

  • 重写实例属性

子类可以把父类的存储属性和计算属性重写为计算属性,但是不能重写为存储属性。

class Animal {
    var age: Int = 0 // 存储属性
    var month: Int { // 计算属性
        set {
            print("Animal setMonth")
            
            age = newValue / 12
        }

        get {
            print("Animal getMonth")
            
            return age * 12
        }
    }
}

class Dog: Animal {
    override var age: Int { // 把父类的存储属性重写为计算属性
        set {
            print("Dog setAge")
            
            super.age = newValue
        }

        get {
            print("Dog getAge")
            
            return super.age
        }
    }
    
    override var month: Int { // 把父类的计算属性重写为计算属性
        set {
            print("Dog setMonth")
            
            super.month = newValue
        }

        get {
            print("Dog getMonth")
            
            return super.month
        }
    }
}

var animal = Dog()

// Dog setAge
animal.age = 11 
// Dog setMonth、Animal setMonth、Dog setAge
animal.month = 24 

// Dog getAge
print(animal.age) // 2 
// Dog getMonth、Animal getMonth、Dog getAge
print(animal.month) // 24
  • 重写类型属性

子类可以把父类用class定义的(不能是用static定义的)计算属性重写为计算属性,存储属性不支持重写。

class Animal {
    static var age: Int = 0 // 存储属性
    class var month: Int { // 用class定义的计算属性
        set {
            print("Animal setMonth")
            
            age = newValue / 12
        }

        get {
            print("Animal getMonth")
            
            return age * 12
        }
    }
}

class Dog: Animal {
    override class var month: Int { // 把父类用class定义的计算属性重写为计算属性
        set {
            print("Dog setMonth")
            
            super.month = newValue
        }
        get {
            print("Dog getMonth")
            
            return super.month
        }
    }
}

Dog.age = 11
Dog.month = 24

print(Dog.age)
print(Dog.month)

2、重写方法

  • 重写实例方法
class Animal {
    func speak() {
        print("Animal speak")
    }
}

class Dog: Animal {
    override func speak() {
        super.speak()
        
        print("Dog speak")
    }
}

var animal = Animal()
animal.speak() // Animal speak

animal = Dog()
animal.speak() // Animal speak、Dog speak
  • 重写类型方法

class定义的类型方法才支持重写,用static定义的类型方法不支持重写。

class Animal {
    class func speak() {
        print("Animal speak")
    }
}

class Dog: Animal {
    override class func speak() {
        super.speak()
        
        print("Dog speak")
    }
}

Animal.speak() // Animal speak
Dog.speak() // Animal speak、Dog speak


二、多态(针对类)


多态是指父类指针指向子类对象。因为只有类才支持继承,所以也只有类才支持多态。

class Animal {
    func speak() {
        print("Animal speak")
    }
    
    func eat() {
        print("Animal eat")
    }
    
    func sleep() {
        print("Animal sleep")
    }
}

class Dog: Animal {
    override func speak() {
        print("Dog speak")
    }
    
    override func eat() {
        print("Dog eat")
    }
    
    func run() {
        print("Dog run")
    }
}

var animal: Animal = Animal() // animal变量为Animal类型,指向了一个Animal对象
animal.speak() // Animal speak
animal.eat() // Animal eat
animal.sleep() // Animal sleep

animal = Dog() // animal变量重新指向了一个Dog对象,变成了Dog类型
animal.speak() // Dog speak,虽然在编译时编译器依旧认为animal是Animal类型的,但只要编译能通过,运行时才会决定到底调用谁的方法
animal.eat() // Dog eat
animal.sleep() // Animal sleep
//animal.run() // 不能调用run,因为在编译时编译器依旧认为animal是Animal类型的,它没有run方法,编译都通不过,OC也是这样的

Swift方法的调用流程

简单地说,OC方法的调用流程:指针变量 --> 对象 --> 对象的isa指针/superclass指针 --> 对象所属的类/父类 --> 方法列表 --> 函数的地址 --> 调用函数。

Swift实例方法的调用流程:变量 --> 对象 --> 对象的前8个字节(指向该对象的类型信息) --> 对象的类型信息(里面存储着这个类所有方法的地址) --> 函数的地址 --> 调用函数。

animal变量指向Animal对象时
animal变量指向Dog对象时

可见有一个很明显的区别是:Swift里没有superclass指针这个东西,子类是直接把父类所有的方法地址都存储在自己的类型信息里。

另一个区别是:OC调用类方法照样是遵循这套调用流程的,而Swift调用类型方法则是直接拿代码区的函数来调用,没有这套流程。


三、协议(枚举、结构体、类都可以)


和OC一样,协议一般用来定义一些属性的声明、方法的声明,而交给遵守该协议的东西去实现,而且默认是必须实现,枚举、结构体、类都可以遵守协议。

// 协议1
protocol Protocol1 {
    ...
}

// 协议2
protocol Protocol2 {
    ...
}

// 协议3
protocol Protocol3 {
    ...
}

// 父类:遵守协议1
class ParentClass: Protocol1 {
    ...
}

// 子类:继承自ParentClass,并遵守协议1、协议2、协议3
class SubClass: ParentClass, Protocol2, Protocol3 {
    ...
}

1、协议中的属性

  • 协议中声明的属性,不需要指定是存储属性还是计算属性,你只需要指定是实例属性还是类型属性就可以了(但是为了枚举、结构体、类可以通用某个协议,类型属性必须得用static定义,而不能用class定义),但必须用{ get set }来表明该属性可读可写,或者用{ get }来表明该属性只读
  • 协议中声明的属性,必须用var
protocol Drawable {
    var x: Int { get set } // 声明了一个可读可写的实例属性
    var y: Int { get } // 声明了一个只读的实例属性
}
  • 实现协议中声明的属性时,其访问权限不能小于声明时的那个权限
class Person: Drawable {
    var x: Int = 11 // 存储属性,肯定是可读可写的,不小于声明时的权限(可读可写)
    var y: Int = 12 // 存储属性,肯定是可读可写的,不小于声明时的权限(只读)
}
class Person: Drawable {
    var x: Int { // 计算属性,这里定义为可读可写的,不小于声明时的权限(可读可写)
        get { 11 }
        set {}
    }
    
    var y: Int { // 计算属性,这里定义为只读的,不小于声明时的权限(只读)
        get { 12 }
//        set {} // 当然也可以定义为可读可写的计算属性
    }
}

2、协议中的方法

同样的,为了枚举、结构体、类可以通用某个协议,类型方法必须得用static定义,而不能用class定义,当然你实现类型方法的时候可以是static也可以是class,这要看你想不想这个方法被子类重写。

protocol Drawable {
    func draw() // 声明了一个实例方法
    static func draw() // 声明了一个类型方法
}

class Person: Drawable {
    func draw() {
        print("实例方法draw")
    }
    
    static func draw() {
        print("类型方法draw")
    }
    
//    class func draw() {
//        print("类型方法draw")
//    }
}

3、协议的继承

一个协议可以继承一个或多个其他协议。

// 父协议1
protocol ParentProtocol1 {
    
}

// 父协议2
protocol ParentProtocol2 {
    
}

// 父协议3
protocol ParentProtocol3 {
    
}

// 子协议
protocol SubProtocol: ParentProtocol1, ParentProtocol2, ParentProtocol3 {
    
}


四、扩展(枚举、结构体、类都可以)


类似于OC的分类,Swift的扩展也有两个作用:

  • 一般用来给一个已有的枚举、结构体、类扩展计算属性(注意不能扩展存储属性)、方法、协议,注意是扩展哦,不是重写,OC的分类是可以重写类里面的东西的;
  • 把一个类里同一功能的函数聚合、不同功能的函数分散,编写到不同的扩展里去,实现代码分离,便于维护。
  • 需注意:自己的扩展里不能重写自己本类里的方法、也不能重写父类本类、扩展里的方法,所以如果想要实现重写方法的功能,父类和子类的方法都必须在各自的本类里

1、添加计算属性

extension Double {
    // 只读的计算属性
    var km: Double {
        get {
            return self / 1_000.0
        }
    }
    var m: Double {
        get {
            return self
        }
    }
    var dm: Double {
        get {
            return self * 10
        }
    }
    var cm: Double {
        get {
            return self * 100
        }
    }
    var mm: Double {
        get {
            return self * 1_000.0
        }
    }
}

var distance = 1000.0
print(distance.km) // 1km
print(distance.m) // 1000m
print(distance.dm) // 10000dm
print(distance.cm) // 100000cm
print(distance.mm) // 1000000mm

2、添加方法

extension Int {
    // 方法
    func square() -> Int {
        return self * self
    }
}

print(10.square()) // 100

3、遵守某个协议

class Person {
    var age: Int
    var name: String

    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}

// 遵守某个协议
extension Person: Equatable {
    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.age == rhs.age
    }
}

var p1 = Person(age: 11, name: "张三")
var p2 = Person(age: 11, name: "张三")

print(p1 == p2) // true

4、代码分离

class HomeViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate {
    lazy var tableView = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }
}

// MARK: - UITableViewDataSource
extension HomeViewController {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        return cell
    }
}

// MARK: - UITableViewDelegate
extension HomeViewController {
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 44
    }
}


五、访问控制(枚举、结构体、类都可以)


  • 模块:是指一个独立的项目或者动态库,一个模块里可以通过import关键字导入另外一个模块。
  • 源文件:是指我们编写的一个个.swift文件,它通常属于某个模块,而它内部又包含着多个类和函数等。
  • private:修饰的属性、方法只能在当前类内访问,修饰的类/结构体/枚举只能在当前源文件内访问(实际开发中我们一把都会把属性、方法写成private的,感觉就是在OC的.m文件里写代码一样,等需要时再把private去掉暴露出去)
  • fileprivate:修饰的属性、方法、类/结构体/枚举只能在当前源文件内访问
  • internal(默认):修饰的属性、方法、类/结构体/枚举能在(当前整个项目 - 项目导进来动态库等其它模块)内访问(所以Swift项目很少import其它文件)
  • public:修饰的属性、方法、类/结构体/枚举能在(当前整个项目 + 项目导进来的动态库等其它模块)内访问到它,但是项目导进来的动态库等其它模块不能继承、重写它们
  • open:修饰的属性、方法、类能在(当前整个项目 + 项目导进来的动态库等其它模块)内访问到它,而且项目导进来的动态库等其它模块能继承、重写它们open不能修饰结构体/枚举,如果想公开就用public吧)


六、内存管理(针对类)


和OC一样,Swift也采用基于引用计数的ARC来进行内存管理,我们这里说的一般指堆内存。

1、引用修饰符

Swift里有3种引用修饰符:

  • 强引用:默认情况下就是强引用
class Person {
    
}

var p = Person() // 强引用
  • 弱引用:通过weak来定义弱引用,但是弱引用必须是可选项、还必须是var,因为ARC将来会把这个引用自动置为nil,不是可选项当然就不能置为nil,不是var当然就不能修改值
class Person {
    
}

weak var p2: Person? = Person() // 弱引用
  • 无主引用:通过unowned来定义无主引用,类似于OC的unsafe_unretained,ARC将来不会把这个引用自动置为nil,所以它不是可选项也可以,不是var也可以
class Person {
    
}

unowned var p = Person() // 无主引用

2、循环引用

2.1 使用weakunowned都可以解决循环引用
class Person {
    var apartment: Apartment?
    
    deinit {
        print("person对象销毁")
    }
}

class Apartment {
    var person: Person?
    
    deinit {
        print("apartment对象销毁")
    }
}

func test() {
    let person = Person()
    let apartment = Apartment()
    
    person.apartment = apartment
    apartment.person = person
}
test()

上面的代码就存在循环引用,PersonApartment对象都无法销毁,我们只需要把其中任意一个引用变为弱引用或无助引用就可以了,例如:

class Apartment {
    weak var person: Person?
    
    deinit {
        print("Apartment对象销毁")
    }
}
2.2 闭包的循环引用

闭包表达式会对它内部访问的对象进行强引用,所以如果它内部用到的对象也对闭包表达式进行了强引用,这就会导致循环引用。(类似于OC的block

class Person {
    var age: Int = 11
    var fn: (() -> ())? // 属性,想要接收一个函数
    
    deinit {
        print("Person对象销毁")
    }
}

func test() {
    let person = Person()
    
    // fn又是person对象的一个属性,所以person对象也强引用着闭包表达式
    person.fn = { // 闭包表达式
        // 内部访问了person对象,所以会强引用person对象
        print(person.age)
    }
}
test()

上面的代码就存在循环引用,Person对象和闭包表达式都无法销毁,我们只需要在闭包表达式的捕获列表里用weakunowned声明一下是一个弱引用或者无主引用就可以了,例如:

func test() {
    let person = Person()
    
    person.fn = {
        [weak weakPerson = person] in // [...]为捕获列表
        print(weakPerson?.age) // 因为weak修饰的弱引用是可选项,所以得用可选链来访问
    }
}
test()

更简单的写法:

func test() {
    let person = Person()
    
    person.fn = {
        [weak person] in
        print(person?.age) // 因为weak修饰的弱引用是可选项,所以得用可选链来访问
    }
}
test()

推荐阅读更多精彩内容

  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 1,909评论 1 10
  • 1、范型范型所解决的问题 函数、方法、类型:类,结构体,枚举,元组类型,协议参数,返回值,成员函数参数,成员属性类...
    穿靴子的阿拉丁阅读 371评论 0 1
  • Swift属性 Swift属性将值跟特定的类,结构体,枚举关联。分为存储属性和计算属性,通常用于特定类型的实例。属...
    小小厨师阅读 450评论 0 0
  • 扩展 扩展就是向一个已有的类、结构体、枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类...
    cht005288阅读 175评论 0 0
  • 墙上的石英钟是会发声的,奇怪的是白天里却听不到,夜深人静的时候,听起来越发的分明。 个中原因很简单...
    盐城大银子阅读 47评论 0 0