写在前面
- 其实大家可以结合着我末尾写的泛类型知识数来理解会比较好懂,我也是想了半天,才理顺的
- 为什么突然先看起Generics了呢? 主要是看到了别人用swift写的算法里用到了泛类型,我当时也搞不懂,反正就先照着手打了几遍,对那段代码除了泛类型以外的东西都明白了个八九不离十了以后,就回过头来,对这个Generics产生了兴趣,打开文档一看,Generics在文档的倒数第三章,如果我要是从头看那要看到啥时候才看的到这一章啊,以前的我失败就失败在,每次拿起一本书来,就觉得一定要从头看到尾,才能看的懂.也不明白如此shabi的思想是如何在我脑子里存在了二十多年,TNND所以我就直接开始看Generics这一章.你问我为什么,我只想说我特么现在就对这章感兴趣,管它看懂看不懂,至少俺有兴趣,有兴趣最重要,有兴趣才能看完,只有看完了才知道看懂看不懂啊,看不懂还载反过来可以查啊,自己花功夫反过来查印象才最深啊......哎呀,又说多了,又激动了,至于我为什么觉醒了,原因很多,不过在这方面对我影响比较大的,是来自于一本书啦,以后有机会,把笔记整理整理,再写篇文章吧,哈哈哈
Generics的功效
Generics是为了解决简化那些功能完全一样,但参数类型不一样的方法而存在的,可以免去写很多重复的方法,比如:
func swapTwoInts(inout a: Int, inout _ b: Int) {
//括号里是代码实现,我省去了
}
func swapTwoStrings(inout a: Stirng, inout _ b: String) {
//括号里是代码实现,我省去了,与上面代码实现完全一样
}
可以看到上面这两种方法,都是为了交换前后两个参数而存在的,它们也就只有传入的两个参数的类型不同,函数名不同而已,其它都是相同的,所以,就有了Generics的出现.如下:
func swapTwoValues<T>(inout a : T, inout _ b: T) {
//此处为代码实现,省略
}
Generics Method(泛函数,泛方法)
这就是Generics方法(泛函数,泛方法),你能很明显发现:
- 传入参数的类型变成了大写的
T
,它属于是一个placeholder type(占位符类型),等到该方法真正调用的时候,swifth会自动推测出T所代表的类型 - 方法名的后面多了一个
<T>
,也就是这个地方,正式通知swift,T
是一个placeholder type(占位符类型) - 不是很重要,但你会注意到,方法名也起了一个很通用的方法名swapTwoValues,跟这个Generics的意思还蛮相符的
Type Parameters(类型参数)
- 上例中的T就是类型参数,还有像
Array<Element>
中的Element,Dictionary<Key, Value>
中的Key和Value,这些都是Type Parameters(类系参数) - 你可以命名很多类型参数比如
<V,U,T>
这样都是可以的 - 类型参数的命名也要跟Dictionary,Array等一样要定的有意义,见名思意
Generic Type(泛类型)
Generic除了可以改进方法,还可以改进Type(类)
struct Stack<Element> {
var items = [Element]()
mutating func push(item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
var stackOfStrings = Stack<String>()
这样就可以让一个stack<Element>
类放入各种类型了.
Extending a Generic Type(对泛类型进行扩展)
既然右泛类型,那么就可以给泛类型进行扩展,扩展关键词是extension
,这种扩展就不需要像泛函数那样需要类型参数列表,只需要extension
+类型名
就好了,然后在这个扩展里面写新的方法新的变量的时候,依然可以引用原类型里的泛类型参数,如下:(承接上面的例子)
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
这段代码是对上一段代码定义的泛类型(泛结构体)Stack的一个扩展,可以看到以下几点:
- 前面加有关键词
extension
这个是扩展关键词,正常扩展都用这个关键词 - 在其名字后面并没出出现
extension Stack<Element>
这样的代码,<Element>
被省去,不需要写 - 大括号里的代码,并不需要去重复写原
Stack<Element>
类的变量方法,同时还可以直接引用原类的方法与属性以及原类里的泛类型Element
Type Constraints(泛类型的类型约束)
既然有了泛类型,就会有泛类型的类型约束,来对我们创造出的泛类型进行类型约束,而这种类型约束就两种:
- 继承(inherit from a class)
- 协议(conform to some protocol)
下面是代码示例:
func someFucntion<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
//代码实现就省略了哈~
}
可以看到上面的示例是描述了一个名为someFucntion的函数,函数名后面跟了
<T: SomeClass, U: SomeProtocol
,意思是,告诉swift这个方法定义了两个placeholder(占位符),它们分别是T与U,这两个参数稍后会在函数声明的后半段,也就是紧接着的小括号里会作为两个参数传入函数中,所以结合上面学习的类型参数,这两个占位符T与U就是有了新的更准确的名字叫做Type Parameter(类型参数),或者可以叫它泛类型参数(好像没有看到这么叫的,我自己这么叫方便我自己理解),同时这两个泛类型参数还分别要遵循各自的约束(Type Constraints),类型参数T需要继承自某个类(SomeClass),类型参数U则要遵循某个协议(SomeProtocol)
另,文档中提到了两种协议一个Hashable
与Equatable
,Hashable是为了让让每个字符,元素神马神马的都拥有独一无二的编码,不会重复,且能准确提取查找,String,Int,Double,Bool都是默认遵循Hashable的;而Equatable则是要保证元素可以做==
与!=
的操作.
Associated Types(这我也不知道怎么翻....)
这个Associated Types 我也不晓得怎么翻译,就直接打原名吧,它貌似是为了protocol而存在的,在Protocol里引入一个placeholder(占位符),然后直到该协议(protocol)被真正引用并用代码实现调用的时候,才会知道它真正代表是个神马类型,见下面代码:
protocol Container {
associatedtype ItemType //这句是重点!
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
可以看到Associated Type的关键词(key)是associatedtype
,也就是说这个关键词的后面定义的就是所谓的Associated Type,也就是协议(protocol)里的占位符(placeholder,我还是喜欢叫它泛占位符,方便记忆,哈哈)
还记得上面学到的extension
吗,在扩展一个泛类型(generic type)的时候,虽然不用在类名(如:Stack)后写<T,U,Element>
这样的类型参数类表,但在扩展的同时,如果又希望它能遵循一个新的protocol的话,就需要在类名后面写出来了,代码如下:
extension Array: Container {
//代码实现省略
}
从代码中可以看到这次扩展是让Array这个类在扩展的同时,还遵循了Container这个协议(protocol),注意尊许protocol是不需要写尖括号<>
的,直接在类名后加冒号:
+协议名
Where Clauses(where从句)
这个标题起始并不好,应该写成Associated Type Constraints,为什么呢?因为类型参数(generic parameter)可以加约束(Type Constraints),同样类比过来,协议(protocol)也可以加约束,而这个约束的实现方法就是使用where从句(Where Clauses).
- where从句写在哪?
- 写在泛函数还是泛类型中
- 如果是在泛函数声明中给类型参数所遵循的协议写约束的话,就在写类型参数的尖括号
<>
中,接在类型参数列表的后面 - 如果是写在泛类型中,由于没有尖括号
<>
,搞得我有点迷茫,文档上没写,不知道是不行还是不重要,不过我猜,也就是直接在协议后接where语句就好,这个需要用代码去测试以下才知道
- 如果是在泛函数声明中给类型参数所遵循的协议写约束的话,就在写类型参数的尖括号
- 写在泛函数还是泛类型中
- where从句怎么写?
- 用where关键词衔接
- 在关键词后可以写多个约束,约束之间用逗号隔开
- 内容涉及到协议中的Associated Type的约束,或是类型之间的关系
下面看代码,印证以下:
func allItemsMatch< C1: Container, C2:Container where C1.ItemType == C2.ItemType, C1.ItemType: Equatable >(someContainer: C1, _ anotherContainer: C2) -> Bool {
//代码实现就省略了哈~
}
可以看到,泛函数allItemsMatch有两个泛类型参数C1与C2,它们都加了约束,约束为遵循Cantainer协议,而后面接了一个where关键词,从这里开始就是给这个Container协议加额外的约束,约束有两个,一个是说C1与C2在遵循Containner协议后(Containner协议中有一个Associated Type,在之前的代码中可以看到,也就是ItemType
),C1与C2都可以取道自己的Associated Type,也就是C1.ItemType
与C2.ItemType
,第一个约束也就是在要求这两个参数要相等;而第二约束是给这两个参数加了另外一个协议(Equatable),意思是在这两个参数比较是否相等之前,还要确定其遵循Equatable协议,才能比较,换句话说,你只有可以使用==
与!=
你才能比较是不是相等,如果连这两个相等与不能的操作符都不能用,那也比较不了.
总结
好了泛类型看完了,总结一下
一句话总结: Generic实际上是在讲一个叫做Generic的占位符(placeholder)在函数和类中的定义和应用.
详细总结:
这个占位符作为参数传给函数或者应用在类中定义变量的时候就叫做Type Parameter(类型参数)
它如果出现在protocol中就叫做Associated Type
这两种占位符我们统称为Generic
而作为Type Parameter(类型参数)的时候我们就可以给它加Constraints(约束)
Constraints(约束)又分为Inherit(继承)与Protocol(协议)
其中我们还可以给Protocol(协议)加Constraints(约束),而这个Constraint(约束)叫where从句
Generic Tree(泛类型知识树)
- Generic Method (泛函数,泛方法)
- Type Parameter (泛类型参数)
- Type Parameter Constraints(类型参数的约束)
- Inherit from a Type(继承)
- Conform some protocols(协议)
- Type Parameter Constraints — protocol's Constraints(协议的约束)
- where Clauses (where从句)
- Type Parameter Constraints — protocol's Constraints(协议的约束)
- Type Parameter Constraints(类型参数的约束)
- Type Parameter (泛类型参数)
- Generic Type(泛类型)
- Generic Type Constraints(泛类型的约束)
- Inherit from a Type(继承)
- Conform some protocols(协议)
- Type Parameter Constraints — protocol's Constraints(协议的约束)
- where Clauses (where从句)
- Type Parameter Constraints — protocol's Constraints(协议的约束)
- Extension Generic Type (泛类型的扩展)
- Inherit from a Type(继承)
- Conform some protocols(协议)
- Type Parameter Constraints — protocol's Constraints(协议的约束)
- where Clauses (where从句)
- Type Parameter Constraints — protocol's Constraints(协议的约束)
- Generic Type Constraints(泛类型的约束)
- Generic Protocol(泛协议,这我自己编的,实际是Gneric在协议中的应用)
- Associated Type
网友评论