服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Swift - Swift 中如何使用 Option Pattern 改善可选项的 API 设计

Swift 中如何使用 Option Pattern 改善可选项的 API 设计

2021-01-16 15:19OneV''s Den Swift

这篇文章主要介绍了Swift 中如何使用 Option Pattern 改善可选项的 API 设计,帮助大家更好的进行ios开发,感兴趣的朋友可以了解下

SwiftUI 中提供了很多“新颖”的 API 设计思路和 Swift 的使用方式,我们可以进行借鉴,并反过来使用到普通的 Swift 代码中。PreferenceKey 的处理方式就是其中之一:它通过 protocol 的方式,为子 view 们提供了一套模式,让它们能将自定义值以类型安全的方式,向上传到父 view 去。如果有机会,我会再专门介绍 PreferenceKey,但这种设计的模式其实和 UI 无关,在一般的 Swift 里,我们也能使用这种方法来改善 API 设计。

在这篇文章里,我们就来看看要如何做。文中相关的代码可以在这里找到。你可以将这些代码复制到 Playground 中执行并查看结果。

红绿灯

用一个交通信号灯作为例子。

Swift 中如何使用 Option Pattern 改善可选项的 API 设计

作为 Model 类型的 TrafficLight 类型定义了 .stop.proceed .caution 三种 State,它们分别代表停止、通行和注意三种状态 (当然,通俗来说就是“红绿黄”,但是 Model 不应该和颜色,也就是 View 层级相关)。它还持有一个 state 来表示当前的状态,并在设置时将这个状态通过 onStateChanged 发送出去:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TrafficLight {
 
  public enum State {
    case stop
    case proceed
    case caution
  }
 
  public private(set) var state: State = .stop {
    didSet { onStateChanged?(state) }
  }
  
  public var onStateChanged: ((State) -> Void)?
}

其余部分的逻辑和本次主题无关,不过它们也比较简单。如果你有兴趣的话,可以点开下面的详情查看。但这不影响本文的理解。

TrafficLight 的其他部分

在 (ViewController 中) 使用这个红绿灯也很简单。我们按照红绿黄的颜色,在 onStateChanged 中设定 view 的颜色:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
light = TrafficLight()
light.onStateChanged = { [weak self] state in
  guard let self = self else { return }
  let color: UIColor
  switch state {
  case .proceed: color = .green
  case .caution: color = .yellow
  case .stop: color = .red
  }
  UIView.animate(withDuration: 0.25) {
    self.view.backgroundColor = color
  }
}
light.start()

这样,View 的颜色就可以随着 TrafficLight 的变化而变更了:

Swift 中如何使用 Option Pattern 改善可选项的 API 设计

青色信号

世界很大,有些地方 (比如日本) 会使用倾向于青色,或者实际上应该是绿松色 (turquoise),来表示“可以通行”。有时候这也是技术的限制或者进步所带来的结果。

The green light was traditionally green in colour (hence its name) though modern LED green lights are turquoise.

– Wikipedia 中关于 Traffic light 的记述

Swift 中如何使用 Option Pattern 改善可选项的 API 设计

假设我们想要让 TrafficLight 支持青色的绿灯,一个能想到的最简单的方式,就是在 TrafficLight 里为“绿灯颜色”提供一个选项:

?
1
2
3
4
5
6
7
8
9
public class TrafficLight {
  public enum GreenLightColor {
    case green
    case turquoise
  }
  public var preferredGreenLightColor: GreenLightColor = .green
  
  //...
}

然后在 ViewController 中使用对应的颜色:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension TrafficLight.GreenLightColor {
  var color: UIColor {
    switch self {
    case .green:
      return .green
    case .turquoise:
      return UIColor(red: 0.25, green: 0.88, blue: 0.82, alpha: 1.00)
    }
  }
}
 
light.preferredGreenLightColor = .turquoise
light.onStateChanged = { [weak self, weak light] state in
  guard let self = self, let light = light else { return }
  // ...
  
  // case .proceed: color = .green
  case .proceed: color = light.preferredGreenLightColor.color
}

这样做当然能够解决问题,但是也会带来一些隐患。首先,需要在 TrafficLight 中添加一个额外的存储属性 preferredGreenLightColor,这使得 TrafficLight 示例所使用的内存开销增加了。在上例中,额外的 GreenLightColor 属性将会为每个实例带来 8 byte 的开销。 如果我们需要同时处理很多 TrafficLight 实例,而其中只有很少数需要 .turquoise 的话,这个开销就非常可惜了。

严格来说,上例的 TrafficLight.GreenLightColor 枚举其实只需要占用 1 byte。但是 64-bit 系统中在内存分配中的最小单位是 8 bytes。

如果想要添加的属性不是像例子中这样简单的 enum,而是更加复杂的带有多个属性的类型的话,这一开销会更大。

另外,如果我们还要添加其他属性,很容易想到的方法是继续在 TrafficLight 上加入更多的存储属性。这其实是很没有扩展性的方法,我们并不能在 extension 中添加存储属性:

?
1
2
3
4
5
6
7
// 无法编译
extension TrafficLight {
  enum A {
    case a
  }
  var myOption: A = .a // Extensions must not contain stored properties
}

需要修改 TrafficLight 的源码,才能添加这个选项,而且还需要为添加的属性设置合适的初始值,或者提供额外的 init 方法。如果我们不能直接修改 TrafficLight 的源码 (比如这个类型是别人的代码,或者是被封装到 framework 里的),那么像这样的添加选项的方式其实是无法实现的。

Option Pattern

可以用 Option Pattern 来解决这个问题。在 TrafficLight 中,我们不去提供专用的 preferredGreenLightColor,而是定义一个泛用的 options 字典,来将需要的选项值放到里面。为了限定能放进字典中的值,新建一个 TrafficLightOption 协议:

?
1
2
3
4
5
6
public protocol TrafficLightOption {
  associatedtype Value
 
  /// 默认的选项值
  static var defaultValue: Value { get }
}

TrafficLight 中,加入下面的 options 属性和下标方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TrafficLight {
 
  // ...
 
  // 1
  private var options = [ObjectIdentifier: Any]()
 
  public subscript<T: TrafficLightOption>(option type: T.Type) -> T.Value {
    get {
      // 2
      options[ObjectIdentifier(type)] as? T.Value
        ?? type.defaultValue
    }
    set {
      options[ObjectIdentifier(type)] = newValue
    }
  }
  
  // ... 
}
  1. 只有满足 Hashable 的类型,才能作为 options 字典的 key。ObjectIdentifier 通过给定的类型或者是 class 实例,可以生成一个唯一代表该类型和实例的值。它非常适合用来当作 options 的 key。
  2. 通过 key 在 options 中寻找设置的值。如果没有找到的话,返回默认值 type.defaultValue

现在,对 TrafficLight.GreenLightColor 进行扩展,让它满足 TrafficLightOption。如果 TrafficLight 已经被打包成 framework,我们甚至可以把这部分代码从 TrafficLight 所在的 target 中拿出来:

?
1
2
3
4
5
6
7
8
extension TrafficLight {
  public enum GreenLightColor: TrafficLightOption {
    case green
    case turquoise
 
    public static let defaultValue: GreenLightColor = .green
  }
}

我们将 defaultValue 声明为了 GreenLightColor 类型,这样TrafficLightOption.Value 的类型也将被编译器推断为 GreenLightColor

最后,为这个选项提供 setter 和 getter:

?
1
2
3
4
5
6
extension TrafficLight {
  public var preferredGreenLightColor: TrafficLight.GreenLightColor {
    get { self[option: GreenLightColor.self] }
    set { self[option: GreenLightColor.self] = newValue }
  }
}

现在,你可以像之前那样,通过直接在 light 上设置 preferredGreenLightColor 来使用这个选项,而且它已经不是 TrafficLight 的存储属性了。只要不进行设置,它便不会带来额外的开销。

?
1
light.preferredGreenLightColor = .turquoise

有了 TrafficLightOption,现在想要为 TrafficLight 添加选项时,就不需要对类型本身的代码进行改动了,我们只需要声明一个满足 TrafficLightOption 的新类型,然后为它实现合适的计算属性就可以了。这大幅增加了原来类型的可扩展性。

总结

Option Pattern 是一种受到 SwiftUI 的启发的模式,它帮助我们在不添加存储属性的前提下,提供了一种向已有类型中以类型安全的方式添加“存储”的手段。

这种模式非常适合从外界对已有的类型进行功能上的添加,或者是自下而上地对类型的使用方式进行改造。这项技术可以对 Swift 开发和 API 设计的更新产生一定有益的影响。反过来,了解这种模式,相信对于理解 SwiftUI 中的很多概念,比如 PreferenceKey alignmentGuide 等,也会有所助益。

以上就是Swift 中如何使用 Option Pattern 改善可选项的 API 设计的详细内容,更多关于Swift 改善api设计的资料请关注服务器之家其它相关文章!

原文链接:https://onevcat.com/2020/10/use-options-pattern/

延伸 · 阅读

精彩推荐
  • SwiftSwift算法之栈和队列的实现方法示例

    Swift算法之栈和队列的实现方法示例

    Swift语言中没有内设的栈和队列,很多扩展库中使用Generic Type来实现栈或是队列。下面这篇文章就来给大家详细介绍了Swift算法之栈和队列的实现方法,需要...

    李峰峰10002021-01-05
  • Swift浅谈在Swift中关于函数指针的实现

    浅谈在Swift中关于函数指针的实现

    这篇文章主要介绍了浅谈在Swift中关于函数指针的实现,是作者根据C语言的指针特性在Swifft中做出的一个实验,需要的朋友可以参考下...

    Swift教程网4372020-12-21
  • SwiftSwift中排序算法的简单取舍详解

    Swift中排序算法的简单取舍详解

    对于排序算法, 通常简单的, 为大家所熟知的有, 选择排序, 冒泡排序, 快速排序, 当然还有哈希, 桶排序之类的, 本文仅比较最为常见的选择, 冒泡和快排,文...

    Castie111012021-01-10
  • Swift分析Swift性能高效的原因

    分析Swift性能高效的原因

    绝大多数公司选择Swift语言开发iOS应用,主要原因是因为Swift相比Objc有更快的运行效率,更加安全的类型检测,更多现代语言的特性提升开发效率;这一系...

    louis_wang9092021-01-16
  • SwiftSwift 基本数据类型详解总结

    Swift 基本数据类型详解总结

    在我们使用任何程序语言编程时,需要使用各种数据类型来存储不同的信息。变量的数据类型决定了如何将代表这些值的位存储到计算机的内存中。在声明...

    Lucky_William4672021-12-26
  • Swiftswift相册相机的权限处理示例详解

    swift相册相机的权限处理示例详解

    在iOS7以后要打开手机摄像头或者相册的话都需要权限,在iOS9中更是更新了相册相关api的调用,那么下面这篇文章主要给大家介绍了关于swift相册相机权限处...

    hello老文12682021-01-08
  • SwiftSwift网络请求库Alamofire使用详解

    Swift网络请求库Alamofire使用详解

    这篇文章主要为大家详细介绍了Swift网络请求库Alamofire的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    lv灬陈强56682021-01-06
  • Swift详解Swift 之clipped是什么如何用

    详解Swift 之clipped是什么如何用

    这篇文章主要介绍了详解Swift 之clipped是什么如何用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下...

    iCloudEnd8532021-05-28