美文网首页Swift学习iOS
是什么使代码 “Swifty”? —— Fast

是什么使代码 “Swifty”? —— Fast

作者: 韦弦Zhy | 来源:发表于2020-02-25 14:57 被阅读0次

    Swift的官方网站上的About页面列出了三个关键字:

    • 安全(Safe):为了最大限度地减少开发人员的错误;
    • 迅速(Fast):执行的速度要快;
    • 表现力(Expressive):因为Swift的目标是尽可能清晰易懂。

    是什么使代码 “Swifty”? —— Safe 介绍了如何有选择地使用类型系统的各个方面和功能,以使我们的代码更易于理解和使用。
    是什么使代码 “Swifty”? —— Expressive 介绍了如何使用表达性命名和API设计传达我们的代码意图

    Swifty Code —— Fast

    性能之路(The path to performance)

    Swift的第二个核心目标,就是要快一些,总的来说,这有点棘手。毕竟,编写高性能代码的主要部分在于测量,微调和再次测量。但是,使我们的代码在性能方面与Swift本身更加一致的一种方法是,充分利用标准库所提供的功能——特别是在处理集合(例如字符串)时。

    就像我们在 Swift:字符串解析Swift:集合切片中看过一样,Swift标准库针对性能进行了高度优化,并且使我们能够以高效的方式执行许多常见的集合操作-假设我们使用正确的API。

    例如,从字符串中删除一组特定字符的一种常见方法是使用旧的ReplacementOccurences(of:with :)API,该API是Swift的String类型从其表亲Objective-C的NSString继承而来的。在这里,我们使用了对该API的一系列调用,以通过删除一组特殊字符来清理字符串:

    let sanitizedString = string
        .replacingOccurrences(of: "@", with: "")
        .replacingOccurrences(of: "#", with: "")
        .replacingOccurrences(of: "<", with: "")
        .replacingOccurrences(of: ">", with: "")
    

    上面的实现的问题是,它将导致我们的字符串进行4次单独的迭代——使用较短的字符串,或者在不经常遇到的代码路径中进行上述操作时,这可能不是问题,但可能会变成当我们需要最大性能时的瓶颈。

    值得庆幸的是,Swift通常不需要我们在性能代码和优雅代码之间进行选择,我们要做的就是切换到一种更合适的API,在Set中这个API仅通过我们的字符串一次即可删除其中包含的每个字符。,像这样:

    let charactersToRemove: Set<Character> = ["@", "#", "<", ">"]
    string.removeAll(where: charactersToRemove.contains)
    

    因此,从性能的角度来看,使我们的代码更“Swifty”,有时我们要做的就是探索标准库在面对给定任务时必须提供的内容,尤其是在集合,机会方面相当高,因为有一个优雅,简单的API,它还为我们提供了出色的性能特征。

    文章来自 John SundellWhat makes code “Swifty”?中关于Fast的内容

    附几个简单性能优化例子:
    • 在这篇文章也是用到了文中这个方法iOS - DeviceToken 解析来解析Token
    • swift filter会创建全新的数组,且会对所有元素进行操作,例如:
    bigArray.filter { someCondition }.count > 0
    

    写成如下形式性能更好:

    bigArray.contains { someCondition }
    

    这种做法会比原来快得多,主要因为两个方面:它不会去为了计数而创建一整个全新的数组, 并且一旦找到了第一个匹配的元素,它就将提前退出。一般来说,你只应该在需要所有结果时才去选择使用 filter

    • swift Stringprefix总是从头开始,例如:
    extension String {
    var allPrefixes1: [Substring] {
    return (0...self.count).map(self.prefix) }
    }
    let hello = "Hello"
    hello.allPrefixes1 // ["", "H", "He", "Hel", "Hell", "Hello"]
    

    这段代码看上去简单,但是它非常低效。首先,它会遍历一次字符串,来计算其⻓度,这没什 么大问题。但是,之后 n + 1 次对 prefix 的调用中,每一次都是一个 O(n) 操作,这是因为 prifix总是要从头开始工作,然后在字符串上经过所需要的字符个数。在一个线性复杂度的处 理中运行另一个线性复杂度的操作,意味着算法复杂度将会是 O(n2)。随着字符串⻓度的增⻓, 这个算法所花费的时间将以平方的方式增加。

    如果可能的话,一个高效的字符串算法应该只对字符串进行一次遍历,而且它应该操作字符串 的索引,用索引来表示感兴趣的子字符串。这里是相同算法的另一个版本:

    extension String {
    var allPrefixes2: [Substring] {
    return [""] + self.indices.map { index in self[...index] } }
    }
    let hello = "Hello"
    hello.allPrefixes2 // ["", "H", "He", "Hel", "Hell", "Hello"]
    

    上面的代码依然需要迭代一次字符串,以获取索引的集合 indices。不过,一旦这个过程完成, map 中的下标操作就是 O(1) 复杂度的。这使得整个算法的复杂度得以保持在 O(n)。

    例子来自《Swift进阶》一书原作者【德】Chris Eidhof(克里斯·安道夫) 【德】Ole Begemann (奥勒·毕格曼) 【德】Airspeed Velocity (空速网站),中文版由王巍译

    是什么使代码 “Swifty”? —— Safe 介绍了如何有选择地使用类型系统的各个方面和功能,以使我们的代码更易于理解和使用。
    是什么使代码 “Swifty”? —— Expressive 介绍了如何使用表达性命名和API设计传达我们的代码意图

    相关文章

      网友评论

        本文标题:是什么使代码 “Swifty”? —— Fast

        本文链接:https://www.haomeiwen.com/subject/oxctchtx.html