Swift 惯用语法

作者:Erica Sadun,原文链接,原文日期:2017-01-24
译者:星夜暮晨;校对:Crystal Sun;定稿:CMB

久而久之,Swift 发展出一种别具一格的专用语法——即一组与其他语言相差甚远的基本惯用语法 (core idioms)。许多来自 Objective-C、Ruby、Java、Python 等等语言的开发者纷纷投向 Swift 的麾下。数日前,Nicholas T Chambers 让我帮他来磨练这门新习得的语言。他通过将 Ruby 代码移植为 Swift 的方式,来构建自己基本的编程技能。他所移植的代码是这样的:

ruby
def find_common(collection)
    sorted = {}
    most = [0,0]

    for item in collection do
        if not sorted.key? item then
            sorted[item] = 0
        end

        sorted[item] += 1

        if most[1] < sorted[item] then
            most[0] = item
            most[1] = sorted[item]
        end
    end

    return most
end

然后他所尝试改编而成的 Swift 代码为:

func find_common(items: [Int]) -> [Int] {
    var sorted = [Int: Int]()
    var most = [0, 0]

    for item in items {
        if sorted[item] == nil {
            sorted[item] = 0
        }

        sorted[item]! += 1

        if most[1] < sorted[item]! {
            most[0] = item
            most[1] = sorted[item]!
        }
    }

    return most
}

除了一些强制解包的代码外,这两者之间几乎没有任何区别。我对 Ruby 并不是了如指掌,但是这两段代码感觉仍然还是 C 语言的风格,并且也一点都不函数化(是函数式编程领域的意思,而不是「无法工作」的意思)。

我知道 Ruby 支持类似 reduce 之类的操作,但是这里我们并没有看到。当我刚开始学习 Swift 的时候,我做的第一件事情就是将大篇大篇的 Ruby 函数式调用方法用 Swift 实现出来。当然时至今日,仍然有很多诸如 selectrejectdelete_ifkeep_if 之类的功能仍然等着我用 Playground 实现出来,感觉无穷无尽的样子。不过讲道理,这种做法非常适合学习 Swift。

下面就是经我建议之后重写的版本:

import Foundation

extension Array where Element: Hashable {
    /// Returns most popular member of the array
    ///
    /// - SeeAlso: https://en.wikipedia.org/wiki/Mode_(statistics)
    ///
    func mode() -> (item: Element?, count: Int) {
        let countedSet = NSCountedSet(array: self)
        let counts = countedSet.objectEnumerator()
            .map({ (item: $0 as? Element, count: countedSet.count(for: $0)) })
        return counts.reduce((item: nil, count: 0), {
            return ($0.count > $1.count) ? $0 : $1
        })
    }
    
}

就某些方面而言,这种重构显然是作弊了,因为我「借用」了 NSCountedSet 的帮助,不过我觉得用 Swift 来编程并不意味着我们必须要将 Foundation 拒之门外。在我看来,使用计数集 (counted set) 正是这段代码的任务所在:「假设我们有一个类型随机的列表(尽管类型是相同的),列表当中的顺序是随机的。那么该如何找到这个列表当中出现次数最多的元素呢?」。

下面是我关于重构的一些建议和想法:

  • 充分利用各种库 (Leverage Libraries)。在迁移到 Swift 的时候,您需要考虑 Foundation 以及 Swift Foundation 类型是否会让您的重构更加方便快捷。这里计数集便是一个很好的例子,因为它本身就可以自行完成全部的成员分组和计数。不过我希望能够拥有一个原生版本的计数集,这样就不用操心于令人疯狂的对象枚举 (objectEnumerator),此外如果没有指定可哈希元素的时候,代码仍然可以通过编译的情况了。
  • 拥抱泛型 (Embrace Generics)。尝试挑战一下将示例中的列表换成随机类型。将列表硬编码 (hardcoding) 为 Int 并不是个好方法。因此,一旦您意识到要实现的功能需要用于多种类型的时候,请选择将泛型引入您的解决方案中。
  • 必要时考虑使用协议 (Consider Protocols)。计数集的原生版本不管怎么说也得是 Hashable 的,就如 Swift 原生的 Set 数据类型一样。这里我添加了一些限制条件,但是它编译和运行的结果和我们的预期并不一致。
  • 活用函数式编程 (Live Functionally)。所有类似「从列表中找寻某种类型的元素」的操作无不在明示着让我去使用函数式编程来解决。如果在对列表进行迭代的时候,需要用变量来存储中间状态的话,那么可以考虑使用 Swift 最基本的函数式调用操作:map/reduce/filter,从而消除冗余的显式状态 (explicit state)。
  • 避免使用全局函数 (Avoid Global Functions)。我觉得这段代码最好是用集合扩展的方式进行实现,而不是使用独立的函数。mode 函数基本上是对数组进行描述和操作的。这段代码实现将作为 Array 的一部分存在。我甚至觉得,我应该将其实现为一个属性,而不是一个函数,因为列表的 mode 功能应该是数组的本质所在 (intrinsic quality)。不过关于这一点,我还有些举棋不定。
  • 别忘了编写测试以及文档 (Think Tests and Documents)。在编写代码之前就考虑如何编写测试用例以及文档,这已经是 Swift 开发的核心所在。这里我添加了一些相关的文本标记。不过我还没有添加相关的测试。
  • 使用良好的 Swift 语法规范 (Prefer Good Swiftsmanship)。首先,在我对代码整体进行思考之前,我陷入了对语法细节的桎梏当中,比如说「使用条件绑定」以及「键入变量/尽可能使用字面量」等等。不过随着我花了大量的心思来思索之后,我对函数式编程的看法发生了改变,但是这并不意味着基本 Swift 语法规范就可以被忽略掉。

世间有很多事情既需要顾全大局,也需要深入细节 (A lot of this falls into the big picture little picture dichotomy)。在学习 Swift 的时候,您可能希望从细节开始学习:学习可空值的原理、学习如何正确的使用可空值、学习如何使用函数式编程等等,从而一直学习到如何创建测试、如何编写文档、如何利用协议和泛型。要学的东西实在是纷繁复杂,很难一步登天。

这些知识的基础之上,又还有基本的 API 用法,这使得学习 Swift 变得更加困难。对于那些刚刚接触苹果开发的人而言,即便他们具备了现代编程语言的基础知识,但是要区分出 Swift 原生类型和 Cocoa 类型的区别并且掌握 Cocoa/Cocoa Touch API 都将是一个重大的挑战。

正因如此,用「更 Swift 化」的方式来编写代码,不仅意味着要使用约定的惯用语法,同时还意味着要记住和使用语言所处平台的相关特性。我希望计数集(以及其他 Cocoa Foundation 当中没被 Swift 原生化的类型)能够成为 Swift 的原生部分。

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,067评论 4 62
  • 知识点综述: 部分容器数据结构: 代码中vector大部分函数都已经实现。 下起了雨,今天早上没有去跑步。 vec...
    知识学者阅读 501评论 0 2
  • 1. 半夜两点多的时候,小安给我打来电话。我被铃声惊醒的时候恨不得把他给掐死:你特么不想睡还要拖老子下水! 小安说...
    罪里杨阅读 269评论 0 0
  • 一天当中,有太阳升起的时候,也有下沉的时候。人生也一样,有白天和黑夜,只是不会像真正的太阳那样,有定时的日出和日落...
    糖二傻子阅读 122评论 0 0