美文网首页
Swift - Kingfisher 之 Kingfisher.

Swift - Kingfisher 之 Kingfisher.

作者: Tony17 | 来源:发表于2020-04-13 18:21 被阅读0次

    前言

    在学习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中类型的属性。

    • 存储属性 这个就是一般意义上的属性了,和其他语言的属性没有任何不同。
    • 计算属性 定义了一个 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 这个文件中使用到的知识点暂时就总结到这里,本人才疏学浅,势必会有一些遗漏和错误的地方,欢迎斧正~

    相关文章

      网友评论

          本文标题:Swift - Kingfisher 之 Kingfisher.

          本文链接:https://www.haomeiwen.com/subject/mongmhtx.html