Swift: 有用的标准库全局函数

2020-05-18 16:29:29 浏览数 (1)

全局函数 Global functions无需特定类型范围就可以从任何地方访问的函数是一个古老的概念,在 C 和 Objective-C 等语言中很流行,但是在 Swift 中不建议使用,因为我们希望对它们进行很好的类型化和范围划分("swifty")。

global function

由于历史原因,Swift 标准库中仍然具有相当多的公共全局功能,其中一些功能至今仍然非常有用。让我们看一下zip()dump()之类的函数。

zip()

zip函数也许是最著名的全局函数,它使您可以采用两个或多个数组并将它们合并为一个元组序列。如果您需要同时迭代两件事,这将非常有用,因为如果没有zip,则必须手动构建一个for循环并分别访问每个数组中的每个索引。使用zip可以使您以更实用的for-in方式访问所有数组中的元素。

例如,如果我们有一个用户注册表单界面,并且我们想更新我们的textFields以呈现从后端获取的验证结果的列表,我们可以执行以下操作:

代码语言:javascript复制
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,我们可以删除所有手动索引。

代码语言:javascript复制
func present(validationResults: [FieldValidationResult],
             inTextFields textFields: [MyTextField]) {
    for (field, result) in zip(textFields, validationResults) {
        field.render(validationResult: result)
    }
}

zip的返回类型是符合SequenceZip2Sequence对象,因此所有其他与序列相关的方法都适用于它,包括将其转换为真正的数组。

dump()

dump函数可以很好地替代打印对象。尽管打印对象只是类型的descriptiondebugDescription属性的语法糖,而dumpMirror(reflecting :)的增强版本,它使用反射来打印对象的内容,这通常会产生更多信息,包括对象的层次结构。

代码语言:javascript复制
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循环:

代码语言:javascript复制
var currentView: UIView? = self
while currentView != nil {
    currentView?.backgroundColor = .green
    currentView = currentView?.superview
}

这是sequence()的最佳用例,因为此函数的目的是为您提供一个序列,该序列反复应用特定的闭包。由于此方法的递归内容 currentView = currentView?.superview 始终相同,因此我们可以使用sequence()将其转换为简单的for循环:

代码语言:javascript复制
for view in sequence(first: self, next: { $0.superview } ) {
    view.backgroundColor = .green
}

它的工作方式是sequence()返回自定义的UnfoldFirstSequence类型,这是Sequence的简单包装,该包装不断在其next()函数中反复应用闭包。

isKnownUniquelyReferenced()

isKnownUniquelyReferenced函数接收一个类对象,并返回一个布尔值,该布尔值指示该对象是否仅被引用了一次,目的是使您能够对引用类型实现值语义。尽管结构本身就是值类型,但其中的内容可能不是。您可能知道,将类放入结构体并不意味着它将在赋值时复制:

代码语言:javascript复制
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检测何时访问此属性,并在必要时创建该类的新实例:

代码语言:javascript复制
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()完全就是他表面上的意思。给定一个对象和一个数字,结果是一个可以重复的序列,为您提供该对象特定次数的数量。

代码语言:javascript复制
let repeated: Repeated<String> = repeatElement("SwiftRocks", count: 3)
for value in repeated {
    print(value)
}
//SwiftRocks
//SwiftRocks
//SwiftRocks

重复元素是Swift中的常见操作,尤其是填补StringsArrays中的空白。实际上,大多数这些类型甚至为此都有一个特定的初始化程序:

代码语言:javascript复制
let array = [Int](repeating: 0, count: 10)

那么,为什么要使用repeatElement?原因是性能。repeatElement()的返回类型是Repeated<T>序列类型,类似于Zip2Sequence,它除了提供此“重复”功能外不执行任何操作。假设您想用另一个数字替换数字数组的特定部分;实现此目的的一种方法是将replaceSubrange与另一个数组一起使用:

代码语言:javascript复制
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会更好:

代码语言:javascript复制
array.replaceSubrange(2...7, with: repeatElement(1, count: 6))

stride()

同样非常流行的是,将stride()函数添加到Swift中,作为一种创建可以跳过某些元素的循环的方法,因为从swift 语言中删除了等效的 C 样式方法:

代码语言:javascript复制
for (int i = 0; i < 10; i  = 2) { ... }

现在,您可以使用stride()实现相同的行为:

代码语言:javascript复制
for i in stride(from: 0, to: 10, by: 2) {
    //从0到9,跳过奇数。
}

stride()的参数是符合Strideable协议的参数,该协议表示可以表示距离概念的对象。例如,这是我们如何在Date对象中添加“日差”的概念,以便可以在stride()中使用它们:

代码语言:javascript复制
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默认实现了distanceadvanced所以只需要补上一个:

代码语言:javascript复制
/// 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

0 人点赞