闭包
首先来看一段示例:
代码语言:javascript复制//计算一个数的平方
//函数写法
func square(a : Int) -> Int {
return a * a
}
square(a: 6)
//闭包写法
let sq = {
(a : Int) -> Int in
return a * a
}
sq(6)
闭包的写法:
代码语言:javascript复制
let 闭包名 = {
函数类型 in
需要执行的代码块
}
闭包能够捕获和存储定义在其上下文中的任何常量和变量,即闭合并包裹那些常量和变量,因此被称为“闭包”。
闭包是可以被传递和引用的一个独立模块。
闭包跟函数一样,也是引用类型。
闭包表达式
闭包表达式的语法有如下的一般形式:
代码语言:javascript复制
{
(参数罗列) -> (返回值类型) in
需要执行的代码语句
}
说明如下:
1,闭包表达式由一对花括号 {} 开始与结束
2,由 in 关键字将闭包分割成两部分:参数与返回值(可以理解成函数的类型)与闭包体
3,闭包中的参数不同于函数中的参数的一点是,闭包中的参数不能提供默认值,而函数中的参数可以提供默认值。其他都是一样的。
闭包的简写
首先我们来看一个例子,从一个数组中筛选出合适的数据组成新的数组:
代码语言:javascript复制//首先定义一个函数,函数有两个参数,第一个参数是需要筛选的数组,第二个参数是筛选条件函数
func getNewList(scores : [Int], checkMethod : (Int)->Bool) -> [Int] {
var newList = [Int]()
for num in scores {
if checkMethod(num) {
newList.append(num)
}
}
return newList
}
//函数是一种特殊的闭包,所以上面的筛选条件函数可以写成如下闭包形式
let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {(numInt : Int) -> Bool in return numInt > 80})
print(newList) // [86, 98, 100]
代码语言:javascript复制let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {(numInt : Int) -> Bool in return numInt > 80})
上面的这行代码,是闭包的完整写法。下面来看一些简写的写法。
第一层简写:省略掉闭包类型中的 -> 与 返回值类型 ,因为根据后面的表达式可以推断出返回值是一个Bool
代码语言:javascript复制let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {(numInt : Int) in return numInt > 80})
第二层简写:省略掉参数的类型和小括号,因为参数的类型可以根据定义闭包时的函数类型进行推断
代码语言:javascript复制let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {numInt in return numInt > 80})
第三层简写:省略掉 return 关键字,在单行闭包的时候,return关键字可以省略
代码语言:javascript复制let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {numInt in numInt > 80})
第四层简写:省略掉参数的声明和 in 关键字,使用参数名称缩写$0
代码语言:javascript复制let newList = getNewList(scores: [55, 60, 71, 86, 98, 100], checkMethod: {$0 > 80})
Swift 提供了参数名称的缩写功能,直接通过$0、$1、$2来顺序调用闭包的参数
最常见的闭包类型——尾随闭包
尾随闭包是最常见的一种闭包类型。
尾随闭包是一个在函数调用的时候,书写在函数括号之后的闭包表达式。当函数中最后一个参数是闭包表达式的时候,在调用该函数的时候,就可以将作为最后一个参数的闭包表达式写成尾随闭包。
func getNewList(scores : [Int], checkMethod : (Int)->Bool) -> [Int] { var newScoreList = [Int]() for score in scores { if checkMethod(score) { newScoreList.append(score) } } return newScoreList}
//调用函数的时候,不使用尾随闭包getNewList(scores: [41,62,83], checkMethod: { (score : Int) -> Bool in return score > 60})
//调用函数的时候,使用尾随闭包getNewList(scores: [41, 62, 83]) { (score) -> Bool in return score > 60}
枚举
枚举的定义
在C和OC中,枚举成员在被创建的时候会被赋予一个默认的整数值,枚举的本质就是一组整型值。
而在Swift中,枚举是更加灵活的,第一,你不必给每一个枚举成员提供一个值;第二,如果需要给枚举成员提供值,那么可以提供的值类型包括字符、字符串、整型值、浮点值等。
定义方式一:
代码语言:javascript复制enum CompassPoint { case East case West case North case South}
这里的 case 关键词表明新的一行成员值将被定义。
与C/OC不同的一点是,Swift的枚举成员在被创建的时候不会被赋予一个默认的整数值。比如上面的这个例子中,East、West、North和South不是隐式的等于0、1、2、3。
定义方式二:
代码语言:javascript复制enum CompassPoint { case East, West, North, South}
枚举的多个成员值可以出现在同一行上,此时只需要只用一个case 关键词即可。
枚举的赋值
枚举类型赋值可以是字符、字符串、整型、浮点型。
如果要给枚举类型赋值,则必须要在枚举类型后面明确说明值的具体类型:
代码语言:javascript复制enum CompassPoint : Int{ case East = 1 case West = 2 case North = 3 case South = 4}
enum CompassPoint : Double{ case East = 1.0 case West = 2.0 case North = 3.0 case South = 4.0}
enum CompassPoint : String{ case East = "East" case West = "West" case North = "North" case South = "South"}
枚举的类型推断
首先给变量赋一个枚举值:
代码语言:javascript复制enum CompassPoint : String{ case East = "East" case West = "West" case North = "North" case South = "South"}
var a = CompassPoint.Eastprint(a) // East
然后对该变量进行穷举:
代码语言:javascript复制switch a {case CompassPoint.East: print("东")case CompassPoint.West: print("西")case CompassPoint.North: print("北")case CompassPoint.South: print("南")}
上面的这个穷举可以简写为如下(将枚举类型移除):
代码语言:javascript复制switch a {case .East: print("东")case .West: print("西")case .North: print("北")case .South: print("南")}
之所以可以将枚举类型给简化掉,是因为根据上下文,系统可以检测到变量a 匹配的值是 CompassPoint 这个枚举类型下面的值。这就是Swift中的枚举类型推断。
枚举的原始值
在C/OC中,枚举的本质就是整数。所以C/OC的枚举是有原始值的,并且默认是从0开始。
Swift中的枚举是没有原始值的,但是可以在定义的时候告诉系统让枚举有原始值。
关于设置Swift中枚举的原始值,需要注意以下几点:
- 如果原始值是String类型,则原始值是区分大小写的
- 通过 rawValue 可以获取原始值
- 通过 rawValue 返回的枚举是一个可选型,因为原始值对应的枚举值不一定存在
- 如果想指定第一个元素的原始值之后,后面的元素的原始值能够默认 1,则枚举一定是Int类型。
enum CompassPoint : Int { case East = 1 case West case North case South}
let a = CompassPoint.Northprint(a) // Northprint(a.rawValue) // 3
这个例子中,枚举的原始值设置的是Int类型,并且设置了第一个枚举值的原始值是1,所以North的原始值就是3。
代码语言:javascript复制enum CompassPoint : Int { case East = 1 case West case North case South}
//通过原始值来获取对应的枚举值//这里获取的枚举值是一个可选型,因为原始值对应的枚举值不一定存在let b = CompassPoint(rawValue: 4)let c = CompassPoint(rawValue: 5)
结构体
结构体(struct)是由一系列具有相同类型或者不同类型的数据构成的数据集合。
结构体既可以定义属性(变量、常量),也可以定义方法(函数)。
Swift中的结构体是值类型。
结构体的定义语法
代码语言:javascript复制struct 结构体名称 { // 属性和方法}
举例如下:
代码语言:javascript复制struct Person { var name = "norman" var age = 18 var gentle = "man" func singASong() { print("《海阔天空》") }}
该例中,定义了一个名叫Person的结构体。这个结构体拥有三个属性(name、age、gentle)和一个方法(singASong)。
结构体实例
实例化结构体的方式一:
实例化结构体最简单的方式就是在结构体名字后面加上(),此时,任何属性都被初始化为默认值。
代码语言:javascript复制struct Person { var name = "norman" var age = 18 var gentle = "man" func singASong() { print("《海阔天空》") }}
let person1 = Person()print("我叫(person1.name),我今年(person1.age)岁,性别(person1.gentle)")//打印结果:我叫norman,我今年18岁,性别man
实例化结构体的方式二:
所有的结构体都会有一个自动生成的成员构造函数来实例化结构体,可以使用它来初始化所有的成员属性。
代码语言:javascript复制struct Person { var name = "norman" var age = 18 var gentle = "man" func singASong() { print("《海阔天空》") }}
let person2 = Person(name: "Lavie", age: 20, gentle: "male")print("我叫(person2.name),我今年(person2.age)岁,性别(person2.gentle)")//打印结果:我叫Lavie,我今年20岁,性别male
访问结构体实例的属性和方法
我们可以使用点语法来访问一个结构体实例的属性和方法。如下:
代码语言:javascript复制struct Person { var name = "norman" var age = 18 var gentle = "man" func singASong() { print("《海阔天空》") }}
let person1 = Person()print("我叫(person1.name),我今年(person1.age)岁,性别(person1.gentle)") // 我叫norman,我今年18岁,性别manperson1.singASong() // 《海阔天空》
如果点语法是在等号后面,或者没有等号,那么就是访问;如果点语法在等号前面,就是赋值。如下:
代码语言:javascript复制struct Person { var name = "norman" var age = 18 var gentle = "man" func singASong() { print("《海阔天空》") }}
var person1 = Person()print("我叫(person1.name)") // 我叫normanperson1.name = "lavie" // 赋值print("我叫(person1.name)") // 我叫lavieperson1.singASong() // 《海阔天空》
结构体是值类型
值类型是一种当它被赋值给一个常量或者变量,或者被传递给函数时,会被拷贝的类型。
Swift中的枚举、结构体等都是值类型,它在代码传递中总是会被拷贝。
代码语言:javascript复制struct Person { var name = "norman"}
var person1 = Person()// 值类型拷贝var person2 = person1
//此时改变person2,并不会改变person1的值person2.name = "lavie"
print(person1.name) // normanprint(person2.name) // lavie
Swift中的字符串String、字典Dictionary、数组Array类型,是作为结构体来实现的,这意味着,当String、Dictionary、Array类型的实例被赋值到一个新的常量或者变量,或被传递到一个函数中的时候,其实传递的是拷贝后的值。
OC中的NSString、NSArray和NSDictionary,他们是作为类来实现的,所以NSString、NSArray和NSDictionary的实例对象总是作为一个引用,而不是拷贝来实现赋值和传递。
类
Swift虽然推荐是面向协议编程,但其也是一门面向对象的语言。
面向对象的语言中很重要的两个概念是:类和对象。对象是类的实例。
Swift中用class关键字来定义类。
定义语法
代码语言:javascript复制class 类名 { // 定义属性和方法}
举例:
代码语言:javascript复制class Person {
var name = "norman"
var age = 20
func play() {
print("play")
}
}
该例中定义了一个名为Person的类,该类有两个属性和一个方法。
类的实例
类的实例化与结构体的实例化一样,最简单的就是在名称后面加一个小括号(),但是类默认没有成员构造函数。
代码语言:javascript复制var person = Person()
访问属性和方法
类属性方法的访问和结构体的属性方法的访问是一模一样的。
我们可以使用点语法来访问一个类的实例的属性和方法。如下:
代码语言:javascript复制class Person { var name = "norman" var age = 18 var gentle = "man" func singASong() { print("《海阔天空》") }}
let person1 = Person()print("我叫(person1.name),我今年(person1.age)岁,性别(person1.gentle)") // 我叫norman,我今年18岁,性别manperson1.singASong() // 《海阔天空》
如果点语法是在等号后面,或者没有等号,那么就是访问;如果点语法在等号前面,就是赋值。如下:
代码语言:javascript复制class Person { var name = "norman" var age = 18 var gentle = "man" func singASong() { print("《海阔天空》") }}
var person1 = Person()print("我叫(person1.name)") // 我叫normanperson1.name = "lavie" // 赋值print("我叫(person1.name)") // 我叫lavieperson1.singASong() // 《海阔天空》
类是引用类型
与值类型不同,引用类型被赋值到一个常量或者变量,或者被传递到一个函数中的时候,它是不会被拷贝的,而是使用的同一个对某实例的引用。
代码语言:javascript复制class Person { var name = "norman"}
var person1 = Person()// 引用类型var person2 = person1
//此时改变person2,会影响person1的值,因为它俩引用的是同一个对象person2.name = "lavie"
print(person1.name) // lavieprint(person2.name) // lavie
特征运算符
由于类是引用类型,可能有很多常量或者变量都是引用到了同一个类的实例。有时候需要找出两个常量或者变量是否引用自同一个实例,Swift中提供了两个特征运算符来检查两个常量或者变量是否引用自相同的实例。
- === 引用自类的同一个实例
- !== 没有引用自类的同一个实例
class Person { var name = "norman"}
var person1 = Person()var person2 = person1var person3 = Person()
// 特征运算符print(person1 === person2) // trueprint(person2 === person3) // false
继承
一个类是可以从另外一个类那继承方法、属性和其他的特性的。
当一个类从另外一个类那里继承的时候,继承的类就是子类,被继承的类就是父类。
继承的目的是为了代码复用。
代码语言:javascript复制//父类class Person { var name = "norman" func eat() { print("eat") }}
//子类class Child : Person { var school = "杭州市第一小学"}
//子类class Adult : Person { var company = "杭州魔筷"}
var person = Person()var child = Child()var adult = Adult()
//Child和Adult都继承了其父类Person的方法person.eat()child.eat()adult.eat()
重写(覆写)
所谓重写,就是子类可以对继承自父类的实例方法、类型方法、实例属性、类型属性进行覆盖。
重写需要在前面加上override关键字。
override关键字执行的时候,Swift编译器会检查重写的类的父类(或者父类的父类)是否有与之匹配的声明来提供重写。
代码语言:javascript复制//父类class Person { var name = "norman" func eat() { print("eat") }}
//子类class Child : Person { var school = "杭州市第一小学" //重写父类的方法 override func eat() { print("child eat") }}
//子类class Adult : Person { var company = "杭州魔筷" //重写父类的方法 override func eat() { print("adult eat") }}
var person = Person()var child = Child()var adult = Adult()
person.eat() // eatchild.eat() // child eatadult.eat() // adult eat
如果不想父类的属性或者方法被重写,那么可以通过final关键字来阻止子类的重写行为:
代码语言:javascript复制//父类
class Person {
var name = "norman"
//通过final关键字阻止被重写
final func eat() {
print("eat")
}
}
//子类
class Child : Person {
var school = "杭州市第一小学"
//重写父类的方法
// 此时会报错error: instance method overrides a 'final' instance method
override func eat() {
print("child eat")
}
}
类与结构体都可以用来定义自定义的数据类型,结构体实例总是通过值来传递,而类实例总是通过引用来传递。
类的属性介绍
在Swift的类中,属性分为如下三种:
- 存储属性:用于存储类的实例的常量和变量
- 计算属性:通过某种方式计算出来的属性
- 类属性:与整个类自身相关的属性
一、存储属性
存储属性是最简单的属性,它作为类实例的一部分,用于存储常量和变量。
我们可以给存储属性提供一个默认值,也可以在初始化方法中对其进行初始化。
代码语言:javascript复制class Student { var name : String? var age : Int = 19 var chineseScore : Double = 0.0 var mathScore : Double = 0.0}
//创建实例对象var student = Student()
//给存储属性赋值student.name = "norman"student.age = 18
student.chineseScore = 98.0student.mathScore = 96.0
本例中的name和age都是存储属性,用来记录该学生的姓名和年龄。
chineseScore和mathScore也是存储属性,用来记录该学生的语文分数和数学分数。
二、计算属性
计算属性并不存储实际的值,而是提供一个getter和一个可选的setter来间接获取和设置其属性值。
计算属性一般只提供getter方法,不提供setter方法。
如果只提供getter方法,而不提供setter方法的话,则该计算属性为只读属性,并且可以省略掉get{}。
代码语言:javascript复制class Student { //存储属性 var name : String? var age : Int = 19 var chineseScore : Double = 0.0 var mathScore : Double = 0.0 //计算属性 var averageScore : Double { get { return (chineseScore mathScore)/2 } } }
//创建实例对象var student = Student()
//给存储属性赋值student.name = "norman"student.age = 18
student.chineseScore = 98.0student.mathScore = 96.0
//获取计算属性student.averageScore // 97
本例中,averageScore是计算属性,它的值是由chineseScore和mathScore计算得来。
需要注意的是,averageScore的定义如下:
代码语言:javascript复制//计算属性
var averageScore : Double {
get {
return (chineseScore mathScore)/2
}
}
可以简化为:
代码语言:javascript复制//计算属性
var averageScore : Double {
return (chineseScore mathScore)/2
}
三、类属性
类属性是与类相关联的,而不是与类的实例相关联。
所有的类和实例都共有一份类属性,因此在某一处修改之后,该类属性就会被修改。
类属性的设置和修改需要通过类来完成。
下面是类属性的写法:
代码语言:javascript复制class Student { //存储属性 var name : String? var age : Int = 19 var chineseScore : Double = 0.0 var mathScore : Double = 0.0 //计算属性 var averageScore : Double { return (chineseScore mathScore)/2 } //类属性 static var courseCount : Int = 3}
//创建实例对象var student = Student()
//给存储属性赋值student.name = "norman"student.age = 18
student.chineseScore = 98.0student.mathScore = 96.0
//获取计算属性student.averageScore // 97
//类属性的设值与取值Student.courseCount = 6print(Student.courseCount) // 6
四、懒加载属性
懒加载属性是指第一次被调用的时候才会计算其初始值的属性,也就是说,懒加载的对象在第一次使用的时候才会真正被加载到内存中。
在OC中,我们通过gettter方法来实现懒加载。
但是在Swift中,我们是在属性的声明前使用lazy关键字来表示该属性是延迟加载(即懒加载)的。
代码语言:javascript复制class Student { //懒加载属性 //在第一次使用到该属性的时候,执行闭包,将闭包的返回值赋值给属性 lazy var terchers: [String] = { () -> [String] in return ["tercher1", "tercher2", "tercher3"] }()}
//创建实例对象var student = Student()
print(student.terchers) // ["tercher1", "tercher2", "tercher3"]
协议
协议的定义
协议的定义方式与类、结构体、枚举的定义方式非常相似:
代码语言:javascript复制
protocol SomeProtocol {
//属性
//方法
}
协议中的属性需要遵循以下几点:
- 必须设置为变量var
- 不可以有默认值
- 必须设置是{get}还是{get set},{get}表示只读,{get set}表示可读可写
协议中的方法需要注意以下几点:
- 方法不能有方法体
- 方法中的参数不能有默认值
protocol SomeProtocol { //属性 var name : String {get set} //方法 func play(company : String) mutating func eat()}
协议的遵循
遵循协议的写法跟继承父类的写法其实是一样的:
代码语言:javascript复制
//协议
protocol SomeProtocol {
//属性
var name : String {get set}
//方法
func play(company : String)
mutating func eat()
}
//父类
class Person {
var age : Int = 18
}
//遵循协议,继承父类
class Student : Person, SomeProtocol {
//实现协议中的属性
var name: String = ""
//实现协议中的方法
func play(company: String) {
print("play with (company)")
}
func eat() {
print("to dine")
}
}
协议的继承
代码语言:javascript复制
//父协议
protocol ParentProtocol {
//属性
var name : String {get set}
//方法
func play(company : String)
mutating func eat()
}
//子协议
protocol SonProtocol : ParentProtocol {
func study()
}
//遵循协议
class Student : SonProtocol {
//实现父协议中的内容
var name: String = ""
func play(company: String) {
print("play with (company)")
}
func eat() {
print("to dine")
}
//实现子协议中的内容
func study() {
print("study")
}
}
协议中方法的可选
1,optional关键字
代码语言:javascript复制
@objc protocol ParentProtocol {
func play(company : String)
//该方法可选
@objc optional func eat()
}
//遵循协议
class Student : ParentProtocol {
//实现协议中的内容
//此时可以不实现协议中的可选方法,当然也可以实现
func play(company: String) {
print("play with (company)")
}
}
2,扩展协议
代码语言:javascript复制protocol ParentProtocol {
func play(company : String)
func eat()
}
extension ParentProtocol {
//可以在协议的拓展中对协议中的方法进行默认的实现
func eat() {
print("eat")
}
}
//遵循协议
class Student : ParentProtocol {
//实现协议中的内容
//如果协议中的方法已经在拓展中默认实现了,这里可以实现也可以不实现
func play(company: String) {
print("play with (company)")
}
}
我们可以利用协议的扩展,对协议中的一些方法进行默认实现。如果在协议的扩展中对某些方法进行了实现,那么在遵循协议的类里面,可以不实现已经有了默认实现的方法。
协议的运用——代理模式
代码语言:javascript复制//第1步,定义一个协议,规定需要完成的任务protocol BuyTicketProtocol { func buyTicket()}
//第2步,让具体的类或者结构体来实现协议,将任务具体化class Assistant : BuyTicketProtocol { func buyTicket() { print("秘书去买票") }}
class Scalpers : BuyTicketProtocol { func buyTicket() { print("黄牛去买票") }}
//第3步,委托方的一个配置class Person { //3.1,首先设置代理属性 var delegate : BuyTicketProtocol? func goToShanghai() { print("准备去上海,找个人去买票") //3.2,在需要完成任务的时候通过代理来进行操作 delegate?.buyTicket() print("买到票了,准备出发") }}
//第4步,设置委托方的代理var person = Person()person.delegate = Scalpers() // 设置委托方的代理person.goToShanghai()
打印结果为:
准备去上海,找个人去买票
黄牛去买票
买到票了,准备出发
扩展
扩展可以为类、结构体、枚举、协议添加新的功能。Swift中的扩展类似于OC中的分类。
扩展可以做的事情:
- 添加计算属性
- 定义方法
- 使现有类型遵循某种协议
在Swift中,使用extension关键字来实现扩展。
其语法如下:
代码语言:javascript复制
extension SomeType {
// 这里的SomeType可以是类、枚举、结构体,也可以是协议
// 在这里面去添加一些新的内容
}
扩展计算属性
扩展可以在不动原有类型的情况下,向已有的类型添加计算实例属性和计算类型属性。
代码语言:javascript复制extension Int { //给Int类型增加一个平方属性 var square: Int { return self * self }}
var a = 2print(a.square) // 4
Int是结构体,上例演示了通过扩展给一个结构体增加计算实例属性。
扩展方法
扩展一个普通方法
代码语言:javascript复制extension Int { func square() -> Int { return self * self }}
var a = 8print(a.square()) // 64
使现有类型遵循某协议
扩展可以使既有类型遵循一个或者多个协议,协议的遵循书写方式与原类中完全一致。语法如下:
代码语言:javascript复制extension SomeClass : SomeProtocol, AnotherProtocol { //可以用于分离原类中的代码}
这个写法在开发中是非常有用的,比如可以将tableView、collectionView的代理从原控制器中给抽离出来,避免臃肿。
泛型
所谓泛型,顾名思义,就是广泛类型。也就是说,一开始不确定是什么类型,等到真正使用的时候,根据赋值的数据类型来确定类型。
先来看个案例,比如现在我们需要交换两个Int类型的变量的值,实现如下:
代码语言:javascript复制//交换传入的两个数的值func SwapTwoInts(a : inout Int, b : inout Int) { let temp = a a = b b = temp}
var lilyAge = 20var normanAge = 26SwapTwoInts(a: &lilyAge, b: &normanAge)print(lilyAge) // 26print(normanAge) // 20
我现在又增加了一个需求,就是既需要转换Int类型的两个数值,也需要转换Double类型的两个数值,或者是转换其他类型的两个数值,那怎么办呢?,这些方法仅仅是参数的类型不同,是不是针对每一个类型都需要写一个转换方法呢?
此时就需要用到泛型了。
代码语言:javascript复制//泛型函数func SwapTwoValues<T>(a : inout T, b : inout T) { let temp = a a = b b = temp}
var lilyAge = 20var normanAge = 26SwapTwoValues(a: &lilyAge, b: &normanAge)print(lilyAge) // 26print(normanAge) // 20
var lilyName = "lily"var normanName = "norman"SwapTwoValues(a: &lilyName, b: &normanName)print(lilyName) // normanprint(normanName) // lily
该例中的 T 是一个类型参数(它可以是任意名称,我们一般使用 T 来表示),类型参数用于指定并命名一个占位符类型,并使用<>包裹,放在函数名后边,比如该例中的<T>。可以使用它来指定参数类型或者返回值的类型。在真正调用的时候会被实际的类型替代,如传递的是Int,就替换为Int;如果传递的是Double,就替换为Double等等。
泛型的类型约束
有时候我们需要给泛型做一些约束,比如必须继承自某个父类,或者必须遵循某些协议等,这也是可以做到的。语法如下:
代码语言:javascript复制func someFunction<T : SomeClass, U : SomeProtocol>(someT : T, someU : U) {}
协议里面的泛型——关联类型
上面所说的是在类或者结构体、枚举中使用泛型,在协议中是不可以这样使用的。协议中使用泛型,是通过 associatedtype 关键字。
代码语言:javascript复制protocol SomeProtocol { associatedtype Element func play(parameter1 : Element, parameter2 : Element)}
struct SomeStruct : SomeProtocol { func play(parameter1: String, parameter2: String) { print("play, (parameter1), (parameter2)") }}
var s = SomeStruct()s.play(parameter1: "666", parameter2: "norman")
说明如下:
- 协议中的泛型,是通过关键字 associatedtype 来实现的
- 与类、结构体或者枚举中的泛型一样,协议中的泛型也可以进行类型约束(通过继承或者遵循协议的方式)
面向协议编程
众所周知,Swift是一门面向协议编程的语言。我所理解的面向协议编程是这样的:针对某个需要实现的功能,可以使用协议定义出接口,然后利用协议的拓展为其提供默认的实现。如果在某地方需要这个功能,则只需要声明遵循该协议即可。遵守某个协议的对象调用协议中声明的方法时,如果遵循者本省没有提供方法的实现,那么协议扩展提供的默认实现将会被调用。
代码语言:javascript复制//定义一个协议protocol WatchMovieProtocol { func watchAMovie(name : String)}
//遵循协议并实现协议中的方法class Student : WatchMovieProtocol { func watchAMovie(name: String) { print("去电影院看《(name)》") }}
上例中是一个遵循者遵循该协议,如果此时有100个遵循者都需要遵循该协议,并且该协议方法的实现都是一样的,那么是否需要在每一个遵循者里面都实现一下协议方法呢?
当然不需要,我们可以使用协议的拓展为协议中的方法提供一个默认实现:
代码语言:javascript复制//定义一个协议protocol WatchMovieProtocol { func watchAMovie(name : String)}
//利用协议的拓展,给协议中的方法提供默认实现extension WatchMovieProtocol { func watchAMovie(name : String) { print("去电影院看《(name)》") }}
//遵循协议class Student : WatchMovieProtocol {}
var studet = Student()studet.watchAMovie(name: "燃情岁月")
打印如下:
去电影院看《燃情岁月》
如果某一个遵循者需要独特的实现,那么我们自己再实现一个协议即可:
代码语言:javascript复制//定义一个协议protocol WatchMovieProtocol { func watchAMovie(name : String)}
//利用协议的拓展,给协议中的方法提供默认实现extension WatchMovieProtocol { func watchAMovie(name : String) { print("去电影院看《(name)》") }}
//遵循协议,并且自定义协议方法的实现class Student : WatchMovieProtocol { func watchAMovie(name : String) { print("学生去电影院看《(name)》") }}
var studet = Student()studet.watchAMovie(name: "燃情岁月")
打印如下:
学生去电影院看《燃情岁月》