Swift - Kingfisher 之 Kingfisher.

2020-04-13  本文已影响0人  Tony17

前言

在学习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个:

  1. 让类名更有实际意义(更符合当前环境要表示的含义),只要看到类名就大概知道它的作用。
    这个关键词其实我们在开发过程中经常碰到,在我们处理时间相关的逻辑时,经常会有 TimeInterval 类的变量出现。而我们在给这个变量赋值的时候,是可以直接写一个浮点数的。查看这个类的定义时,我们可以看到 public typealias TimeInterval = Double。我想 TimeIntervalDouble 更容易让我们想到这个变量是指时间间隔而不是其他含义的值。这个机制对于只有一两个变量的时候好像没有什么作用,但是当碰到大量的 Int,String 等基础变量名并且要识别每个变量的含义的时候,这个别名机制就非常有用了。

  2. 对外暴露时,针对不同的类型使用统一的类名,简化组件使用的复杂度并且可以隐藏更多的细节。
    在 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中类型的属性。

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 这个文件中使用到的知识点暂时就总结到这里,本人才疏学浅,势必会有一些遗漏和错误的地方,欢迎斧正~

上一篇 下一篇

猜你喜欢

热点阅读