Swift中鲜为人知的功能

数组
这是使用数组文字语法或完整类型名称语法声明数组时获得的默认数据类型。 此数组类型已针对在Swift ArrayNSArray类型之间进行转换进行了优化。

 让水果= [“苹果”,“猕猴桃”,“梨”] 
让空= Array ()

它是迄今为止Swift标准库中使用最广泛的数组类型。 并且由于与Objective-C的自动桥接,对于大多数返回数组的API类型,这是推荐的选择。 这样,API的使用者就不必担心会返回意外的类型。 如果数组的元素已经是class@objc协议的实例,则从Array桥接到NSArray会占用O(1)时间和O(1)空间; 否则,将花费O( n )时间和空间。

但是,我们确实有其他一些类型的数组,在某些情况下可能会有用。 检查标准库中一些常用功能的实现以深入了解Swift的作者如何实现这些功能以及它们利用的数据类型类型是很有趣的。

连续数组
三种阵列类型中最快的。 ContiguousArray始终将其元素存储在内存的连续区域中(就像C旧式数组一样)。 这与标准Array类不同,后者可能根据元素类型使用连续内存或NSArray实例。 如果要存储class@objc协议类型,而无需桥接到Objective-C或调用Objective-C API,则ContiguousArray通常会更快。 实际上, ContiguousArray在Swift标准库中出现了41次。

grep “ContiguousArray<” * | wc -l

ContiguousArray有几种有趣的用法,例如, Sequence上的Swift的mapfilter函数使用ContiguousArray作为其实现的一部分。 让我们看一下标准库代码。

Swift标准库中的地图和过滤器

由于我们在每个元素上使用map / filter函数,因此我们预先知道数组的大小。 声明一个大小为ContiguousArray的数组,可以避免在处理map函数时扩展数组以容纳更多元素的需要。 这是效率优化。 只有在填充数组超出初始容量后,我们才使用Swift的append函数,该函数会根据需要自动调整数组的大小。

数组切片
ArraySlice类型使您可以快速有效地对较大数组的各个部分执行操作。 ArraySlice提供与Array相同的接口,允许大多数阵列操作。 ArraySlice通过在现有阵列的一部分上显示视图来实现此目的。 这使您可以执行诸如将数组拆分为不同部分的操作,而无需实际创建新数组或复制数组元素。

  luckyNumbers = [38,38,41,4,35,15,41,42,12,29,15,55,69,76,25,13] 
 让中点= luckyNumbers.count / 2 
让firstHalf = luckyNumbers [.. <midpoint]
让secondHalf = luckyNumbers [中点…]
  //上半场 
[38、38、41、4、35、15、41、42]
  //下半场 
[12、29、15、55、69、76、25、13]

我们可以像使用普通数组一样使用ArraySlice一半,包括mapreducesplit等函数。

 让firstHalfSum = firstHalf.reduce(0,+) // firstHalfSum = 254 
让secondHalfSum = secondHalf.reduce(0,+) // secondHalfSum = 294

这样的事情在哪里有用? Binary Search可能是一个书呆子示例(您可以并且应该使用Range做到这一点)。 使用切片的示例:

使用ArraySlice进行二进制搜索

Swift标准库对ArraySlice的主要用途是在Sequence扩展中和Array类型上。 在标准库中使用ArraySlice有效地预分配存储的情况subscript在使用Range时, ArraySlice主要用作与范围相关的函数(例如splitsubscript的返回类型。 我们来看一下Swift标准库中split的示例。

从Swift标准库拆分

用一个示例说明split ,我们有一个幸运数字数组,我们的枢轴元素为15。枢轴位于位置5和10,因此我们的数组将被拆分为3个单独的ArraySlices包含约15个元素(不包括枢轴) )。

 让luckyNumbers = [38、38、41、4、35、15、41、42、12、29、15、55、69、76、25、13] 
 让拆分= luckyNumbers.split(分隔符:15) 
  for(index,split)in splits.enumerated(){ 
print(“ split [\(index)]”,split)
}
  split [0] [38,38,41,4,35] 
split [1] [41,42,12,29]
split [2] [55,69,76,25,13]

ArraySlice是使用Array的有效方法,而不会在内存分配和复制中造成不必要的开销。 而且,您可以使用Array.init()方法轻松地将ArraySlice转换为Array

let arrayFromSlice = Array(splits[0])

ArraySlice一些重要警告是切片的索引保留了原始数组的索引。 这意味着在ArraySlice上执行操作时需要获取开始和结束索引

  var startIndex = secondHalf.startIndex // 8 
var endIndex = secondHalf.endIndex — 1 // 15

为了安全地引用切片的开始索引和结束索引,请始终使用startIndexendIndex属性而不是特定值。 另外,还需要考虑内存使用情况,只要存在一个Array的切片,即使尝试删除它,原始Array仍将保留在内存中。 从Apple的文档中:

要点:不建议长期存储ArraySlice实例。 即使原始数组的生命周期结束后,切片也会保留对较大数组整个存储的引用,而不仅仅是对其呈现的部分的引用。 因此,切片的长期存储可能会延长原本无法访问的元素的寿命,这些元素似乎是内存和对象泄漏。

SwapAt(_:_ :)函数
ArrayswapAtDictionary都实现了swapAt方法,该方法允许按索引就地交换数组元素。

  var swapable = [4,5] 
swapable.swapAt(0,1)
打印(可交换)// [5,4]

在标准库的Sort ,此隐晦的方法用作私有插入排序方法的一部分,在MutableCollection ,有趣的是在SequenceAlgorithms用于reversedreversed方法调用的工作方式类似于旧式C方法,以反向字符串,在此您交换第一项和最后一项,然后向内交换其他元素,直到反向完成为止。

与Swift标准库相反

LexicographicallyPrecedes(__ :)函数
SequenceAlgorithmslexicographicallyPrecedes函数是另一个有趣的标准库功能。 它返回truefalse取决于一个序列在lexicographically上是否执行另一个序列。 但是,当我们想到词典词典中的单词时,我们倾向于认为它是否是字母顺序的。 字母顺序将是词典功能的子集。 当应用于数字时,字典顺序是数字顺序递增。 根据维基百科:

在数学中, 词典顺序词典顺序 (也称为词典顺序 ,词典顺序 ,字母顺序词典产品)是对单词根据其组成字母的字母顺序进行字母排序的方式的概括。

让我们看一下某些用例中的函数并查看结果。 我们可以比较诸如batcat单词,并确定字典顺序(我们希望bat出现在cat之前)。 我们可以比较一个数字序列,例如[1,2,3][3,4,5] ,我们自然会认识到[1,2,3][3,4,5]之前。 我们还可以比较日期字符串,并得到我们在这种比较中自然期望的结果。

函数的一个参数让我惊讶,那就是使用谓词来检查序列中的项是否按升序排列。 我们在检查Swift标准库对该函数的实现时可以看到这一点。 minmax函数中也使用areInIncreasingOrder谓词。 这是一小段简洁的代码的一个很好的示例,该代码执行单个功能,类似于swapAt

请注意, 请勿使用lexicographicallyPrecedes比较实际日期字符串。 Apple使用Swift DateDateComponentsCalendar类型提供了更好的日期比较和修改工具。 这些类型将使您的约会生活变得更加轻松。 也不要使用它对字符串进行排序,因为Apple文档明确提到:

此方法实现了字典顺序的数学概念,该概念与Unicode没有关系。 如果您要对要呈现给最终用户的字符串进行排序,请使用执行本地化比较的`String` API。

Swift标准库在幕后提供了许多有趣的功能和实现。 我鼓励您花一些时间研究核心语言的实现,以此来激励您并加深您的理解。 您可能会发现它可以帮助您编写Swiftier代码。 请记住,Swift是开源的,您可以从源代码免费下载它。

资料来源
Luca Bravo在Unsplash上​​的照片