美文网首页
Swift POP 可以使你的代码更优雅

Swift POP 可以使你的代码更优雅

作者: zbzbwxe | 来源:发表于2021-03-07 17:33 被阅读0次

POP 全称 Protocol Oriented Programming, 面向协议编程。
所谓协议,就是一组属性和/或方法的定义,而如果某个具体类型想要遵守一个协议,那它需要实现这个协议所定义的所有这些内容。协议实际上做的事情不过是"关于实现的约定"。

protocol Codeable {
    var name: String?
    func coding()
}

Swift是一门面向对象的语言,类已经满足我们所有的需求,功能也十分强大。为什么还要使用POP?
首先在Swift中,值类型优先于类。然而,面向对象的概念不能很好地与结构体和枚举一起共存, 因为结构体和枚举不能够被继承。因此,作为面向对象的一大特征—继承就不能够应用于值类型了。

Swift的 POP 是使用了继承的思想,它模拟了多继承关系,实现了代码的跨父类复用,同时也不存在 is-a 关系。swift中主类和 extension扩展类 的协同工作,保留了 在主类中定义方法 在所继承的类中进行实现的特性,又新增了在 extension拓展类 中定义并实现的方法在任何继承自此协议的类中可以任意调用,从而实现组件化编程。

让我们假设你的产品经理过来和你说,“我们在点击那个按钮时候出现一个视图,而且它会抖动。” 这是一个非常常见的动画,比如,在你的密码输入框上 – 当用户输入错误密码时,它就会抖动。

一些人可能已经有了 Swift 抖动对象的基础代码。一些人甚至都有 Swift 的抖动对象的代码,我想都不用想,只要稍稍修改一下。

//  ShakeImageView.swift

import UIKit

class ShakeImageView: UIImageView {
     func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 4.0, y: self.center.y))
        animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 4.0, y: self.center.y))
        layer.add(animation, forKey: "position")
    }
}

我将创建一个 UIImageView 的子类,创建我的 ShakeImageView 然后增加一个抖动的动画:

ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var ShakeImageView: ShakeImageView!

    @IBAction func onShakeButtonTap(sender: AnyObject) {
        shakeImageView.shake()
    }
}

在我的 view controller 里面,在 interface builder 里我连接我的 view,把它做为 ShakeImageView 的子类,我有一个 shake(),然后 完成了!我的代码工作得很正常。

然后,你的产品经理过来说,”你需要在抖动视图的时候抖动按钮。” 然后我回去对按钮做了同样的事情。

//  ShakeButton.swift

import UIKit

class ShakeButton: UIButton {

    func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 4.0, y: self.center.y))
        animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 4.0, y: self.center.y))
        layer.add(animation, forKey: "position")
    }
}

新建UIButton子类 ShakeButton,增加一个 shake() 函数。现在我能抖动我的 ImageView 和 Button 了,完成了。

//  ViewController.swift

class ViewController: UIViewController {

    @IBOutlet weak var shakeImageView:  ShakeImageView!
    @IBOutlet weak var shakeButton:  ShakeButton!

    @IBAction func onShakeButtonTap(sender: AnyObject) {
      foodImageView.shake()
      actionButton.shake()
    }
}

幸运的是,这会给你一个警告:我在两个地方重复了抖动的代码。如果我想改变抖动的幅度,我需要改两处代码,这很不好。

话说ImageView UIButton 这两个玩意儿不都是UIView的子类么,一拍大腿, 于是,灵机一动!
作为一个优秀的程序员,我们马上会意识到这点。如果你以前使用过 Objective-C,我会创建一个 UIView 的类别,在 Swift 里面,这就是扩展(Extension), 对UIView 进行扩展。

我能这样做,因为 UIButton 和 UIImageView 都是 UI 视图。我能扩展 UI 视图而且增加一个 shake 函数。现在我仍然可以给我的按钮和图像视图都加上其他的逻辑,但是 shake 函数就到处都是了。

//  UIViewExtension.swift

import UIKit

extension UIView {

   func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 4.0, y: self.center.y))
        animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 4.0, y: self.center.y))
        layer.add(animation, forKey: "position")
    }
}

class ViewController: UIViewController {
    @IBOutlet weak var foodImageView: ShakeImageView!
    @IBOutlet weak var actionButton: ShakeButton!

    @IBAction func onShakeButtonTap(sender: AnyObject) {
        foodImageView.shake()
        actionButton.shake()
    }
}

马上我们就能发现可读性很差了。例如,对于 shakeImageView 和 shakeButton 来说,你看不出来任何抖动的意图。整个类里面没有任何东西能告诉你它需要抖动。这样表意不明确,因为别处可能会随机存在一个抖动函数,你甚至不知道它是从哪里来的,曾经自己犯的错。

最主要的是,如果你常常为类别和 UIView 的扩展这样做的话,你可能会有更好的办法。在你增加了 shake()的地方,就成了所谓的 科学怪人的垃圾场。然后有人来和你说, “我想要一个可调暗的视图”,然后你增加一个 dim 函数和其他别处随机的调用函数。这样,代码文件文件就会变成一个很长的垃圾文件,因为这些随机调用的事情都是在 UIView 里面完成,尽管有些时候也许只有一两个地方需要这么做,这导致代码变得不可读,难以查看。我们该如何改变这点呢?

主角开始登场,我们当然会用到协议。首先我们创建一个 Shakeable 的协议:

//  Shakeable.swift
import Foundation
import UIKit

protocol Shakeable { }

extension Shakeable where Self: UIView {
       func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 4.0, y: self.center.y))
        animation.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 4.0, y: self.center.y))
        layer.add(animation, forKey: "position")
    }
}

在协议扩展的帮助下,你可以把它们限制在一个特定的类里面。在这个例子里面,我能抽出我的 shake(),然后用类别,我能说这是我们需要遵循的唯一的东西,只有 UI 视图会有这个函数。

你仍然可以使用你原来想用的同样强大的扩展功能,但是你有协议了。任何遵循协议的非视图不会工作。只有视图才能有这个 shake 的默认实现。

class ShakeImageView: UIImageView, Shakeable {

}

class ShakeButton: UIButton, Shakeable {

}

我们可以看到 ShakeImageView 和 ShakeButton 会遵循 Shakeable 协议。它们会有 shake(),现在的可读性强多了 –- 我可以理解 shaking 是有意存在的。如果你在别处使用视图,我需要想想,”在这也需要抖动吗?”。它增强了可读性,但是代码还是闭合的和可重用的。

假设我们想抖动和调暗视图。我们会有另外一个协议,一个 Dimmable 协议,然后我们可以为了调暗做一个协议扩展。再强调一遍,通过看类的定义来知晓这个类的用途,这样意图就会很明显了。

class FoodImageView: UIImageView, Shakeable, Dimmable {

}

关于重构,当产品说 “我不想要抖动了” 的时候,你只需要删除相关的 Shakeable 协议就好了。

class FoodImageView: UIImageView, Dimmable {

}

在你想添加业务的地方调用此协议:

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var shakeButton: ShakeButton!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    @IBAction func didClickShakeButton(_ sender: Any) {
        shakebutton.shake()
    }
    
}

基于POP 思想的面向协议编程是不是可以很优雅

例如: 我们使用XIB加载的自定义View都会使用相同的方法:

Bundle.main.loadNibNamed("RedView", owner: nil, options: nil)?.last
通常在自定义View的实现中定义一个类方法。
例如:

class func loadWithNib() -> RedView {
    return Bundle.main.loadNibNamed("RedView", owner: nil, options: nil)?.last! as! RedView
}

如果我们定义一个可从XIB加载某类的协议,在自定义类中仅仅需要遵守这个协议,无需实现协议中的方法,便可实现此协议中实现的所有方法.例如定义一个 可从XIB加载的协议 NibLoadable。

import Foundation

protocol NibLoadable {

}

extension NibLoadable where Self: UIView {
    static func loadViewWithNib() -> Self {
        return Bundle.main.loadNibNamed("\(self)", owner: nil, options: nil)?.last! as! Self
    }
}

只要在xib加载的类中继承此协议。

import UIKit

class GreenView: UIView , NibLoadable {

}

就可以在需要初始化此对象时直接调用。

GreenView.loadViewWithNib()

这种类似于插件化的 POP 思想,能确实能解决现实中的代码层次结构混乱的问题, 并且苹果鼓励我们使用面对协议的思想, 这也是Chris Lattne在创造这种语言的最核心的思想。

相关文章

  • Swift POP 可以使你的代码更优雅

    POP 全称 Protocol Oriented Programming, 面向协议编程。所谓协议,就是一组属性...

  • Swift入门

    Swift语言与OC的对比? 积极的一面来说,Swift让我们的代码更清晰,比如Swift的block代码,我们可...

  • 使用 Block 代码块进行实例化操作

    Swift 为我们提供一种非常优雅的类实例化语法, Block 代码块实例化。 使用 Block 代码块实例化,可...

  • OCLINT自定义规则-长函数限定LongMethodRule

    目的: 总所周知,优雅的代码结构,可以使代码结构更清晰,维护更方便。但,不是团队的每一个成员都有如此编写习惯,在c...

  • Swift Tips - Defer关键字

    前面有说到,在 swift 2.0 引入了 guard 关键字,可以让代码编写更流畅。它的优雅简洁而功能强大确实给...

  • 【Swift 3.0】popViewController出现警告

    swift3中pop代码出现⚠️,如下图所示 这是因为** popViewController方法默认返回了一个 ...

  • Swift 基本语法(十)— 协议扩展

    swift里强大的协议 协议的基本使用 协议继承 协议组合 由于很多种语言都不支持多继承。 所以可以使用POP(面...

  • Swift-泛型笔记

    Swift 泛型 Swift 提供了泛型让你写出灵活且可重用的函数和类型。 Swift 标准库是通过泛型代码构建出...

  • Swift Then语法糖使用教程

    简介 Then是一个Swift初始化库,使用非常简洁和优雅,大大提高代码可阅读性。 开始使用 依赖 切换到项目目录...

  • 泛型

    Swift 提供了泛型让你写出灵活且可重用的函数和类型。Swift 标准库是通过泛型代码构建出来的。Swift 的...

网友评论

      本文标题:Swift POP 可以使你的代码更优雅

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