SwiftAPI设计之使用@autoclosure

原文链接:Using @autoclosure when designing Swift APIs

Swift的@autoclosure属性能让你定义一个自动被闭包的参数。它的主要作用是推迟表达式(可能代价高昂)的执行,从而避免在传递参数时就直接执行。

assert

在Swift标准库中的assert函数就使用了@autoclosure属性。我们都知道断言只会在debug模式下被触发,因此在release模式下是没有必要执行断言表达式的。assert定义如下

func assert(_ expression: @autoclosure () -> Bool, 
            _ message: @autoclosure () -> String) {
    guard isDebug else {
        return
    }

    // 在assert内部,我们可以把表达式当做一个正常的闭包来使用
    if !expression() {
        assertFailure(message())
    }
}

上面是我简单模拟assert的实现,真正的实现在这里

@autoclosure的好处在于它对调用方没有任何影响。如果assert使用正常的闭包来定义的话,那么你就必须这么使用它:

assert({ someCondition() }, { "Hey, it failed!" })

但是现在,你可以像调用非闭包参数一样调用它:

assert(someCondition(), "Hey it failed!")

接下来,让我们来看看如何在自己的代码中使用@autoclosure属性,来使我们的API更友好。

内联赋值

@autoclosure可以在函数调用中内联表达式。我们能利用它做一些事情,比如传递赋值表达式作为参数。我们看看下面这个可能有用的例子。

UIView.animate(withDuration: 0.25) {
    view.frame.origin.y = 100
}

使用@autoclosure,我们可以编写一个自动创建动画闭包并执行它的动画函数,如下所示:

func animate(_ animation: @autoclosure(escaping) () -> (),
             duration: TimeInterval = 0.25) {
    UIView.animate(withDuration: duration, animations: animation)
}

现在我们可以使用简单的函数调用来执行动画,而不需要额外的{}语法:

animate(view.frame.origin.y = 100)

使用@autoclosure,我们可以真正减少动画代码的冗长度,而不会牺牲可读性或变现力🎉。

使用表达式传递错误

我发现@autoclosure的另一个非常有用的情况:编写处理错误的代码。比如,假设我们要在Optional上添加一个扩展,使我们能够在解包它出错时抛出异常。这样我们可以要求Optional是非nil,否则将抛出异常。如下所示:

extension Optional {
    func unwrapOrThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
        guard let value = self else {
            throw errorExpression()
        }
        return value
    }
}

类似于assert的实现,我们只会在需要的时候执行表达式,而不是每次尝试解包时都要执行。现在我们可以像这样使用我们的unwrapOrThrow

let name = try argument(at: 1).unwrapOrThrow(ArgumentError.missingName)

使用默认值做类型推断

我发现的最后的使用场景是从dictionarydatabase或者UserDefaults中提取可选值。

通常,当从一个没有指明特定类型的字典中提取一个值并提供一个默认值时,你必须这么写:

let coins = (dictionary["numberOfCoins"] as? Int) ?? 100

这种方式难以阅读,并且有很多复杂的语法糖。使用@autoclosure,我们可以定义一个API,来让我们想下面这样实现同样的功能:

let coins = dictionary.value(forKey: "numberOfCoins", defaultValue: 100)

从上面,我们可以看到默认值可以拿来做类型推断,而不需要指定类型来完成类型转换。很简洁👍

让我们来看看如何编写这个API:

extension Dictionary where Value == Any {
    func value<T>(forKey key: Key, defaultValue: @autoclosure () -> T) -> T {
        guard let value = self[key] as? T else {
            return defaultValue()
        }

        return value
    }
}

再次强调,我们使用@autoclosure来避免每次调用方法是都执行默认值表达式。

结论

减少冗长总是需要仔细考虑的事情。 我们的目标应该始终是编写富有表现力,易于阅读的代码,所以我们需要确保在设计低冗余性API时不会在使用时丢掉重要信息。

我认为在适当的情况下使用@autoclosure是一个很好的工具。用表达式代替数值,使我们能够减少冗长和多余,同时也可能获得更好的性能。

感谢阅读! 🚀

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,347评论 19 139
  • 86.复合 Cases 共享相同代码块的多个switch 分支 分支可以合并, 写在分支后用逗号分开。如果任何模式...
    无沣阅读 5,293评论 1 5
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 175,254评论 25 709
  • 本章将会介绍 闭包表达式尾随闭包值捕获闭包是引用类型逃逸闭包自动闭包枚举语法使用Switch语句匹配枚举值关联值原...
    寒桥阅读 5,438评论 0 3
  • 今天睡午觉的时候,忽然,一阵狂风吹来,原本安静的教室立刻热闹起来。我被惊醒了,但是我没有睁眼,细细分辨着声音,...
    遇见蝴蝶阅读 1,859评论 0 2