swift 协议的使用方法和场景

协议是swift一个重要的部分,类似于Java中的接口,但是还不是很一样。相比较OC,swift中协议更加灵活,它可以应用在很多场景,使整个项目的框架结构更加易于延展。

一、什么场景下使用协议

协议与类类似,可以被继承,当继承某个协议之后就要给协议所定义的属性赋值并且实现协议中的方法。

既然协议与类这么类似,那我们为什么不全部用类来实现,为什么还要用到协议?
举个简单的例子,有一只猫和狗,他们都属于宠物,用类去实现就要这样操作,定义一个父类叫做宠物,里面有喂食和玩耍两个方法,猫和狗都继承与宠物这个父类。这样操作自然可以实现,但是要知道,猫和狗不都是宠物,这里把宠物定义成父类就不是很合适,这里应该把宠物定义成协议就相对合适很多啦

二、协议的使用方法

1、协议定义

// 协议定义通过关键字protocol
protocol SomeProtocol {
    // 协议定义
}

// 协议可以继承一个或者多个协议
protocol SomeProtocol2 :SomeProtocol {
    // 协议定义
}

// 结构体实现协议
struct SomeStructure : SomeProtocol,SomeProtocol2 {
    // 结构体定义
}

// 类实现协议和继承父类,协议一般都写在父类后面
class SomeSuperclass {
    // 父类定义
}

class SomeClass :SomeSuperclass,SomeProtocol,SomeProtocol2 {
    // 子类定义
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

2、协议的属性

协议不指定是否该属性应该是一个存储属性或者计算属性,它只指定所需的属性名称和读写类型。属性要求总是声明为变量属性,用var关键字做前缀。

protocol ClassProtocol {
    static var present:Bool { get set }    // 要求该属性可读可写,并且是静态的
    var subject :String { get }            // 要求该属性可读
    var stname :String { get set }         // 要求该属性可读可写
}

// 定义类来实现协议
class MyClass :ClassProtocol {
    static var present = false       // 如果没有实现协议的属性要求,会直接报错
    var subject = "Swift Protocols"  // 该属性设置为可读可写,也是满足协议要求的
    var stname = "Class"

    func attendance() -> String {
        return "The \(self.stname) has secured 99% attendance"
    }

    func markSScured() -> String {
        return "\(self.stname) has \(self.subject)"
    }
}

// 创建对象
var classa = MyClass()
print(classa.attendance())     // 结果:The Class has secured 99% attendance
print(classa.markSScured())    // 结果:Class has Swift Protocols
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3、协议普通方法实现

协议可以要求指定实例方法和类型方法被一致的类型实现。这些方法被写为协议定义的一部分,跟普通实例和类型方法完全一样,但是没有大括号或方法体。可变参数是允许的,普通方法也遵循同样的规则,不过不允许给协议方法参数指定默认值。

// 定义协议,指定方法要求
protocol RandomNumberGenerator {
    func random() -> Double    // 实现该协议,需要实现该方法
}

class LinearCongruentialGenerator :RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0

    // 实现协议方法
    func random() -> Double {
        lastRandom = ((lastRandom * a + c) % m)
        return lastRandom / m
    }
}

let generator = LinearCongruentialGenerator()
print("随机数:\(generator.random())")          //结果:随机数: 0.37464991998171
print("另一个随机数:\(generator.random())")     //结果:另一个随机数: 0.729023776863283
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4、协议中实现构造函数

协议SomeProtocol中不光可以声明方法/属性/下标,还可以声明构造器,但在Swift中,除了某些特殊情况外,构造器是不被子类继承的,所以SomeClass中虽然能够保证定义了协议要求的构造器,但不能保证SomeClass的子类中也定义了协议要求的构造器。所以我们需要在实现协议要求的构造器时,使用required关键字确保SomeClass的子类必须也得实现这个构造器。

protocol TcpProtocol {
    // 初始化构造器要求
    init(aprot :Int)
}

class TcpClass :TcpProtocol {
    var aprot: Int
    // 实现协议的初始化要求时,必须使用required关键字确保子类必须也得实现这个构造器
    required init(aprot: Int) {
        self.aprot = aprot
    }
}

var tcp = TcpClass(aprot: 20)
print(tcp.aprot)   // return:20
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

三、 使用实例

开头说的宠物猫和宠物狗的例子,利用协议可以这样实现,声名个动物的父类,然后让猫和狗class都继承与动物class。在定义一个宠物的属性,里面有玩耍和喂食两个方法,让猫和狗都继承与宠物协议,实现代码如下:

protocol Pet {
    func playWith()
    func fed(food : String)

}

class Animal{


    var name : String = ""
    var birthPlace : String = ""

    init(name: String,birthPlace:String) {

        self.name = name
        self.birthPlace = birthPlace
    }
}


class Dog: Animal, Pet{

    func playWith() {

        print("狗狗在玩")
    }

    func fed(food: String) {

        if food == "骨头" {

            print("狗狗Happy")
        }
        else {

            print("狗狗Sad")
        }
    }


}

class Cat: Animal, Pet {

    func playWith() {


        print("猫猫在玩")
    }

    func fed(food: String) {

        if food == "鱼" {

            print("猫猫Happy")
        }
        else {

            print("猫猫Sad")
        }
    }
}


let dog = Dog(name:"狗狗小黑", birthPlace: "北京")

dog.playWith()

dog.fed(food:"骨头")

let cat = Cat(name:"猫猫小白", birthPlace:"上海")

cat.playWith()

cat.fed(food: "鱼")
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

注意:同时继承父类和协议的时候,父类要写在前面

四、typealias与协议结合的使用

typealias 的作用是给类型进行扩展,它与协议放在一起使用会碰撞出不一样的火花

1、typealias的基本使用

extension Double {

    var km : Length{ return self * 1000.0 }

    var m : Length{ return self }

    var cm : Length{ return self / 100 }


}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里对Double类型进行扩展

let runDistance:Length = 3.14.km //3140
    
    
  • 1

2、typealias结合协议使用

定义一个协议,代表重量,但是它的类型要根据继承与它的类或结构体来定义,协议代码如下:

protocol WeightCalculable {

    associatedtype WeightType

    var weight:WeightType{get}

}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里weight属性的类型就抛了出来,便于继承协议的类或结构体来定义

class iPhone7 : WeightCalculable {

    typealias WeightType = Double

    var weight: WeightType {

        return 0.114
    }

}

class Ship : WeightCalculable {

    typealias WeightType = Int

    let weight: WeightType

    init(weight: Int) {

        self.weight = weight
    }


}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

这里定义了两个类,一个是iPhone7,一个是Ship,都继承于协议WeightCalculable,但是weight的类型大不相同。
iPhone7的weight属性是Double类型的,Ship的weight属性是Int类型的。

extension Int {

    typealias Weight = Int
    var t:Weight  {
        return 1_000*self
    }


}

let ship = Ship(weight:4_637.t)
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

最后这段代码,用于扩充Int类型,自定义了t字段来代表吨

五、系统协议的使用

我们还可以继承于系统协议来定义系统方法,这里简单极少介绍三种常用系统协议

1、Equatable协议用于自定义”==”来实现操作

class Person:Equatable , Comparable, CustomStringConvertible {

    var name:String
    var age:Int

    init(name:String,age:Int) {

        self.name = name
        self.age = age
    }

    var description: String {

        return"name: "+name + ",age:" + String(age)
    }

}

func == (left: Person, right: Person) ->Bool{

    return left.name == right.name && left.age == right.age

}

let personA = Person(name:"a",age:9)

let personB = Person(name:"a",age:10)

personA == personB

personA != personB
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

注意:func == 方法要紧跟协议下面写,否则编译器会报错

2、Comparable协议用于自定义比较符号来使用的

func <(left: Person, right: Person) ->Bool{


        return left.age < right.age

}

let personA = Person(name:"a",age:9)

let personB = Person(name:"a",age:10)
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意,在定义比较符号后,很多方法也会同时修改便于我们使用,例如排序方法

let person1 = Person(name:"a",age:9)

let person2 = Person(name:"a",age:12)

let person3 = Person(name:"a",age:11)

var  arrPerson = [person1,person2,person3]

arrPerson .sort()

//此时arrPerson : [person1,person3,person2]
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3、CustomStringConvertible协议用于自定义打印

class Person:Equatable , Comparable, CustomStringConvertible {

    var name:String
    var age:Int

    init(name:String,age:Int) {

        self.name = name
        self.age = age
    }

    var description: String {

        return"name: "+name + ",age:" + String(age)
    }

}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

重写description 讲自定义打印格式return出来

print(person1)

//name: a,age:9
    
    
  • 1
  • 2
  • 3

协议是swift非常重要的一部分,苹果甚至为了它单独出来——–面向协议编程,利用协议的优点和灵活性可以使整个项目结构更加灵活,拥有更加易于延展的架构。

                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/production/markdown_views-68a8aad09e.css">
                        </div>

协议是swift一个重要的部分,类似于Java中的接口,但是还不是很一样。相比较OC,swift中协议更加灵活,它可以应用在很多场景,使整个项目的框架结构更加易于延展。

一、什么场景下使用协议

协议与类类似,可以被继承,当继承某个协议之后就要给协议所定义的属性赋值并且实现协议中的方法。

既然协议与类这么类似,那我们为什么不全部用类来实现,为什么还要用到协议?
举个简单的例子,有一只猫和狗,他们都属于宠物,用类去实现就要这样操作,定义一个父类叫做宠物,里面有喂食和玩耍两个方法,猫和狗都继承与宠物这个父类。这样操作自然可以实现,但是要知道,猫和狗不都是宠物,这里把宠物定义成父类就不是很合适,这里应该把宠物定义成协议就相对合适很多啦

二、协议的使用方法

1、协议定义

// 协议定义通过关键字protocol
protocol SomeProtocol {
    // 协议定义
}

// 协议可以继承一个或者多个协议
protocol SomeProtocol2 :SomeProtocol {
    // 协议定义
}

// 结构体实现协议
struct SomeStructure : SomeProtocol,SomeProtocol2 {
    // 结构体定义
}

// 类实现协议和继承父类,协议一般都写在父类后面
class SomeSuperclass {
    // 父类定义
}

class SomeClass :SomeSuperclass,SomeProtocol,SomeProtocol2 {
    // 子类定义
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

2、协议的属性

协议不指定是否该属性应该是一个存储属性或者计算属性,它只指定所需的属性名称和读写类型。属性要求总是声明为变量属性,用var关键字做前缀。

protocol ClassProtocol {
    static var present:Bool { get set }    // 要求该属性可读可写,并且是静态的
    var subject :String { get }            // 要求该属性可读
    var stname :String { get set }         // 要求该属性可读可写
}

// 定义类来实现协议
class MyClass :ClassProtocol {
    static var present = false       // 如果没有实现协议的属性要求,会直接报错
    var subject = "Swift Protocols"  // 该属性设置为可读可写,也是满足协议要求的
    var stname = "Class"

    func attendance() -> String {
        return "The \(self.stname) has secured 99% attendance"
    }

    func markSScured() -> String {
        return "\(self.stname) has \(self.subject)"
    }
}

// 创建对象
var classa = MyClass()
print(classa.attendance())     // 结果:The Class has secured 99% attendance
print(classa.markSScured())    // 结果:Class has Swift Protocols
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

3、协议普通方法实现

协议可以要求指定实例方法和类型方法被一致的类型实现。这些方法被写为协议定义的一部分,跟普通实例和类型方法完全一样,但是没有大括号或方法体。可变参数是允许的,普通方法也遵循同样的规则,不过不允许给协议方法参数指定默认值。

// 定义协议,指定方法要求
protocol RandomNumberGenerator {
    func random() -> Double    // 实现该协议,需要实现该方法
}

class LinearCongruentialGenerator :RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0

    // 实现协议方法
    func random() -> Double {
        lastRandom = ((lastRandom * a + c) % m)
        return lastRandom / m
    }
}

let generator = LinearCongruentialGenerator()
print("随机数:\(generator.random())")          //结果:随机数: 0.37464991998171
print("另一个随机数:\(generator.random())")     //结果:另一个随机数: 0.729023776863283
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4、协议中实现构造函数

协议SomeProtocol中不光可以声明方法/属性/下标,还可以声明构造器,但在Swift中,除了某些特殊情况外,构造器是不被子类继承的,所以SomeClass中虽然能够保证定义了协议要求的构造器,但不能保证SomeClass的子类中也定义了协议要求的构造器。所以我们需要在实现协议要求的构造器时,使用required关键字确保SomeClass的子类必须也得实现这个构造器。

protocol TcpProtocol {
    // 初始化构造器要求
    init(aprot :Int)
}

class TcpClass :TcpProtocol {
    var aprot: Int
    // 实现协议的初始化要求时,必须使用required关键字确保子类必须也得实现这个构造器
    required init(aprot: Int) {
        self.aprot = aprot
    }
}

var tcp = TcpClass(aprot: 20)
print(tcp.aprot)   // return:20
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

三、 使用实例

开头说的宠物猫和宠物狗的例子,利用协议可以这样实现,声名个动物的父类,然后让猫和狗class都继承与动物class。在定义一个宠物的属性,里面有玩耍和喂食两个方法,让猫和狗都继承与宠物协议,实现代码如下:

protocol Pet {
    func playWith()
    func fed(food : String)

}

class Animal{


    var name : String = ""
    var birthPlace : String = ""

    init(name: String,birthPlace:String) {

        self.name = name
        self.birthPlace = birthPlace
    }
}


class Dog: Animal, Pet{

    func playWith() {

        print("狗狗在玩")
    }

    func fed(food: String) {

        if food == "骨头" {

            print("狗狗Happy")
        }
        else {

            print("狗狗Sad")
        }
    }


}

class Cat: Animal, Pet {

    func playWith() {


        print("猫猫在玩")
    }

    func fed(food: String) {

        if food == "鱼" {

            print("猫猫Happy")
        }
        else {

            print("猫猫Sad")
        }
    }
}


let dog = Dog(name:"狗狗小黑", birthPlace: "北京")

dog.playWith()

dog.fed(food:"骨头")

let cat = Cat(name:"猫猫小白", birthPlace:"上海")

cat.playWith()

cat.fed(food: "鱼")
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

注意:同时继承父类和协议的时候,父类要写在前面

四、typealias与协议结合的使用

typealias 的作用是给类型进行扩展,它与协议放在一起使用会碰撞出不一样的火花

1、typealias的基本使用

extension Double {

    var km : Length{ return self * 1000.0 }

    var m : Length{ return self }

    var cm : Length{ return self / 100 }


}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里对Double类型进行扩展

let runDistance:Length = 3.14.km //3140
  
  
  • 1

2、typealias结合协议使用

定义一个协议,代表重量,但是它的类型要根据继承与它的类或结构体来定义,协议代码如下:

protocol WeightCalculable {

    associatedtype WeightType

    var weight:WeightType{get}

}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这里weight属性的类型就抛了出来,便于继承协议的类或结构体来定义

class iPhone7 : WeightCalculable {

    typealias WeightType = Double

    var weight: WeightType {

        return 0.114
    }

}

class Ship : WeightCalculable {

    typealias WeightType = Int

    let weight: WeightType

    init(weight: Int) {

        self.weight = weight
    }


}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

这里定义了两个类,一个是iPhone7,一个是Ship,都继承于协议WeightCalculable,但是weight的类型大不相同。
iPhone7的weight属性是Double类型的,Ship的weight属性是Int类型的。

extension Int {

    typealias Weight = Int
    var t:Weight  {
        return 1_000*self
    }


}

let ship = Ship(weight:4_637.t)
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

最后这段代码,用于扩充Int类型,自定义了t字段来代表吨

五、系统协议的使用

我们还可以继承于系统协议来定义系统方法,这里简单极少介绍三种常用系统协议

1、Equatable协议用于自定义”==”来实现操作

class Person:Equatable , Comparable, CustomStringConvertible {

    var name:String
    var age:Int

    init(name:String,age:Int) {

        self.name = name
        self.age = age
    }

    var description: String {

        return"name: "+name + ",age:" + String(age)
    }

}

func == (left: Person, right: Person) ->Bool{

    return left.name == right.name && left.age == right.age

}

let personA = Person(name:"a",age:9)

let personB = Person(name:"a",age:10)

personA == personB

personA != personB
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

注意:func == 方法要紧跟协议下面写,否则编译器会报错

2、Comparable协议用于自定义比较符号来使用的

func <(left: Person, right: Person) ->Bool{


        return left.age < right.age

}

let personA = Person(name:"a",age:9)

let personB = Person(name:"a",age:10)
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意,在定义比较符号后,很多方法也会同时修改便于我们使用,例如排序方法

let person1 = Person(name:"a",age:9)

let person2 = Person(name:"a",age:12)

let person3 = Person(name:"a",age:11)

var  arrPerson = [person1,person2,person3]

arrPerson .sort()

//此时arrPerson : [person1,person3,person2]
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3、CustomStringConvertible协议用于自定义打印

class Person:Equatable , Comparable, CustomStringConvertible {

    var name:String
    var age:Int

    init(name:String,age:Int) {

        self.name = name
        self.age = age
    }

    var description: String {

        return"name: "+name + ",age:" + String(age)
    }

}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

重写description 讲自定义打印格式return出来

print(person1)

//name: a,age:9
  
  
  • 1
  • 2
  • 3

协议是swift非常重要的一部分,苹果甚至为了它单独出来——–面向协议编程,利用协议的优点和灵活性可以使整个项目结构更加灵活,拥有更加易于延展的架构。

                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/production/markdown_views-68a8aad09e.css">
                        </div>

猜你喜欢

转载自blog.csdn.net/speverriver/article/details/79755779