Swift - Kingfisher 之 Kingfisher.
前言
在学习Swfit语法的时候,就看到 AssociatedType 和 typealias 这两个关键字,但是当时对这两个并没有多想什么。不知道 AssociatedType 怎么使用,而且觉得 typealias 并没有什么实际用处。最近在看 Kingfisher 这个库的时候,才发觉这个两个关键词的实际用处比我想象中的大很多。然而在看这个库的时候,发现使用的知识点非常多。这些都是我需要学习和了解的地方,所以这里对这个库使用到的知识点做一个简单的总结。
Kingfisher.swift 文件中使用到的知识点
先贴一下 Kingfisher 这个库中的代码,然后就代码中的知识点做个简单的梳理。
#if os(macOS)
import AppKit
public typealias Image = NSImage
public typealias View = NSView
public typealias Color = NSColor
public typealias ImageView = NSImageView
public typealias Button = NSButton
#else
import UIKit
public typealias Image = UIImage
public typealias Color = UIColor
#if !os(watchOS)
public typealias ImageView = UIImageView
public typealias View = UIView
public typealias Button = UIButton
#else
import WatchKit
#endif
#endif
public final class Kingfisher<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
/**
A type that has Kingfisher extensions.
*/
public protocol KingfisherCompatible {
associatedtype CompatibleType
var kf: CompatibleType { get }
}
public extension KingfisherCompatible {
public var kf: Kingfisher<Self> {
return Kingfisher(self)
}
}
extension Image: KingfisherCompatible { }
#if !os(watchOS)
extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }
#else
extension WKInterfaceImage: KingfisherCompatible { }
#endif
AssociatedType
这个是关联类型,在定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被实现时才会被指定。这样的好处就是在定义协议的时候只是占个位置,并不需要知道具体代表的类型。这样在实现的时候可以发挥空间更大。通常会与 typealias 配合使用。
public protocol AssociateTest {
associatedtype ATest
var tc: ATest{get}
}
extension UIViewController: AssociateTest {
public typealias ATest = String
public var tc: ATest {
return "\(self)"
}
}
extension String: AssociateTest {
public typealias ATest = Int
public var tc: ATest {
return self.count
}
}
extension Array: AssociateTest {
public var tc: Int {
return self.count
}
}
上面三个扩展都是可以正常编译并运行的。而在 Kingfisher 这个库中,使用 protocol 的扩展来实现这个功能,并把类型确定为 Kingfisher。这个样的做法一方面可以方便我们的调用。另一方面也让我们可以很容易替换这个库提供的方法。
我觉得 AssociatedType 的一个显而易见的好处就是它只是一个占位符,并不限制协议实现者的具体操作。不同的实现者可以根据自身的情况做不同的处理并给出不同的结果。这样的方式让 <协议> 这个机制更通用并且可以更好的履行自身的职责。
typealias
这个就是我们通常称呼的 "别名"。作用是给一个已知类创建另外一个名字。
现在我了解的作用有2个:
-
让类名更有实际意义(更符合当前环境要表示的含义),只要看到类名就大概知道它的作用。
这个关键词其实我们在开发过程中经常碰到,在我们处理时间相关的逻辑时,经常会有TimeInterval
类的变量出现。而我们在给这个变量赋值的时候,是可以直接写一个浮点数的。查看这个类的定义时,我们可以看到public typealias TimeInterval = Double
。我想TimeInterval
比Double
更容易让我们想到这个变量是指时间间隔而不是其他含义的值。这个机制对于只有一两个变量的时候好像没有什么作用,但是当碰到大量的 Int,String 等基础变量名并且要识别每个变量的含义的时候,这个别名机制就非常有用了。 -
对外暴露时,针对不同的类型使用统一的类名,简化组件使用的复杂度并且可以隐藏更多的细节。
在 Kingfisher 这个库中,别名机制用于统一不同平台的同一种特质的类(例如显示效果比较一致,或者含义比较接近)。例如上面代码中的 NSImage 和 UIImage,都是用于描述一张图片的,但是由于平台的关系,导致使用的是不同的类名。这样使用别名的时候更有利于对外暴露接口时的统一性,展示给不同类型调用者的是同一套接口,而在封装库内部使用平台判断等手段区分不同类的细微之处。
typealias 只能作用于确定的类型,如果我我们想让它对范型生效的话,就需要确定范型的实际类型。以下几种定义都是正确的:
typealias man = Person
typealias female = Human<Person>
typealias male = Human<man>
范型
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。
以上内容摘自百度百科。这是范型的学术性定义。我的理解是,范型就是一个在调用时才会确定具体类型的一个占位符。Swift对于范型的看重我们从语法上可以看出一些端倪。
在 Kingfisher 这个库中,入口类就使用了范型Kingfisher<Base>
,这里使用范型可以很容易的兼容各种平台/组件调用本库。
下面写一个简单的示例说明:
// 定义范型类
public class GenericTest<T> {
public var base: T
init(_ value: T) {
self.base = value
}
public var printTest: T {
get {
return base
}
}
}
// 测试代码
let value1 = "xxx"
print("\(value1) --- \(GenericTest(value1).printTest)")
let person = Person()
print("\(person) --- \(GenericTest(person).printTest)")
// 打印结果:
xxx --- xxx
CircleImageView.Person --- CircleImageView.Person
从上面的示例可以看出,GenericTest 类中 变量base 的类型是随着传入对象类型变化而变化的。根据这种机制,我们可以写出灵活且可重用的函数和类型。当然伴随而来的就是代码的可读性和可维护性问题(个人观点),所以在使用的时候需要注意一些。
protocol + extension
protocol 在定义之后,可以通过扩展的方式来直接实现定义的方法。这个功能我觉得是 Swift 比较大的一个亮点了。这样我们就不用在具体的实现者那里实现一些不需要实现的方法了。极大程度增强了 protocol的实用性和适用性。
计算属性和存储属性
对于属性而言,一般情况下都是用于存储值的,但是在 Swift 中这个情况有点不同,Swift 中分为存储属性和计算属性2中类型的属性。
- 存储属性 这个就是一般意义上的属性了,和其他语言的属性没有任何不同。
- 计算属性 定义了一个 getter 方法和可选的 setter 方法的属性,这个属性本身并不存储值,而是通过 提供的两个方法操作类中的其他元素信息。最常见的用法如
var x: CGFloat {
get {
return self.frame.minX
}
set {
var frame = self.frame
frame.origin.x = newValue
self.frame = frame
}
}
var viewSize: CGSize {
return CGSize(width: self.width, height: self.height)
}
最后
Kingfisher.swift 这个文件中使用到的知识点暂时就总结到这里,本人才疏学浅,势必会有一些遗漏和错误的地方,欢迎斧正~