我们经常看到的OC中的泛型如
OC泛型
// 实例化一个元素类型为`NSString`的数组
NSArray <NSString *> *array = [NSArray new];
// 或者字典
NSDictionary <NSString *, NSNumber *> *dict = @{@"manoboo": @1}
// 或者
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {}
OC泛型定义
@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即里氏替换原则,大概可以描述为: 程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的[1]
__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关键词,表示其中的类型为该类型或者其子类,如:
@property (nonatomic, readonly) NSMutableArray <__kindof T> *elements;
之后就可以这样调用了
MBCollection <NSString *> *string_collection;
NSMutableString *str = string_collection.elements.lastObject;
Swift中的泛型
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
}
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等都支持泛型,使用如下:
在Swift中泛型应用地很广泛,常见的Array、Dictionary等都支持泛型,使用如下:
var dict: Dictionary<String, Any>
var dict: [String: Any] // 与上面的功能一样,只是语法糖的简写
var arr: [String]
var arr: Array<String>
举例
// 模仿 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"]
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将方法归类给CIImageKit类中,同样也可以对UIButton进行扩展
extension UIButton: CIImageDownloaderProtocol {}
extension CIImageKit where Base: UIButton {
func setImage(url: URL, placeHolder: UIImage?) {
// 实现 下载图片并缓存、展示的逻辑
}
}
自己写一个
public final class MHFImageKit<Base> {
public let base:Base
public init(base: Base) {
self.base = base
/** 后面调用的打印
(lldb) po self
<MHFImageKit<UIImageView>: 0x6000007fd4e0>
(lldb) po base
<UIImageView: 0x7f91b9d0e750; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x6000007f6aa0>>
*/
}
}
public protocol MHFDownloadProtocol {
associatedtype type
var mhf:type {get}
}
public extension MHFDownloadProtocol {
/** 计算属性
var Variable name: type {
statements
}
*/
var mhf: MHFImageKit<Self> {
return MHFImageKit(base: self)
}
}
/**
我们声明了一个MHFDownloadProtocoll协议,对于遵从了该协议的类,都有一个MHFImageKit类型的对象,下面我们扩展UIImageView
*/
extension UIImageView : MHFDownloadProtocol{}
extension MHFImageKit where Base: UIImageView {
func setImage(url: URL, placeHolder: UIImage?) {
// 实现 下载图片并缓存、展示的逻辑
}
}
/**
这就是一个基本的第三方库封装的模版,如何调用呢?
*/
func test() {
let imageV = UIImageView()
imageV.mhf.setImage(url: URL.init(string: "https://www.manoboo.com")!, placeHolder: nil)
}