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是一个很好的工具。用表达式代替数值,使我们能够减少冗长和多余,同时也可能获得更好的性能。

感谢阅读! 🚀

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

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