Swift 命名空间形式扩展的实现
Swift 的 extension 机制很强大,不仅可以针对自定义的类型,还能作用于系统库的类型,甚至基础类型比如 Int。当在对系统库做 extension 的时候,就会涉及到一个命名冲突的问题。Objective-C 时代的通行解决办法是在扩展方法名字的最前面加上 XXX_ 形式的前缀。这种形式不但解决了命名冲突的问题,而且增强了代码可读性。一旦阅读到这种风格的方法名,就知道是非系统的实现。Swift 社区最初的一段时间内,也是按照这种命名方式来做的。
Swifty
在前缀形式的扩展使用了一段时间之后,大家渐渐觉得前缀形式的 Objective-C 风格不再适合 Swift。在命名方式上,社区掀起了一股 Swifty 化的风潮。WWDC 2016 的 Session 403 Swift API Design Guidelines 中详细阐述了 Swifty 风格的命名规则,而 Swift 3 的大量 API 的改动,也是按照这种风格进行的演进。很多开源的 Swift 陆陆续续在接下来的版本中,抛弃了之前的前缀命名形式,改用了 namespace 形式的命名。比如 RxSwift 的 rx_ => rx.,SnapKit 的 snp_ => snp.
对我们来说,实际的开发过程中,也经常会对系统库中的已有类型做自定义的扩展,如果有一种通用的形式,来实现这种扩展,那就太好了。
原理
namespace 形式扩展的原理,就是对原类型进行一层封装。在 Swift 中,这个封装类型使用的是 Struct,然后,对这个 Struct 进行自定义的方法扩展。
代码实现
以我刚写的一个开源库 HandOfTheKing举例,这个库的名字起源于冰与火之歌中的’国王之手’,主要功能是提供一个基于 RxSwift 和 SnapKit 的 iOS App 开发环境,并包含一些 iOS 开发中的实用扩展,比如链式的 UI 布局代码实现。其中的 HandOfTheKing/Namespace.swift 文件里,包含了命名空间形式扩展的实现。源码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public protocol NamespaceWrappable { associatedtype WrapperType var hk: WrapperType { get } static var hk: WrapperType.Type { get }}public extension NamespaceWrappable { var hk: NamespaceWrapper<self> { return NamespaceWrapper(value: self) } static var hk: NamespaceWrapper<self>.Type { return NamespaceWrapper.self }}public protocol TypeWrapperProtocol { associatedtype WrappedType var wrappedValue: WrappedType { get } init(value: WrappedType)}public struct NamespaceWrapper<t>: TypeWrapperProtocol { public let wrappedValue: T public init(value: T) { self.wrappedValue = value }}</t></self></self> |
使用方式
如果只想使用的话,把上面的源码拖到你的工程里,然后修改 hk 为任何你想要的前缀即可。扩展使用方式也很简单,举一个实际使用情况的例子,如果想要对 String 扩展一个名为 test 的方法,方法返回自身内容。除了写成方法,为了更便于使用,定义成计算属性也是可以的,代码如下:
|
1
2
3
4
5
6
|
extension String: NamespaceWrappable { }extension TypeWrapperProtocol where WrappedType == String { var test: String { return wrappedValue }} |
然后就可以按照下面的形式来使用这个扩展
|
1
2
|
let testStr = "foo".hk.testprint(testStr) |
在对 TypeWrapperProtocol 这个协议做 extension 时, where 后面的 WrappedType 约束可以使用 == 或者 :,两者是有区别的。如果扩展的是值类型,比如 String,Date 等,就必须使用 ==,如果扩展的是类,则两者都可以使用,区别是如果使用 == 来约束,则扩展方法只对本类生效,子类无法使用。如果想要在子类也使用扩展方法,则使用 : 来约束。
还有一些注意的地方
由于 namespace 相当于将原来的值做了封装,所以如果在写扩展方法时需要用到原来的值,就不能再使用 self,而应该使用 wrappedValue。
对类型扩展实现 NamespaceWrappable 协议,只需要写一次。如果对 UIView 已经写了 NamespaceWrappable 协议实现,则 UILabel 不需要再写。实际上写了之后,编译会报错。
如果在实现的 func 前加上 static 关键字,可以扩展出静态方法。
代码分析
解释一下实现的代码,由于使用了 protocol 和 generic 来实现,这里的代码不是很容易理解。
首先是定义了一个 NamespaceWrappable 协议,这个协议代表了支持 namespace 形式的扩展。并紧接着给这个协议 extension 了默认实现。这样实现了这个协议的类型就不需要自行实现协议所约定的内容了。
NamespaceWrappable 协议的默认实现返回了 NamespaceWrapper 这个带有泛型的 Struct。同时这个 Struct 实现了 TypeWrapperProtocol 协议。而 TypeWrapperProtocol 协议也带有泛型,而这两个泛型相互关联。这样就形成了最终的写法。
如果没有 TypeWrapperProtocol 这个协议,是可以直接对 NamespaceWrapper 这个泛型的 Struct 进行扩展的,对 Objective-C 的类来说这样的写法没有问题,但当尝试对 Swift 的值类型进行扩展时,会产生编译错误,比如下面这两种写法的代码:
|
1
2
3
4
|
extension NamespaceWrapper where T == String {}extension NamespaceWrapper where T: String {} |
会产生不同的编译错误,有兴趣可以尝试。为了统一,则多封装了 TypeWrapperProtocol 这个协议。
http://www.cocoachina.com/ios/20171031/21000.html
Swift 命名空间形式扩展的实现的更多相关文章
- 从字符串总分离文件路径、命名、扩展名,Substring(),LastIndexOf()的使用;替换某一类字符串,Replace()的用法
一:从字符串总分离文件路径.命名.扩展名,上图 二:代码 using System; using System.Collections.Generic; using System.ComponentM ...
- swift 学习- 23 -- 扩展
// 扩展 就是为一个已有的 类, 结构体, 枚举, 或者 协议类型添加新功能, 这包括在没有权限获取 原始代码的情况下 扩展类型的能力 (即 逆向建模), 扩展和 OC 中的分类类似, (与 OC ...
- swift 命名,字符串
命名: let numberOfDogs = 6 +2; 字符串连接: let finishedMessage = username + "xx" + password; 字符串 ...
- jQuery扩展$.fn、$.extend jQery命名方法扩展 练习总结
<script> $.fn.hello = function(){ //扩展jQuery实例的自定义方法,基于$.fn的jq方法扩展 this.click(function(){ ...
- 6.Swift协议|扩展|访问权限|异常调试|类型转换|运算函数|ARC|类类型初试化器|值类型初始化器
1. 协议(Protocol):与OC之间唯一不同的是Swift中的协议不管是属性还时方法全部是必须实现的 /** protocol*/ protocol FullNamed { /** 计算属性申明 ...
- swift学习笔记之-扩展(Extensions)
//扩展(Extensions) import UIKit /*扩展(Extensions):扩展 就是为一个已有的类.结构体.枚举类型或者协议类型添加新功能.这包括在没有权限获取原始源代码的情况下扩 ...
- swift:入门知识之协议与扩展
swift中使用protocol声明一个协议接口 swift中类.枚举和结构体都可以实现协议接口 swift中类中的方法都可以修改成员变量的值 swift中结构体中的方法默认是不能修改成员变量的,添加 ...
- Swift—扩展声明-备
声明扩展的语法格式如下: extension 类型名 { //添加新功能 } 声明扩展的关键字是extension,“类型名”是Swift中已有的类型,包括类.结构体和枚举,但是我们仍然可以扩展整型. ...
- 苹果新的编程语言 Swift 语言进阶(十四)--扩展
扩展是为一个已经存在的类.结构.枚举类型添加新功能的一种方式,包括为不能存取源代码的那些已经存在的类型添加功能. 扩展类似于Objective-C语言中的类别,与类别不同的是Swift语言的扩展没有名 ...
随机推荐
- Ubuntu 12.10终端Terminal快捷方式调用
1:使用快捷键:ctrl+alt+t 打开终端 2:在终端上右键,选“Lock to launcher” 这样就锁定在左侧了,需要用时,直接点就打开了.
- Apache POI组件操作Excel,制作报表(三)
Apache POI组件操作Excel,制作报表(三) 博客分类: 探索实践 ExcelApache算法Office单元测试 上一篇介绍了POI组件操作Excel时如何对单元格和行进行设置, ...
- scrapy学习笔记:项目中 使用代理ip
做为一个爬虫,最头疼的问题就是你的ip被封,想要在Scrapy领域无限制畅游,做好伪装是第一步,于是乎,抓取代理IP成了很多教程的开始部分.这里我说一下代理scrapy中代理ip,仅供大家借鉴! 代理 ...
- 【HAOI 2008】 硬币购物
[题目链接] 点击打开链接 [算法] 此题是一道好题! 首先,我们发现 : 付款方法数 = 不受限制的方法数 - 受限制的方法数 那么,我们怎么求呢? 我们用dp求出不受限制的方法数(f[i]表示买i ...
- CentOS6.0忘记root密码解决办法
说明操作系统:CentOS 6.0遇到问题:忘记管理员账号root的密码,进不了系统解决办法:重置root密码为123456操作: 开机启动系统,在进入系统之前按键盘上面的Esc键,会进入下面的界面 ...
- 使用css borer实现图层蒙版效果
需要js 思路:假设目标元素是target.在外层定义元素宽高等于target,通过border设置元素铺满整个文档,设置border的透明图,实现蒙版,在元素的内部设置子元素,宽高100%;设置圆角 ...
- 去除inline-block的间隙
产生间隙的原因就是标签之间的空格,去除的方法: 1 设置父元素的font-size:0;空格字符的宽高都为0, <div class="demo1 demo2"> &l ...
- Ruby 数式匹配器
str = "x^2 + 12317 +X^2 - Length" str = " x ^ 2 + y ...
- [App Store Connect帮助]七、在 App Store 上发行(3.4)提交至“App 审核”:将构建版本从审核中移除
若要停止“App 审核”流程,您可以将该 App 版本从 App 审核中移除.要执行此项操作,App 状态必须为下列之一: 正在等待出口合规检查 正在等待审核 正在审核 等待开发者发布 等待 Appl ...
- 《windows核心编程系列》十六谈谈内存映射文件
内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...