TinyExtension:空Closure简化

Galvin Li
7 min readFeb 17, 2019

--

English version also available: TinyExtension: Simplified empty Closure

在Swift语言下,我们经常使用Closure,当然Closure大部分都是用于实现具体功能块,但在一些场景下我们会用到空Closure:

场景一:忽略回调方法

一般来说回调方法都会实现的,但有时候接口提供的功能多于我们的需求,例如我们需要用到下面这一个接口:

func sampleRequest(success: (Bool, String) -> (), failure: () -> ()) {
// some network request logic
// with some local variable store logic
}

一般来说我们都会实现successfailure回调,但一些场景下我们确实会忽略一些回调,例如预加载数据的时候,我们不需要处理failure回调,毕竟预加载都是后台进行,错误信息也不会反馈到用户界面,而且失败也影响不大。我们调用的代码如下:

sampleRequest(success: { (isSuccess, text) in
// do some complex thing
}, failure: {})

这里我们用到了空Closure,其实也很简洁,因为failure回调没有携带数据,而如果说我们的预加载在接口内都已经完成了,这样success回调都不需要实现,调用的效果就会变成这样:

sampleRequest(success: { _, _ in }, failure: {})

这时候空Closure就变得没那么简单了,我们必须根据实际的参数数量声明_去忽略返回的数据。往后如果回调参数数量变化了,我们还需要手动维护这个空Closure。

有人可能说我们可以在sampleRequest()使用optional的回调配合默认参数nil去实现忽略回调的效果,但这样我们就需要处理更多optional的内容,而且在一些无法修改接口实现的情况下无法使用。

场景二:Closure回调替代delegate

正如UIKit在Objective-C时代很多类新增了block回调替代简单的delegate,在Swift下因为Closure的使用比Objective-C的block简单很多,我们更常使用Closure回调去实现简单的delegate效果,例如我有三个按钮点击需要通知到其他对象,不使用delegate方式,我们可以声明三个Closure对象:

Class A {
var didTapButtonA: () -> () = {}
var didTapButtonB: (Int, URL?) -> () = { _, _ in }
var didTapButtonC: (Int, Int, Int, Int, URL?) -> () = { _, _, _, _, _ in }
}

然后在按钮触发的时候调用相应的Closure,而需要获取通知的对象只要实现这个属性就可以,就像这样:

let a = A()
a.didTapButtonA = {
// some logic code
}

不过我们可以看到didTapButtonBdidTapButtonC已经相当麻烦,问题依然是我们需要配合参数数量去实现空Closure,后续更新也同样需要同步更新。同样我们可以换成optional类型然后把默认值替换成nil,但同样地我们尽可能避免使用optional。

简化方案

首先我们创建一个名为Closure的struct:

public struct Closure {}

对于忽略回调,我们添加下面的静态方法:

static func ignore() {}
static func ignore(_: Any?) {}
static func ignore(_: Any?, _: Any?) {}
static func ignore(_: Any?, _: Any?, _: Any?) {}
static func ignore(_: Any?, _: Any?, _: Any?, _: Any?) {}
static func ignore(_: Any?, _: Any?, _: Any?, _: Any?, _: Any?) {}
static func ignore(_: Any?, _: Any?, _: Any?, _: Any?, _: Any?, _: Any?) {}

然后我们上面两个示例调用就可以简化成下面这个效果,我们不需要再管回调了多少个参数,同时其他人也很清晰这部分是我们有意忽略的。

sampleRequest(success: { (isSuccess, text) in
// do some complex thing
}, failure: Closure.ignore)

sampleRequest(success: Closure.ignore, failure: Closure.ignore)

我们没办法实现所有的情况,但6个参数已经能处理大部分的情况,正如Swift的Tuple的Equatable实现,也只实现了6个参数内的情况

而在Closure对象声明的时候,ignore就不太适合了,所以我选择empty作为方法名。同时我们需要利用到func的一个特性,我们可以用同一个名称调用但在不同场景下返回不同的对象。

static func empty() -> (() -> ()) { return {} }
static func empty() -> ((Any?) -> ()) { return { _ in } }
static func empty() -> ((Any?, Any?) -> ()) { return { _, _ in } }
static func empty() -> ((Any?, Any?, Any?) -> ()) { return { _, _, _ in } }
static func empty() -> ((Any?, Any?, Any?, Any?) -> ()) { return { _, _, _, _ in } }
static func empty() -> ((Any?, Any?, Any?, Any?, Any?) -> ()) { return { _, _, _, _, _ in } }
static func empty() -> ((Any?, Any?, Any?, Any?, Any?, Any?) -> ()) { return { _, _, _, _, _, _ in } }

然后声明就可以简化成这个效果:

class A {
var didTapButtonA: () -> () = Closure.empty()
var didTapButtonB: (Int, URL?) -> () = Closure.empty()
var didTapButtonC: (Int, Int, Int, Int, URL?) -> () = Closure.empty()
}

这样我们代码中空Closure的使用就全面统一和简化了,去掉了维护的成本。

其实我们还可以进一步简化,去掉struct Closure的结构把Closure.ignoreClosure.empty()方法抽成全局方法,但全局方法不太符合Swift的模式,同时也容易出现干扰本地变量和方法的情况,因此建议还是保留struct Closure结构。

  • 本文用到的代码均可以在GitHub项目里面找到。
  • 如果你对文中的内容有疑问或者建议,欢迎留言讨论。
  • 如果你觉得文中内容有价值,请转发让更多人可以看到。
  • 如果你喜欢这类型内容,欢迎follow我的MediumTwitter,我会持续发布更多有用内容给大家。

--

--

Galvin Li

A Tiny iOS developer who love to solve problems and make things better.