Swift5.1-协议

345 阅读6分钟

协议(Protocol)

协议可以用来定义方法、属性、下标的声明, 协议可以被结构体、枚举、类遵守。(多个协议之间使用逗号,分隔)

protocol Drawable {
    func draw()				// 方法不能有默认参数
    var x: Int { set get }  // 可读写属性
    var y: Int { get }  	// 只读属性
    subscript(index: Int) -> Int { set get }  // 可读写的下标
}

protocol Test1 { }
protocol Test2 { }
protocol Test3 { }

class TestClass: Test1, Test2, Test3 { }   // 多个协议之间使用逗号,分隔

注意点:

  1. 协议中的方法不能有默认值
  2. 默认情况下,协议中的内容必须全部实现。(也有例外,如使用oc的option关键字)

协议中的属性

  • 协议中定义属性必须使用var关键字。(原因:协议中的只读属性,当使用计算属性实现时,值是有可能变化的)
  • 实现协议时属性权限要不小于协议中定义的属性权限。
    1. 协议中定义的get set 属性, 使用var存储属性或者get set的计算属性实现。
    2. 协议中定义的get 属性,用任何属性都可以实现。
protocol Drawable {
    func draw()
    var x: Int { set get }
    var y: Int { get }
    subscript(index: Int) -> Int { set get }
}

class Person1: Drawable {
    func draw() {
        print("Person draw")
    }
    var x: Int = 0   // 使用存储属性实现
    var y: Int = 0
    subscript(index: Int) -> Int {
        get { 10 }
        set {}
    }
}

class Person2: Drawable {
    func draw() {
        print("Person draw")
    }
    var x: Int {  // 使用计算属性实现
        set {}
        get { 10 }
    }
    var y: Int {
        set {}
        get { 10 }
    }
    subscript(index: Int) -> Int {
        get { 10 }
        set {}
    }
}

static、class

为了保证通用(照顾一下结构体和枚举),协议中必须使用static关键字定义类型方法、类型属性、类型下标。

protocol Drawable { 
    static func draw()  // 必须使用static,使用class会报错
}
class Person1: Drawable {
    class func draw() {  // 类实现协议中的类型方法时,可以使用class关键字
        print("Person1 draw")
    }
}
class Person2: Drawable {
    static func draw() {  // 类实现协议中的类型方法时,也可以使用static关键字
        print("Person2 draw")
    }
}
struct Person3: Drawable {
    static func draw() {   // 结构体实现协议中的类型方法时,只能使用static关键字
        print("Person3 draw")
    }
}

mutating

只有协议中的实例方法标记为mutating, 才允许结构体、枚举的具体实现修改自身的内存。类在实现时不需要加mutating,枚举、结构体才需要。

protocol Drawable {
    mutating func draw()
}
class Person1: Drawable {
    var age: Int = 0
    func draw() {
        self.age = 12   // 类不需要加mutating
        print("Person1 draw")
    }
}
struct Person2: Drawable {
    var weight: Int = 0
    mutating func draw() {
        self.weight = 22   //结构体中修改存储属性,方法需要加上mutating
        print("Person2 draw")
    }
}

init

  1. 协议中还可以定义初始化器init, 非final类实现时必须加上required。(final类不允许继承)
  2. 如果从协议实现的初始化器,刚好是重写了父类的初始化器,那么这个初始化器必须同时加上requiredoverride关键字。
protocol Drawable {
    init(x: Int, y: Int)
}
class Person: Drawable {
    required init(x: Int, y: Int) { }  // required 确保其所有子类都必须实现这个初始化器
}
final class Student: Drawable {  // final修饰的类不能被继承
    init(x: Int, y: Int) { }
}
protocol Liable {
    init(age: Int)
}
class Person {
    init(age: Int) {}
}
class Student: Person, Liable {
    required override init(age: Int) {  //required是实现协议来的,override是重写父类来的,都不能省略。
        super.init(age: age)
    }
}

init、 init?、init!

  1. 协议中定义的init?init!,可以使用init init? init!去实现
  2. 协议中定义的init,可以使用init init!去实现。
protocol Liable {
    init()
    init?(age: Int)
    init!(no: Int)
}

class Person: Liable {
    required init() {}
    // required init!() {}
    
    required init?(age: Int) {}
//    required init(age: Int) {}
//    required init!(age: Int) {}
    
    required init!(no: Int) {}
//    required init(no: Int) {}
//    required init?(no: Int) {}
}

协议的继承

一个协议可以继承其他协议

protocol Runnable {
    func run()
}
protocol Liable: Runnable {
    func breath()
}
class Person: Liable {
    func breath() {}
    func run() {}
}

协议组合

协议组合,可以包含一个类类型(最多一个)

protocol Runnable {}
protocol Liable{}
class Person {}

// 接受Person及其子类的实例
func fn0(obj: Person) {}
// 接受遵守Liable协议的实例
func fn1(obj: Liable) {}
// 接受同时遵守Liable和Runnable协议的实例
func fn2(obj: Liable & Runnable) {}
// 接受同时遵守Liable和Runnable协议,并且是Person或其子类的实例
func fn3(obj: Person & Liable & Runnable) {}

typealias RealPerson = Person & Liable & Runnable
// 接受同时遵守Liable和Runnable协议,并且是Person或其子类的实例
func fn4(obj: RealPerson) {}

CaseIterable

让枚举遵守CaseIterable协议,可以实现遍历枚举值。

enum Season: CaseIterable {
    case spring, summer, automn, winter
}

let seasons = Season.allCases
print(seasons.count)

for season in seasons {
    print(season)
}

CustomStringConvertible

遵守CustomStringConvertibleCustomDebugStringConvertible协议,都可以自定义实例的打印字符串。(类似OC中的description, Java中的toString)

class Person: CustomStringConvertible, CustomDebugStringConvertible {
    var age = 0
    var description: String {
        "person_\(age)"
    }
    var debugDescription: String {
        "debug_person_\(age)"
    }
}

let person = Person()
print(person)
debugPrint(person)
输出:
person_0
debug_person_0
  • print调用的是CustomStringConvertible协议中的description
  • debugPrint调用的是CustomDebugStringConvertible协议中的debugDescription

Any、 AnyObject

Swift 提供了两种特殊的类型:AnyAnyObject

  • Any: 可以代表任意类型(枚举、结构体、类、也包括函数类型)
  • AnyObject:可以代表任意类型(协议后面写上AnyObject代表只有类能遵守这个协议,当然在协议后面加上class 也代表只有类能遵守这个协议)
var stu: Any = 10  // 定义一个存储任意类型的变量
stu = "Long"
stu = Student()

// var data = Array<Any>()
var data = [Any]()  // 存放任意类型的数组
data.append(1)  // Int
data.append(3.14)  // Double
data.append(Student()) // 对象
data.append("long") // 字符串
data.append({10})  // 闭包表达式

is、as?、as!、as

is 用来判断是否为某种类型, as用来做强制类型转换的。

protocol Runnable {
    func run()
}
class Person {}
class Student: Person, Runnable {
    func run() {
        print("Student run")
    }
    func study() {
        print("Student study")
    }
}
var stu: Any = 10
print(stu is Int) // true
stu = "Long"
print(stu is String) // true
stu = Student()
print(stu is Student) // true
print(stu is Person)  // true
print(stu is Runnable) // true
var stu: Any = 10
(stu as? Student)?.study() // 没有调用study
stu = Student()
(stu as? Student)?.study() // Student study
(stu as! Student).study()  // Student study, 强制解包有风险,当stu不是Student类型时可能会崩溃
(stu as? Runnable)?.run() // Student run

as用于明确知道可以转的

var arr = [Any]()
arr.append(10 as Any)  // 这里可以使用as直接转

X.self、X.Type、AnyClass

  1. x.self 是一个元类型(metadata)的指针,metadata存放着类型相关的东西。
  2. x.selfx.Type类型
class Person {}
class Student: Person {}

var perType: Person.Type = Person.self
var stuType: Student.Type = Student.self
perType = stuType   // 父类指针指向子类对象

var anyType: AnyObject.Type = Person.self
anyType = Student.self

public typealias AnyClass = AnyObject.Type
var anyType2: AnyClass = Person.self
anyType2 = Student.self

var per = Person()
var perType = type(of: per)  // 获取实例的元类型
print(Person.self == type(of: per))  // true

元类型的应用

class Animal{ required init(){} }  // 确保子类都实现了init方法
class Cat: Animal {}
class Dog: Animal {}
class Pig: Animal {}

func create(_ clses: [Animal.Type]) -> [Animal] {
    var arr = [Animal]()
    for cls in clses {
        arr.append(cls.init())
    }
    return arr
}
print(create([Cat.self, Dog.self, Pig.self]))
class Person {
    var age: Int = 0
}
class Student: Person {
    var no: Int = 0
}

print(class_getInstanceSize(Student.self))  // 32
print(class_getSuperclass(Student.self)!)  // Person
print(class_getSuperclass(Person.self)!)   // Swift._SwiftObject

从结果可以看出,Swift还有一个隐藏的基类。Swift._SwiftObject 源码: github.com/apple/swift…

Self

Self代表当前类型。

class Person {
    var age = 1
    static var count = 2
    
    func run() {
        print(self.age)
        print(Self.count)
    }
}

Self一般用作返回值类型,限定返回值跟方法的调用者必须是同一类型(也可作为参数类型)。 和OC的InstanceType类似。

protocol Runnable {
    func test() -> Self
}
class Person: Runnable {
    required init() {}
    func test() -> Self {
        type(of: self).init()
    }
}
class Student: Person  {}

var per = Person()
print(per.test())  // Person

var stu = Student()
print(stu.test())  // Student

参考:

MJ Swift从门到精通课程

苹果开发者文档