Swift中的限定扩展详析

前言

现在很多公司的iOS新项目都开始用Swift来代替OC开发了,Swift带来的亮点和新功能很多,但我觉得最重要的一点是引导我们编程思想的改变,将我们在OC中用到的传统的面向对象编程思想OOP(object-oriented programming)向面向协议编程思想POP(protocol oriented programming)以及面向值的编程思想VOP(value-oriented programming)上转变,苹果也让我们开发者在编程的时候“从一个protocol开始,别从一个class开始”,再加上Swift的标准库中出现了比重很大的结构体struct和枚举类型enum,所以在Swift中灵活运用各种协议protocol和值类型value来实现功能既实用又优雅。而在Swift编程过程中无处不在的extension,不仅让我们在代码结构上发生了巨大改变,同时在编程理念上也与POP不谋而合。

extension

extension,顾名思义就是扩展,类似于OC中的category,但Swift中的extension功能却强大的多, 可以扩展class struc enum 甚至是protocol,来给他们:提供便利构造方法、增加运算属性、定义实例方法和类方法、定义下标、使已有的类型遵循协议等等。

现在有一个需求,A、B和C三个类,都需要扩展一个属性或者方法用处理同样的功能,OOP的做法:让A、B和C继承于一个基类D,然后给D中加上这个属性,或者当A、B和C三个类不方便继承自一个基类时,各自分别扩展一个属性。不过这样做感觉很死板的样子。

而面向协议编程POP的做法:写声明一个协议someProtocol,协议中声明这个属性,然后让A、B、C三个类都遵循这个someProtocol,分别再实现这个协议中的属性,如下所示:

//定义protocol
protocol someProtocol {
 var clickCount: Int { set get }
 func ClickEvent(action: String,value: NSNumber)
}

extension A: someProtocol{
 var clickCount: int { set get }
 func ClickEvent(action: String,value: NSNumber){
  //实现代码
 }
}

限定扩展 extesion...where..

而此时来了新的需求,需要给A类扩展另外一个属性,但B和C暂时不需要,大部分人的思维肯定是既然只有A需要,那我们就单独给A来扩展一个属性不就可以了吗,这样肯定行得通,但既然我们都已经走上了POP编程的道路,而且Swift也鼓励我们尽量用protocol来处理问题,那我们应该怎么在protocol上做文章呢?

首先我们想到了给someProtocol扩展一个属性,但这样不只是A能用,同样遵循了该协议的B和C,以及将来遵循这个协议的所有class、struct等等都扩展得到了这个属性,这从代码的逻辑性上来说是不严谨的,于是苹果在Swift2.0的时候加入了一个新功能,可以在给protocol扩展的时候添加限定,就是说在满足该限定条件(遵循另一个协议或者满足某个类型)下才能允许使用此扩展下的属性或方法, 而这个限定就是通过where来添加的, 先看代码:

extension someProtocol where Self: UIViewController{
 var otherProperty: String{
  return "something you want"
 }

 func handleError(error: String) {
  //实现代码
}}

以上代码的意思就是:Self代表遵循了someProtocol的某个类,只有这个类是继承于UIViewController时,才可以使用otherProperty这个属性和handleError这种方法,这就是限定扩展的基本写法,当然此处的UIViewController也可以是另外的一个协议anotherProtocol,就是说你只有遵循了anotherProtocol的前提下,你才能使用someProtocol中的扩展内容。

RxSwift/RxCocoa中的限定扩展应用

Swift开发到一定阶段,通常都会引入RxSwift框架来进行响应式编程和敏捷开发,而在代码中与RxSwift同时会引入的另一个库RxCocoa,RxCocoa是Rx对iOS的原生API中UIKit以及Foundation中的视图(UIView)、控制事件(Control Event)、键值观察(KVO)、通知(Notification)等的扩展,以便在开发时更方便的对这些原生组件进行Rx应用。例如:

//nameTextField是一个UITextField控件,可以直接通过.rx.text获取到该控件中输入内容的事件序列,然后再进行处理

let nameObservable = nameTextField.rx.text .shareReplay(1).map {
 $0!.characters.count > 0
}

//registerButton是一个UIButton,通过.rx.tap能获取到该button的点击事件序列
registerButton.rx.tap .bindTo(someObservar) .addDisposableTo(disposeBag)

而以上例子中的rx.text以及rx.tap是怎么实现的呢,咱们来看RxCocoa的源码一窥究竟:

/// Extend NSObject with `rx` proxy.将所有NSObject子类都遵循ReactiveCompatible协议
extension NSObject: ReactiveCompatible { }
 //ReactiveCompatible这个协议中扩展了rx属性,类型为struct类型的Reactive
extension ReactiveCompatible {
 /// Reactive extensions.
 public var rx: Reactive {
 get {
  return Reactive(self) //此处返回Reactive实体,将self赋给base属性
 }
 set {
  // this enables using Reactive to "mutate" base object
 }
 }
}

以UIButton举例,通过以上实现,我们明白了通过UIButton.rx可以获得一个Reactive类型的属性,通过Reactive(self) 将button自身赋给了Reactive这个struct的Base属性,来看源码:

public struct Reactive<Base>{
 /// Base object to extend.
 public let base: Base
 /// Creates extensions with base object.
 ///
 /// - parameter base: Base object.
 public init(_ base: Base) {
  self.base = base //上面的return Reactive(self),将self赋给了base
 }
}

通过Reactive的构造方法,此时UIButton.rx获得的这个Reactive实体中的Base类型就是UIButton了,而base就是这个UIButton对象本身,而再接着通过rx.tap又是怎么获得点击事件的呢,这就用到了限定扩展这个非常实用的功能了,接着看源码:

//点击事件扩展,可通过button.rx.tap来观察订阅
extension Reactive where Base: UIButton {
 /// Reactive wrapper for `TouchUpInside` control event.
 public var tap: ControlEvent {
  return controlEvent(.touchUpInside)
 }
}

以上就是RxCocoa中对Reactive进行的限定扩展,只有当Reactive的Base类型是UIButton时,才能使用Reactive下的tap属性,而这个tap属性就是RxSwift封装好的点击事件序列。RxCocoa中也同时对改变UIButton的image和title进行了扩展,如下:

//扩展方法,可通过绑定Observable来动态修改UIButton的title和image
extension Reactive where Base: UIButton {
 /// Reactive wrapper for `setTitle(_:for:)`
 public func title(for controlState: UIControlState = []) -> UIBindingObserver<Base, String?> {
  return UIBindingObserver<Base, String?>(UIElement: self.base) { (button, title) -> () in
   button.setTitle(title, for: controlState)
  }
 }

 /// Reactive wrapper for `setImage(_:for:)`
 public func image(for controlState: UIControlState = []) -> UIBindingObserver<Base, UIImage?> {
  return UIBindingObserver<Base, UIImage?>(UIElement: self.base) { (button, image) -> () in
   button.setImage(image, for: controlState)
  }
 }

 /// Reactive wrapper for `setBackgroundImage(_:for:)`
 public func backgroundImage(for controlState: UIControlState = []) -> UIBindingObserver<Base, UIImage?> {
  return UIBindingObserver<Base, UIImage?>(UIElement: self.base) { (button, image) -> () in
   button.setBackgroundImage(image, for: controlState)
  }
 }
}

我们自己也可以对UI控件来进行其他的Rx扩展,以满足特定需要,比如说只有当用户名和密码同时满足某个条件时,登录按钮才可以点击以及改变背景颜色,因此我们可以将某种条件转换为一个Bool类型的可观察序列Observabel<Bool> ,然后对UIButton进行一个观察者类型btnValidState的扩展,然后绑定就可以随时进行监控了。

//利用限定扩展来自定义对UIButton的Reactive扩展 可以通过rx来访问
extension Reactive where Base:UIButton{
 var btnValidState:UIBindingObserver<Base,Bool>{
  return UIBindingObserver(UIElement: base, binding: { (button, valid) in
   button.isEnabled = valid
   button.backgroundColor = valid ? mainColor : bgGrayColor2
  })
 }
}
//将用户名和密码的String序列转换成Bool序列
let nameObservable = userNameText.rx.text .shareReplay(1).map {
   $0!.characters.count > 0
 }
let passObservable = passwordText.rx.text .shareReplay(1).map {
   $0!.characters.count > 0
 }
//将以上两个序列合成一个序列,绑定到我们扩展的btnValidState属性上
//可以看到此时可以通过loginBtn.rx.btnValidState获取到,这样保持了RxSwift代码的一致性
 _ = Observable.combineLatest(nameObservable,passObservable){$0 && $1}.bind(to: loginBtn.rx.btnValidState).disposed(by: disposeBag)

以上就是限定扩展的基本使用和一些实战中的应用,这在一定程度上确实能改变我们的编码思维和方式,这也是Swift给我们带来的非常灵活的改变。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Swift 3.0基础学习之扩展

    介绍 扩展可以为类,结构体,枚举和协议添加新的功能.包括可以对没有源码访问权限的类型进行扩展.扩展和 Objective-C 分类 的概念类似.(和 Objective-C 的分类不一样的是,Swift 扩展没有名称). 在 Swift 中,扩展可以做到: 添加计算的实例属性和计算的类型属性 定义实例方法和类型方法 提供新的初始化器 定义下标 定义并使用新的嵌套类型 使现有类型符合协议 值得注意的是:扩展可以为类型添加功能,但是不可以重写现有的功能. 扩展语法 使用关键字 extension 定

  • Swift中的限定扩展详析

    前言 现在很多公司的iOS新项目都开始用Swift来代替OC开发了,Swift带来的亮点和新功能很多,但我觉得最重要的一点是引导我们编程思想的改变,将我们在OC中用到的传统的面向对象编程思想OOP(object-oriented programming)向面向协议编程思想POP(protocol oriented programming)以及面向值的编程思想VOP(value-oriented programming)上转变,苹果也让我们开发者在编程的时候"从一个protocol开始,别从一个c

  • IOS 开发之swift中UIView的扩展使用的实例

    IOS 开发之swift中UIView的扩展使用的实例 扩展类代码: import UIKit extension UIView { // MARK : 坐标尺寸 var origin:CGPoint { get { return self.frame.origin } set(newValue) { var rect = self.frame rect.origin = newValue self.frame = rect } } var size:CGSize { get { return

  • IOS 开发之swift中手势的实例详解

    IOS 开发之swift中手势的实例详解 手势操作主要包括如下几类 手势 属性 说明 点击 UITapGestureRecognizer numberOfTapsRequired:点击的次数:numberOfTouchesRequired:点击时有手指数量 设置属性 numberOfTapsRequired 可以实现单击,或双击的效果 滑动 UISwipeGestureRecognizer direction:滑动方向 direction 滑动方向分为上Up.下Down.左Left.右Right

  • 在Swift中如何使用正则表达式详解

    前言 正则表达式,又称规则表达式.(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表通常被用来检索.替换那些符合某个模式(规则)的文本. 正则表达式(Regular expression, regex)允许我们在几秒钟内在成千上万文档间进行复杂检索与替换,自从诞生50多年来它依旧广泛使用. Swift虽然是一个新出的语言,但却不提供专门的处理正则的语法和类.所以我们只能使用古老的NSRegularExpression类进行

  • Spring Boot 中starter的原理详析

    目录 1.springboot 的starter 的启动原理是什么 原理 来个例子 小结 2.springboot 是如何找到配置类的 3.springboot starter 的bean 是怎么加载到容器的 4.总结 前言: 今天介绍springboot ,也是写下springboot的插件机制,starter的原理,其实这个网上已经很多了,也是看了不少别人的文章,今天主要还是带着问题去记录下. 1.springboot 的starter 的启动原理是什么 原理 这个问题是很简单的,只要了解s

  • Swift中闭包实战案例详解

    前言 无论苹果的官方文档还是由官方文档衍生出来的一些文章和书籍都比较重视基础语法知识的讲解,对于实战中的应用提及的都很少,所以当我们想使用"闭包"解决一些问题的时候,会忽然出现看着一堆理论知识却不知从何下手的尴尬感,这就是理论和时实战的区别了. 本文不赘述Swift闭包的的基本语法了,百度或者Google下有很多资料.如题所示本文着重讲述Swift闭包的一些实战案例,有需要的小伙伴可以参考下,经验丰富的大神也请指教. 关于如何理解闭包 学习闭包的第一个难点就是理解闭包,可能很多人用了很

  • Swift中的指针操作详解

    前言 Objective-C和C语言经常需要使用到指针.Swift中的数据类型由于良好的设计,使其可以和基于指针的C语言API无缝混用.但是语法上有很大的差别. 默认情况下,Swift 是内存安全的,这意味着它禁止我们直接操作内存,并且确保所有的变量在使用前都已经被正确地初始化了.但是,Swift 也提供了我们使用指针直接操作内存的方法,直接操作内存是很危险的行为,很容易就出现错误,因此官方将直接操作内存称为 "unsafe 特性". 一旦我们开始直接操作内存,一切就得靠我们自己了,因

  • swift中的@UIApplicationMain示例详解

    前言 最近在学习swift,在学习中遇到了一些需要整理记录的知识点,下面本文主要介绍了关于swift中@UIApplicationMain的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 程序如何开始的 C 系列语言中,程序的入口都是 main 函数,一个 Objective-C 的 iOS app 项目在新建时,Xcode 会给我们创建好一个 main.m 的文件. #import <UIKit/UIKit.h> #import "AppDelegate

  • Swift中转义闭包示例详解

    目录 前言 转义与非转义闭包 逃离方法 将转义关闭付诸行动 注意强参考周期 内存泄漏背后的原因 消除强引用循环 概括 前言 Swift 是一种非常强大的编程语言,是为 Apple 生态系统开发应用程序的首选:iOS.macOS.watchOS 和 tvOS.作为使用 Swift 编写代码的开发人员,我们经常使用闭包:语言的一个重要而重要的章节. 闭包不是初学者开始的主题.然而,这是每个人都必须尽快了解的东西.有很多方面需要了解并了解它们的工作原理.在所有这些中,有一个特定的:转义闭包和@esca

  • swift中自定义正则表达式运算符=~详解

    什么是正则表达式? 正则表达式,又称正规表示法.常规表示法(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表达式使用单个字符串来描述.匹配一系列符合某个句法规则的字符串.在很多文本编辑器里,正则表达式通常被用来检索.替换那些符合某个模式的文本. swift 至今并没有在语言层面上支持正则表达式,可能在开发app时正则表达式使用的场景并不多. 封装 在 Cocoa 中我们可以使用 NSRegularExpression 来做

随机推荐