美文网首页
Swift:面向对象(二)

Swift:面向对象(二)

作者: 意一ineyee | 来源:发表于2020-01-10 18:25 被阅读0次
一、继承(针对类)
二、多态(针对类)
三、协议(枚举、结构体、类都可以)
四、扩展(枚举、结构体、类都可以)
五、访问控制(枚举、结构体、类都可以)
六、内存管理(针对类)

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

  • 优先考虑使用协议,而不是父类(基类)。
  • 优先考虑值类型(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()

相关文章

  • swift问题小结

    1.swift是面向对象还是函数式编程语言 swift是函数式也是面向对象的语言。swift是面向对象语言,因为他...

  • Swift和OC的区别

    一、编程范式 Swift可以面向协议编程、面向函数编程、面向对象编程。 OC主要是面向对象编程。 二、类型安全 S...

  • Swift特性

    Swift是面向对象还是函数式的编程语言 Swift 既是面向对象的,又是函数式的编程语言。说 Swift 是面向...

  • iOS知识点-9.Swift 是面向对象还是函数式的编程语言?

    Swift Basics Swift 是面向对象还是函数式的编程语言? Swift既是面向对象的,又是函数式的编程...

  • Swift--Swift语言中的面向对象特性

    Swift语言中的面向对象类型 枚举 结构体 类 可选链 访问限定 Swift语言中的面向对象类型 面向对象概念的...

  • Swift:面向对象(二)

    一、继承(针对类)二、多态(针对类)三、协议(枚举、结构体、类都可以)四、扩展(枚举、结构体、类都可以)五、访问控...

  • Swift 是一门什么样的语言

    Swift 既是面向对象的,又是函数式的编程语言。 Swift是面向对象的语言。 Swift支持类的封装、继承和多...

  • Swift中类的定义

    Swift中类的定义 Swift也是一门面向对象开发的语言 面向对象的基础是类,类产生了对象 在Swift中如何定...

  • 每天学一点Swift----面向对象上(一)

    一. Swift的面向对象支持 1. Swift不仅提供来面向过程的支持,也提供了全面的面向对象支持。 2.与普通...

  • Swift学习之路

    第二周学习小结 概述: 本周开始系统的对Swift语法部分进行学习。Swift是面向对象的编程,在我的理解中,面向...

网友评论

      本文标题:Swift:面向对象(二)

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