iOS 强大的泛型,同样也可以对UIButton进行扩展

文章围绕这五点:

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即里氏替换原则,大概可以描述为: 程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的[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;

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

使用__kindof消除类型警告.png

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

}

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. 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?) {

// 实现 下载图片并缓存、展示的逻辑

}

}

时间: 2024-12-23 00:10:41

iOS 强大的泛型,同样也可以对UIButton进行扩展的相关文章

学习IOS开发UI篇--UI知识点总结(一) UIButton/UITextField

UIkit框架下的几个基本控件,UIButton,UITextField,UILabel,UIImageView,UIScrollView,UITableView,UITableViewCell,UIPageControl; 他们的继承关系,UILabel,UIImageView,UIScrollView,UITableViewCell,直接继承自UIView; UIButton,UITextField,UIPageControl,继承自UIControl; UIControl继承自UIView

iOS 强大第三方资源库

Github用法 git-recipesGit recipes in Chinese. 高质量的Git中文教程. lark怎样在Github上面贡献代码 my-git有关 git 的学习资料 gitignore非常赞 有用的.gitignore模板集合(忽略上传的文件集合),包含了各种语言. 完整[email protected] open-source-ios-apps- iOS开源App集合,分:swift与Objective-C--国外人整理. NewsBlur作者独自一个人 Samuel

ios新特性(泛型)

协变 子类转父类   逆变父类给子类赋值

泛型List集合转化为DateTable的扩展方法

文章出处:http://www.codeproject.com/Tips/867866/Extension-Method-for-Generic-List-Collection-to-Da 这段代码是能够帮助你把泛型集合List转出成DataTable的扩展方法. 背景: 不知道你是否知道这个扩展方法,但是你可以不做任何修改的去使用下面这个类的代码. 使用代码:   using System; using System.Collections.Generic; using System.Comp

ios开发总结:Utils常用方法等收集,添加扩展类,工具类方法,拥有很多方便快捷功能(不断更新中。。。)

BOBUtils 工具大全 本人github开源和收集功能地址:https://github.com/niexiaobo [对ios新手或者工作一年以内开发人员很有用处] 常用方法等收集.添加扩展类.工具类方法.请求方法封装(包括图片上传等)等等 拥有很多方便快捷功能 ^_^ 用法: 只需要导入BOBsKit.h 头文件就行 // #import "BOBsKit.h" 依赖三方库: pod 'SDWebImage', '~> 3.7.5' pod 'MBProgressHUD'

iOS支持图文混排的按钮(UIButton)

创建UIButton子类 直接上代码了 .h文件 创建UIButton子类 直接上代码了 .h文件 #import <UIKit/UIKit.h> @interface GraphicBtn : UIButton @property (nonatomic,assign)CGRect titleRect; @property (nonatomic,assign)CGRect imageRect; @end .m文件   #import "GraphicBtn.h" @impl

iOS新特性

//1.声明属性可以为空的三种写法: 1.@property (nonatomic, copy, nullable)NSString *name; 2.@property (nonatomic, copy)NSString *_Nullable name1; 3.@property (nonatomic, copy)NSString *__nullable name2; //2.声明属性可以为空的三种写法: 1.@property (nonatomic, copy, nonnull)NSStri

IOS开发第三方开源库

IOSios开发第三方开源库 1. AFNetworking 在众多iOS开源项目中,AFNetworking可以称得上是最受开发者欢迎的库项目.AFNetworking是一个轻量级的iOS.Mac OS X网络通信类库,现在是GitHub上第三大Objective-C库.它建立在NSURLConnection.NSOperation等类库的基础上,让很多网络通信功能的实现变得十分简单,因此,许多iOS应用开发都会使用到它. 支持HTTP请求和基于REST的网络服务(包括GET.POST.PUT

直接拿来用!最火的iOS开源项目(一~三)

结束了GitHub平台上“最受欢迎的Android开源项目”系列盘点之后,我们正式迎来了“GitHub上最受欢迎的iOS开源项目”系列盘点.今天,我们将介绍20个在GitHub上非常受开发者欢迎的iOS开源项目,你准备好了吗? 1. AFNetworking 在众多iOS开源项目中,AFNetworking可以称得上是最受开发者欢迎的库项目.AFNetworking是一个轻量级的iOS.Mac OS X网络通信类库,现在是GitHub上第三大Objective-C库.它建立在NSURLConne