全局函数 Global functions
:无需特定类型范围就可以从任何地方访问的函数是一个古老的概念,在 C 和 Objective-C 等语言中很流行,但是在 Swift 中不建议使用,因为我们希望对它们进行很好的类型化和范围划分("swifty")。
global function
由于历史原因,Swift 标准库中仍然具有相当多的公共全局功能,其中一些功能至今仍然非常有用。让我们看一下zip()
和dump()
之类的函数。
zip()
zip
函数也许是最著名的全局函数,它使您可以采用两个或多个数组并将它们合并为一个元组序列。如果您需要同时迭代两件事,这将非常有用,因为如果没有zip
,则必须手动构建一个for
循环并分别访问每个数组中的每个索引。使用zip
可以使您以更实用的for-in
方式访问所有数组中的元素。
例如,如果我们有一个用户注册表单界面,并且我们想更新我们的textFields
以呈现从后端获取的验证结果的列表,我们可以执行以下操作:
func present(validationResults: [FieldValidationResult],
inTextFields textFields: [MyTextField]) {
for i in 0..<textFields.count {
let field = textFields[i]
let result = validationResults[i]
field.render(validationResult: result)
}
}
使用zip
,我们可以删除所有手动索引。
func present(validationResults: [FieldValidationResult],
inTextFields textFields: [MyTextField]) {
for (field, result) in zip(textFields, validationResults) {
field.render(validationResult: result)
}
}
zip
的返回类型是符合Sequence
的Zip2Sequence
对象,因此所有其他与序列相关的方法都适用于它,包括将其转换为真正的数组。
dump()
dump
函数可以很好地替代打印对象。尽管打印对象只是类型的description
或debugDescription
属性的语法糖,而dump
是Mirror(reflecting :)
的增强版本,它使用反射来打印对象的内容,这通常会产生更多信息,包括对象的层次结构。
class Foo: NSObject {
let bar: String = "bar"
}
let foo = Foo()
print(foo)
// <SwiftRocks.Foo: 0x1030b9250>
dump(foo)
// ▿ <SwiftRocks.Foo: 0x1030b9250> #0
// - super: NSObject
// - bar: "bar"
sequence()
全局sequence()
函数有点晦涩,但是它是一个非常酷的函数,可让您以更好的语法编写递归函数。
假设我们要更改子视图及其所有父视图的背景颜色。也许您会像这样建立一个while
循环:
var currentView: UIView? = self
while currentView != nil {
currentView?.backgroundColor = .green
currentView = currentView?.superview
}
这是sequence()
的最佳用例,因为此函数的目的是为您提供一个序列,该序列反复应用特定的闭包。由于此方法的递归内容 currentView = currentView?.superview
始终相同,因此我们可以使用sequence()
将其转换为简单的for
循环:
for view in sequence(first: self, next: { $0.superview } ) {
view.backgroundColor = .green
}
它的工作方式是sequence()
返回自定义的UnfoldFirstSequence
类型,这是Sequence
的简单包装,该包装不断在其next()
函数中反复应用闭包。
isKnownUniquelyReferenced()
isKnownUniquelyReferenced
函数接收一个类对象,并返回一个布尔值,该布尔值指示该对象是否仅被引用了一次,目的是使您能够对引用类型实现值语义。尽管结构本身就是值类型,但其中的内容可能不是。您可能知道,将类放入结构体并不意味着它将在赋值时复制:
class Foo: NSObject {
var bar: String = "bar"
}
struct FooHolder {
let foo: Foo = Foo()
var intValue: Int = 1
}
var fooHolder = FooHolder()
var fooHolder2 = fooHolder
fooHolder2.foo.bar = "bar2"
fooHolder2.intValue = 2
print(fooHolder.intValue)
// 1
print(fooHolder2.intValue)
// 2
print(fooHolder.foo.bar)
// bar2
print(fooHolder2.foo.bar)
// bar2
在此示例中,尽管fooHolder2
及其基础编号是与原始持有人不同的实体,但是基础类仍在它们之间共享。为了解决这个问题,我们可以使用isKnownUniquelyReferenced
检测何时访问此属性,并在必要时创建该类的新实例:
struct FooHolder {
private var _foo: Foo = Foo()
var foo: Foo {
mutating get {
if isKnownUniquelyReferenced(&_foo) {
return _foo
} else {
let newFoo = Foo()
newFoo.bar = _foo.bar
_foo = newFoo
return _foo
}
} set {
_foo = newValue
}
}
var intValue: Int = 1
}
您可能有兴趣知道,这正是 Swift 标准库如何实现对数组和字符串的写时复制(copy-on-write
)语义的实现——我在有关值类型的内存管理的文章中已经提到了这一点。
repeatElement()
repeatElement()
完全就是他表面上的意思。给定一个对象和一个数字,结果是一个可以重复的序列,为您提供该对象特定次数的数量。
let repeated: Repeated<String> = repeatElement("SwiftRocks", count: 3)
for value in repeated {
print(value)
}
//SwiftRocks
//SwiftRocks
//SwiftRocks
重复元素是Swift中的常见操作,尤其是填补Strings
和Arrays
中的空白。实际上,大多数这些类型甚至为此都有一个特定的初始化程序:
let array = [Int](repeating: 0, count: 10)
那么,为什么要使用repeatElement
?原因是性能。repeatElement()
的返回类型是Repeated<T>
序列类型,类似于Zip2Sequence
,它除了提供此“重复”功能外不执行任何操作。假设您想用另一个数字替换数字数组的特定部分;实现此目的的一种方法是将replaceSubrange
与另一个数组一起使用:
array.replaceSubrange(2...7, with: [Int](repeating: 1, count: 6))
print(array)
// [0, 0, 1, 1, 1, 1, 1, 1, 0, 0]
在这种情况下,[Int](repeating:)
的使用带来了必须初始化数组缓冲区的所有开销,而这在这里毫无用处。如果只需要重复功能,则使用repeatElement
会更好:
array.replaceSubrange(2...7, with: repeatElement(1, count: 6))
stride()
同样非常流行的是,将stride()
函数添加到Swift中,作为一种创建可以跳过某些元素的循环的方法,因为从swift 语言中删除了等效的 C 样式方法:
for (int i = 0; i < 10; i = 2) { ... }
现在,您可以使用stride()
实现相同的行为:
for i in stride(from: 0, to: 10, by: 2) {
//从0到9,跳过奇数。
}
stride()
的参数是符合Strideable
协议的参数,该协议表示可以表示距离概念的对象。例如,这是我们如何在Date
对象中添加“日差”的概念,以便可以在stride()
中使用它们:
extension Date: Strideable {
func advanced(by n: Int) -> Date {
return Calendar.current.date(byAdding: .day, value: n, to: self)!
}
func distance(to other: Date) -> Int {
return Calendar.current.dateComponents([.day], from: other, to: self).day!
}
}
let startDate = Date()
let finalDate = startDate.advanced(by: 5)
for date in stride(from: startDate, to: finalDate, by: 1) {
print(date)
}
// March 24th
// March 25th
// March 26th
// March 27th
// March 28th
(请注意,Date
已经实现了Strideable
方法的实现,该实现可以在几秒钟内完成,因此将其复制到项目中将不起作用。)
PS: 稍微修改一下,Date
默认实现了distance
和advanced
所以只需要补上一个:
/// A type that represents the distance between two values.
associatedtype Stride : Comparable, SignedNumeric
所以代码如下:
代码语言:javascript复制extension Date: Strideable {
typealias step = TimeInterval
}
let startDate = Date()
let finalDate = startDate.advanced(by: 5)
for date in stride(from: startDate, to: finalDate, by: 1) {
print(date)
}
//2020-05-06 12:48:15 0000
//2020-05-06 12:48:16 0000
//2020-05-06 12:48:17 0000
//2020-05-06 12:48:18 0000
//2020-05-06 12:48:19 0000
其他有用的函数
Math
-
max()
:返回参数的最大值 -
min()
:返回参数的最小值 -
abs()
:返回参数的绝对值(在竞争性编程问题中很有用)
Values
swap()
:交换两个对象的值。本文未在本节中单独提及,因为如果需要交换数组元素,则使用的正确方法是Array.swapAt()
。但是,在需要创建伪“aux”属性来保存值的其他情况下,仍然可以使用swap()
。
结论
我们可以看到,尽管这些方法都不是使事情发生的必要方法,但是使用它们可以使您编写的代码比以前的解决方案更易于维护,并且有时甚至可以提高性能。
译自
Useful Global Swift Functions