Swift:面向对象(二)
一、继承(针对类)
二、多态(针对类)
三、协议(枚举、结构体、类都可以)
四、扩展(枚举、结构体、类都可以)
五、访问控制(枚举、结构体、类都可以)
六、内存管理(针对类)
大部分情况下,我们可以优先考虑使用(值类型 + 协议)的方式来代替直接使用引用类型,即面向协议编程占了主导,面向对象编程反而成了辅助。
- 优先考虑使用协议,而不是父类(基类)。
- 优先考虑值类型(
struct
、enum
),而不是引用类型(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 使用weak
和unowned
都可以解决循环引用
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()
上面的代码就存在循环引用,Person
和Apartment
对象都无法销毁,我们只需要把其中任意一个引用变为弱引用或无助引用就可以了,例如:
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
对象和闭包表达式都无法销毁,我们只需要在闭包表达式的捕获列表里用weak
或unowned
声明一下是一个弱引用或者无主引用就可以了,例如:
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()