美文网首页
iOS 强大的泛型

iOS 强大的泛型

作者: differ_iOSER | 来源:发表于2017-09-15 09:58 被阅读40次

文章围绕一下五点进行介绍:

  1. 泛型是什么
  2. 为什么要用泛型
  3. 泛型怎么用
  4. 泛型进阶
  5. 泛型的延伸使用

泛型(Generics)是什么?

引用Apple中Generics的描述:

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.
Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code. In fact, you’ve been using generics throughout the Language Guide, even if you didn’t realize it. For example, Swift’s Array and Dictionary types are both generic collections. You can create an array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift. Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be.

大意是讲:
泛型可以让你使用定义的类型来编写灵活的、可重用的函数和类型,可以避免重复,以清晰,抽象的方式表达其意图。用人话来说,泛型给予我们更抽象的封装函数或类的能力,不严谨的来讲,一门语言越抽象使用越方便。Swift中的Array和Dictionary都是基于泛型编写的集合类型,如果不太理解也没关系,下面讲几个例子理解下。

1. Objective-C中的泛型

在2015年WWDC上苹果推出了Swift 2.0版本,为了让开发者从Objective-C更好得过渡到Swift上,苹果也为Objective-C带来了Generics泛型支持

Generics. Allow you to specify type information for collection classes like NSArray, NSSet, and NSDictionary. The type information improves Swift access when you bridge from Objective-C and simplifies the code you have to write.

所以我们经常看到的OC中的泛型比如:

// 实例化一个元素类型为`NSString`的数组
NSArray <NSString *> *array = [NSArray new];
// 或者字典
NSDictionary <NSString *, NSNumber *> *dict = @{@"manoboo": @1}

或者:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}

我们先看看OC中的泛型大概做了些什么:
打开NSArray.h 我们可以看到:

@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>

@property (readonly) NSUInteger count;
- (ObjectType)objectAtIndex:(NSUInteger)index;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects count:(NSUInteger)cnt NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;

@end

声明一个Generics的格式如下:

@interface 类名 <占位类型名称>
@end

占位类型后也可以加入类型限制,比如:

@interface MBCollection <T: NSString *>
@end

若不加入类型限制,则表示接受id即任意类型。我们先看看一个简单使用泛型的例子:

@interface MBCollection<__covariant T>: NSObject
@property (nonatomic, readonly) NSMutableArray <T> *elements;
- (void)addObject:(T)object;
- (BOOL)insertObject:(T)object atIndex: (NSUInteger)index;
@end

其中T为我们提前声明好的占位类型名称,可自定义(如ObjectType等等),需注意的是该T的作用域只限于@interface MBCollection到@end之间,至于泛型占位名称之前的修饰符则可分为两种:__covariant(协变)和__contravariant(逆变

两者的区别如下:
__covariant意为协变,意思是指子类可以强制转转换为(超类)父类,遵从的是SOLID中的L即里氏替换原则,大概可以描述为: 程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的。
__contravariant意为逆变,意思是指父类可以强制转为子类。
用我们上面自定义的泛型来解释:

MBCollection *collection;
MBCollection <NSString *> *string_collection;
MBCollection <NSMutableString *> *mString_collection;
        
collection = string_collection;
string_collection = collection;
collection = mString_collection;

默认不指定泛型类型的情况下,不同类型的泛型可以互相转换。
这个时候就可以在占位泛型名称前加入修饰符__covariant或__contravariant来控制转换关系,像NSArray就使用了__covariant修饰符。

引申:使用__kindof消除类型警告
在上面这个例子中,声明属性时,还可以在泛型前添加__kindof关键词,表示其中的类型为该类型或者其子类,如:

@property (nonatomic, readonly) NSMutableArray <__kindof T> *elements;

之后就可以这样调用了

MBCollection <NSString *> *string_collection;
NSMutableString *str = string_collection.elements.lastObject;

也不会有这样的类型警告了

2. Swift中的泛型

从The Swift Programming Language的例子开始讲起

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

这个函数作用是为了交换两个Int整型值,但是想象一下,这段代码只能做到交换整型值吗,那我们想交换两个String类型或者其他更多的类型呢?可能会写swapTwoStrings、swapTwoDoubles,如何如何将交换两个Int值进化为交换两个同类型的值呢?怎么去封装可以让函数更抽象支持更多的类型,因此就诞生了泛型,可以看出使用泛型可以更好地、更抽象地扩大该方法的作用域

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

2.1 Class / Struct / Enum + 泛型

依旧用The Swift Programming Language中的一个例子,如何用泛型实现一个栈,这这时用泛型就可以轻松地实现pop和push,免去Swift中的类型转换。

struct Stack<T> {
    
    private var collection = Array<T>.init()
    // private var colection = [T]()  等同于上方
    var maxLength: Int = 10
    
    var topElement: T? {
        return collection.isEmpty ? nil: collection.last
    }
    
    // 声明可以忽略返回结果
    @discardableResult
    mutating func push(_ object: T) -> Bool {
        if collection.count < maxLength {
            collection.append(object)
            return true
        }else {
            return false
        }
    }
    
    @discardableResult
    mutating func pop() -> T {
        return collection.removeLast()
    }
}

// 调用
var stack = Stack<String>.init()
stack.push("mano")
stack.push("boo")
stack.push("welcome")
stack.pop()
if let topElement = stack.topElement {
    print("the top element is (topElement)")
}

我们使用泛型定义了一个Stack栈,栈中的元素类型为T,栈的容量为10个元素,实现了最简单的入栈和出栈的功能,在T泛型后也可以限定泛型的class或者遵从的protocol,如struct Stack<T: NSString>、struct Stack<T: Hashable>、struct Stack<T: Equatable>
在Swift中泛型应用地很广泛,常见的Array、Dictionary等都支持泛型,使用如下:

var dict: Dictionary<String, Any>
var dict: [String: Any] // 与上面的功能一样,只是语法糖的简写
    
var arr: [String]
var arr: Array<String>

再举一个例子[2]

// 模仿 Dictionary 自定义一个泛型字典
struct GenericsDictionary<Key: Hashable, Value> {
    private var data: [Key: Value]
    
    init(data:[Key: Value]) {
        self.data = data
    }
    
    subscript(key: Key) -> Value? {
        return data[key]
    }
}

使用:

let genericsDict = GenericsDictionary.init(data:["name": "manoboo", "age": 24])
let name = genericsDict["name"]
let age = genericsDict["age"]
// 此时 age 的类型为 Any?

在Swift 4.0中给subscript方法带来了泛型支持,所以我们可以这样写:

subscript<T>(key: Key) -> T? {
    return data[key] as? T
}

使用:

let name: String? = genericsDict["name"]
let age: Int? = genericsDict["age"]

2.2 protocol + 泛型

前面介绍了Swift中常见的泛型使用情景,下面看看如何使用protocol配合泛型封装一个小型的图片请求扩展库模板
在OC中我们常见的第三方库基本都是使用category扩展原先的类,比如 SDWebImage的一个例子:

UIImageView *imageView = [[UIImageView alloc] init];
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
             placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

但是扩展很多的情况下,我们可能会重复命名,可能不知道该方法属于哪个库,得益于Swift中extension优秀的扩展能力,我们可以很好的避免这个问题,代码如下:

public final class CIImageKit<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

// protocol中 需要用 associatedtype 来预设一个类型
public protocol CIImageDownloaderProtocol {
    associatedtype type
    var ci: type { get }
}

public extension CIImageDownloaderProtocol {
    public var ci: CIImageKit<Self> {
        get {
            return CIImageKit(self)
        }
    }
}

我们声明了一个CIImageDownloaderProtocol协议,对于遵从了该协议的类,都有一个CIImageKit类型的对象,下面我们扩展UIImageView

extension UIImageView: CIImageDownloaderProtocol {}
extension CIImageKit where Base: UIImageView {
    func setImage(url: URL, placeHolder: UIImage?) {
        // 实现 下载图片并缓存、展示的逻辑
    }
}

这就是一个基本的第三方库封装的模版,如何调用呢?

let image = UIImageView()
image.ci.setImage(url: URL.init(string: "https://www.manoboo.com")!, placeHolder: nil)

我们通过中间一层protocol将方法归类给CIImage类中,同样也可以对UIButton进行扩展

extension UIButton: CIImageDownloaderProtocol {}
extension CIImageKit where Base: UIButton {
    func setImage(url: URL, placeHolder: UIImage?) {
        // 实现 下载图片并缓存、展示的逻辑
    }
}

Generic Subscripts in What's New In Swift(https://www.raywenderlich.com/163857/whats-new-swift-4

原文

相关文章

  • iOS 强大的泛型

    iOS 强大的泛型 iOS 强大的泛型

  • iOS 强大的泛型

    文章围绕一下五点进行介绍: 泛型是什么为什么要用泛型泛型怎么用泛型进阶泛型的延伸使用 泛型(Generics)是什...

  • iOS 强大的泛型

    ManoBoo撸了一个月小程序,感觉身体已经被掏空,各种兼容问题,2333,不闲扯了,其实这篇文章早已写完,一直没...

  • java 基础回顾 - 泛型

    泛型在使用时确定, 但如果提前知道泛型也可以在定义时确定泛型类型, 但这样做体现不了泛型的优势与强大. 泛型不存在...

  • swift泛型

    一、以泛型为参数的函数泛型是Swift语言强大的核心,泛型是对类型的抽象,使用泛型开发者可以更加灵活方便的表达代码...

  • 掌握Swift泛型:一个实际的代码重用示例

    一、序言 泛型是Swift的一项强大功能,可让您以其他方式无法实现的方式泛化和重用代码。泛型具有许多强大的特型,这...

  • OneDayOneSwift[23] - Generics

    泛型是 Swift 的强大特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言...

  • Swift中泛型的使用

    在使用Swift开发的过程中,我们可能经常会碰到泛型。那么究竟什么是泛型?泛型作为Swift最为强大的特性之一,该...

  • 无规矩不成方圆

    Swift里面有个很强大的特性叫泛型。 什么是泛型?[https://swiftgg.gitbook.io/swi...

  • swift4 泛型(一)

    swift 泛型 OC 是没有泛型也不支持命命空间的,但是swift中这两者都有,本章主要介绍 泛型 对于iOS开...

网友评论

      本文标题:iOS 强大的泛型

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