普通视图

发现新文章,点击刷新页面。
昨天以前SwiftGG

宏定义与可选括号

作者 SwiftGG
2019年9月27日 00:00

作者:Mike Ash,原文链接,原文日期:2015-03-20
译者:俊东;校对:numbbbbbNemocdz;定稿:Pancf

前几天我遇到了一个有趣的问题:如何编写一个 C 语言预处理器的宏,删除包围实参的括号?

今天的文章,将为大家分享我的解决方案。

起源

C 语言预处理器是一个相当盲目的文本替换引擎,它并不理解 C 代码,更不用说 Objective-C 了。它的工作原理还算不错,可以应付大部分情况,但偶尔也会出现判断失误。

这里举个典型的例子:

XCTAssertEqualObjects(someArray, @[ @"one", @"two" ], @"Array is not as expected");

这会无法编译,并且会出现非常古怪的错误提示。预处理器查找分隔宏参数的逗号时,没能将数组结构 @ [...] 中的东西理解为一个单一的元素。结果代码尝试比较 someArray@[@"one"。断言失败消息 @"two"]@"Array is not as expected" 是另外的实参。这些半成品部分用于 XCTAssertEqualObjects 的宏扩展中,生成的代码当然错得离谱。

要解决这个问题也很容易:添加括号就行。预编译器不能识别 [],但它确实知道 () 并且能够理解应该忽略里面的逗号。下面的代码就能正常运行:

XCTAssertEqualObjects(someArray, (@[ @"one", @"two" ]), @"Array is not as expected");

在 C 语言的许多场景下,你添加多余的括号也不会有任何区别。宏扩展开之后,生成的代码虽然在数组文字周围有括号,但没有异常。你可以写搞笑的多层括号表达式,编译器会愉快地帮你解析到最里面一层:

NSLog(@"%d",((((((((((42)))))))))));

甚至将 NSLog 这样处理也行:

((((((((((NSLog))))))))))(@"%d",42);

在 C 中有一个地方你不能随意添加括号:类型(types)。例如:

int f(void); // 合法
(int) f(void); // 不合法

什么时候会发生这种情况呢?这种情况并不常见,但如果你有一个使用类型的宏,并且类型包含的逗号不在括号内,则会出现这种情况。宏可以做很多事情,当一个类型遵循多个协议时,在 Objective-C 中可能出现一些类型带有未加括号的逗号;当使用带有多个模板参数的模板化类型时,在 C++ 中也可能出现。举个例子,这有一个简单的宏,创建从字典中提供静态类型值的 getter

#define GETTER(type,name) \
- (type)name { \
return [_dictionary objectForKey: @#name]; \
}

你能这样使用它:

@implementation SomeClass {
NSDictionary *_dictionary;
}

GETTER(NSView *,view)
GETTER(NSString *,name)
GETTER(id<NSCopying>,someCopyableThing)

到目前为止没问题。现在假设我们想要创建一个遵循两个协议的类型:

GETTER(id<NSCopying,NSCoding>,someCopyableAndCodeableThing)

哎呀!宏不起作用了。而且添加括号也无济于事:

GETTER((id<NSCopying,NSCoding>),someCopyableAndCodeableThing)

这会产生非法代码。这时我们需要一个删除可选括号的 UNPAREN 宏。将 GETTER 宏重写:

#define GETTER(type,name) \
- (UNPAREN(type))name { \
return [_dictionary objectForKey: @#name]; \
}

我们该怎么做呢?

必须的括号

删除括号很容易:

#define UNPAREN(...) __VA_ARGS__
#define GETTER(type,name) \
- (UNPAREN type)name { \
return [_dictionary objectForKey: @#name]; \
}

虽然看上去很扯,但这的确能运行。预编译器将 type 扩展为 (id <NSCopying,NSCoding>),生成 UNPAREN (id<NSCopying, NSCoding>)。然后它会将 UNPAREN 宏扩展为 id <NSCopying,NSCoding>。括号,消失!

但是,之前使用的 GETTER 失败了。例如,GETTER(NSView *,view) 在宏扩展中生成 UNPAREN NSView *。不会进一步扩展就直接提供给编译器。结果自然会报编译器错误,因为 UNPAREN NSView * 是无法编译的。这虽然可以通过编写 GETTER((NSView *),view) 来解决,但是被迫添加这些括号很烦人。这样的结果可不是我们想要的。

宏不能被重载

我立刻想到了如何摆脱剩余的 UNPAREN。当你想要一个标识符消失时,你可以使用一个空的 #define,如下所示:

#define UNPAREN

有了这个,a UNPAREN b 的序列变为 a b。完美解决问题!但是,如果已经存在带参数的另一个定义,则预处理器会拒绝此操作。即使预处理器可能选择其中一个,它也不会同时存在两种形式。如果可行的话,这能有效解决我们的问题,但可惜的是并不允许:

#define UNPAREN(...) __VA_ARGS__
#define UNPAREN
#define GETTER(type,name) \
- (UNPAREN type)name { \
return [_dictionary objectForKey: @#name]; \
}

这无法通过预处理器,它会由于 UNPAREN 的重复 #define 而报错。不过,它引导我们走上了成功的道路。现在的瓶颈是怎么找出一种方法来实现相同的效果,而不会使两个宏具有相同的名称。

关键

最终目标是让 UNPAREN(x)UNPAREN((x)) 结果都是 x。朝着这个目标迈出的第一步是制作一些宏,其中传递 x(x) 产生相同的输出,即使它并不确定 x 是什么。这可以通过将宏名称放在宏扩展中来实现,如下所示:

#define EXTRACT(...) EXTRACT __VA_ARGS__

现在如果你写 EXTRACT(x),结果是 EXTRACT x。当然,如果你写 EXTRACT x,结果也是 EXTRACT x,就像没有宏扩展的情况。这仍然给我们留下一个 EXTRACT。虽然不能用 #define 直接解决,但这已经进步了。

标识符粘合

预处理器有一个操作符 ##,它将两个标识符粘合在一起。例如,a ## b 变为 ab。这可以用于从片段构造标识符,但也可以用于调用宏。例如:

#define AA 1
#define AB 2
#define A(x) A ## x

从这里可以看到,A(A) 产生 1A(B) 产生 2

让我们将这个运算符与上面的 EXTRACT 宏结合起来,尝试生成一个 UNPAREN 宏。由于 EXTRACT(...) 使用前缀 EXTRACT 生成实参,因此我们可以使用标识符粘合来生成以 EXTRACT 结尾的其他标记。如果我们 #define 那个新标记为空,那就搞定了。

这是一个以 EXTRACT 结尾的宏,它不会产生任何结果:

#define NOTHING_EXTRACT

这是对 UNPAREN 宏的尝试,它将所有内容放在一起:

#define UNPAREN(x) NOTHING_ ## EXTRACT x

不幸的是,这并不能实现我们的目标。问题在操作顺序上。如果我们写 UNPAREN((int)),我们将会得到:

UNPAREN((int))
NOTHING_ ## EXTRACT (int)
NOTHING_EXTRACT (int)
(int)

标示符粘合太早起作用,EXTRACT 宏永远不会有机会扩展开。

可以使用间接的方式强制预处理器用不同的顺序判断事件。我们可以制作一个 PASTE 宏,而不是直接使用 ##

#define PASTE(x,...) x ## __VA_ARGS__

然后我们将根据它编写 UNPAREN

#define UNPAREN(x)  PASTE(NOTHING_,EXTRACT x)

仍然不起作用。情况如下:

UNPAREN((int))
PASTE(NOTHING_,EXTRACT (int))
NOTHING_ ## EXTRACT (int)
NOTHING_EXTRACT (int)
(int)

但更接近我们的目标了。序列 EXTRACT(int) 显然没有触发标示符粘合操作符。我们必须让预处理器在它看到 ## 之前解析它。可以通过另一种方式间接强制解析它。让我们定义一个只包装 PASTEEVALUATING_PASTE 宏:

#define EVALUATING_PASTE(x,...) PASTE(x,__VA_ARGS__)

现在让我们用UNPAREN

#define UNPAREN(x) EVALUATING_PASTE(NOTHING_,EXTRACT x)

这是展开之后:

UNPAREN((int))
EVALUATING_PASTE(NOTHING_,EXTRACT (int))
PASTE(NOTHING_,EXTRACT int)
NOTHING_ ## EXTRACT int
NOTHING_EXTRACT int
int

即使没有额外加括号也能正常运行,因为额外的赋值并没有影响:

UNPAREN(int)
EVALUATING_PASTE(NOTHING_,EXTRACT int)
PASTE(NOTHING_,EXTRACT int)
NOTHING_ ## EXTRACT int
NOTHING_EXTRACT int
int

成功了!我们现在编写 GETTER 时可以不需要围绕类型的括号了:

#define GETTER(type,name) \
- (UNPAREN(type))name { \
return [_dictionary objectForKey: @#name]; \
}

奖励宏

在选择一些宏来证明这个结构时,我构建了一个很好的 dispatch_once 宏来制作延迟初始化的常量。实现如下:

#define ONCE(type,name,...) \
UNPAREN(type) name() { \
static UNPAREN(type) static_ ## name; \
static dispatch_once_t predicate; \
dispatch_once(&predicate,^{ \
static_ ## name = ({ __VA_ARGS__; }); \
}); \
return static_ ## name; \
}

使用案例:

ONCE(NSSet *,AllowedFileTypes,[NSSet setWithArray:@[ @"mp3",@"m4a",@"aiff" ]])

然后,你可以调用 AllowedFileTypes() 来获取集合,并根据需要高效创建集合。如果类型不巧包括括号,添加括号就能运行。

结论

仅仅写这个宏,我就发现了很多艰涩的知识。我希望接触这些知识也不会影响你的思维。请谨慎使用这些知识。

今天就这样。以后还会有更多令人兴奋的探索,可能比这还要再不可思议。在此之前,如果你对此主题有任何建议,请发送给 我们

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

让我们构建一个Swift.Array

作者 SwiftGG
2018年12月6日 14:00

作者:Mike Ash,原文链接,原文日期:2015-04-17
译者:灰s;校对:numbbbbbForelax;定稿:Forelax

Swift 1.2 现已经作为 Xcode 6.3 的一部分而发布,在新的 API 中有一个允许我们使用值类型建立高效的数据结构,比如 Swift 标准库中的 Array 类型。今天,我们将重新现实 Array 的核心功能。

值类型和引用类型

在我们开始之前,快速的复习一下值类型和引用类型。在 objc 以及大部分其他面向对象的语言中,我们所使用对象的指针或者引用都属于 引用类型。你可以把一个对象的引用赋值给一个变量:

MyClass *a = ...;

现在你可以将这个变量的值复制给另一个变量:

MyClass *b = a;

现在 a 和 b 都指向同一个对象。如果这个对象是可变的,那么对其中一个变量的改变同样会发生在另一个变量上。

值类型 就是类似 Objective-C 中 int 这样的存在。使用值类型,变量包含的是真实的值,并不是值的引用。当你用 = 给另一个变量赋值,是将值的拷贝副本赋值给了另一个变量。比如:

int a = 42;
int b = a;
b++;

现在,b 的值是 43,但是 a 依然是 42。

在 Swift 中,class 属于引用类型,struct 属于值类型。
如果你使用 = 将一个 class 实例的引用赋值给另一个变量,你将得到这个实例的一个新引用。对这个实例的修改对每一个引用可见。
如果你使用 = 将一个 struct 实例的引用赋值给另一个变量,你将得到这个实例的一个副本,与原始数据相互独立。

与大多数语言不同,Swift 标准库中的数组和字典都是值类型。比如:

var a = [1, 2, 3]
var b = a
b.append(4)

在大部分语言中,这段代码(或者等效的代码)运行以后, a 和 b 将都是一个指向数组 [1, 2, 3, 4] 的引用。但是在 Swift 中,a 是指向数组 [1, 2, 3] 的引用,b 是指向数组 [1, 2, 3, 4] 的引用。

值类型的实现

如果你的对象拥有的属性是固定的,在 Swift 中把它申明成值类型是很简单的:只需要把所有的属性放入一个 struct 中即可。比如,如果你需要一个 2D 的 Point 值类型,你可以简单的申明一个 struct 包含 x 和 y :

struct Point {
var x: Int
var y: Int
}

很快的,你就申明了一个值类型。但是如何实现类似 Array 这样的值类型呢?你无法把数组里面所有数据放入 struct 的申明中,因为在写代码的过程中你无法预料你将会在数组中放多少数据。你可以创建一个指针指向所有的数据:

struct Array<T> {
var ptr: UnsafeMutablePointer<T>
}

同时,你需要在该 struct 每次分配销毁的时候进行一些特殊操作。

  • 在分配的过程中,你需要把包含的数据拷贝一份放到一个新的内存地址,这时新的 struct 就不会和原数据共享同一份数据了。
  • 在销毁的过程中,ptr 指针也需要正常销毁。

在 Swift 中不允许对 struct 的分配和销毁过程进行自定义。

销毁操作可以使用一个 class 实现,它提供了 deinit。同时可以在这里对指针进行销毁。class 并不是值类型,但是我们可以将 class 作为一个内部属性提供给 struct 使用,并把数组作为 struct 暴露给外部接口。看起来就像这样:

class ArrayImpl<T> {
var ptr: UnsafeMutablePointer<T>

deinit {
ptr.destroy(...)
ptr.dealloc(...)
}
}

struct Array<T> {
var impl: ArrayImpl<T>
}

这时在 Array 中申明的方法,它的实际操作都是在 ArrayImpl 上进行的。

到这里就可以结束了吗?尽管我们使用的是 struct, 但是使用的依旧是引用类型。如果将这个 struct 拷贝一份,我们将获得一个新的 struct,持有的仍然是之前的 ArrayImpl。由于我们无法自定义 struct 的分配过程,所以没有办法同样把 ArrayImpl 也拷贝一份。

这个问题的解决方法是放弃在分配的过程中进行拷贝,而是在 ArrayImpl 发生改变的时候进行拷贝。关键在于,就算一个拷贝副本与原始数据共享一个引用,但是只要这个引用的数据不发生改变,值类型的语义就依旧成立。只有当这个共享数据的值发生改变时,值类型和引用类型才有了明显的区别。

比如,在实现 append 方法的时候你可以先对 ArrayImpl 进行 copy (假设 ArrayImpl 的实现中有一个 copy 方法,那么将 impl 引用改为原始值的 copy):

mutating func append(value: T) {
impl = impl.copy()
impl.append(value)
}

这样 Array 就是一个值类型了。尽管 a 和 b 在刚赋值完时仍然共享同一个 impl 引用 ,但是任何会改变 impl 的方法都将对其进行一次 copy,因此保留了不共享数据的错觉。

现在可以正常工作了,但是效率却非常低。比如:

var a: [Int] = []
for i in 0..<1000 {
a.append(i)
}

尽管使用者无法看到它,但是它将在循环的每次迭代中复制内存中的数据,然后立即销毁之前的数据内存。如何才能优化它呢?

isUniquelyReferenced

这是一个 Swift 1.2 中新引入的 API。它漂亮的实现了它字面上的意思。赋予它一个对象的引用然后它将告诉你这个引用是否为独立的。具体来说,当这个对象有且仅有一个强引用时,就会返回 true

我们猜测这个 API 会检查对象的引用计数,并且在引用计数为 1 的时候返回 true 。那 Swift 为什么不直接提供一个接口来查询引用计数呢?可能在实现上这个接口不太好做,并且引用计数属于比较容易被滥用的信息,所以 Swift 提供了这个封装过的,更加安全的接口。

使用这个 API,之前对 append 方法的实现将可以改成只有在需要的时候才对内存中的数据进行复制:

    mutating func append(value: T) {
if !isUniquelyReferencedNonObjc(&impl) {
impl = impl.copy()
}
impl.append(value)
}
```

这个 API 实际上是一组三个方法中的一个。存在于 Xcode 自带的 Swift 标准库中:

```swift
func isUniquelyReferenced<T : NonObjectiveCBase>(inout object: T) -> Bool
func isUniquelyReferencedNonObjC<T>(inout object: T?) -> Bool
func isUniquelyReferencedNonObjC<T>(inout object: T) -> Bool

这些方法只能作用在纯 Swift class 中,并不支持 @objc 类型。第一个方法必须确保 T 为 NonObjectiveCBase 的子类。另外两个方法对参数的类型并不做要求,只是当类型为 @objc 时直接返回 false。

我无法让我的代码以 NonObjectiveCBase 类型来编译,所以使用了 isUniquelyReferencedNonObjC 来代替。从功能上来说,它们并没有区别。

译者注:
文章中所阐述的 isUniquelyReferencedNonObjC API 已经在 Swift 3.1 的时候被替换为 isKnownUniquelyReferenced
详情可以参考 swift-evolution 中的这条 建议

ArrayImpl

让我们开始实现 Swift.Array,首先从 ArrayImpl 开始,然后才是 Array

在这里我并不会重新实现 Array 完整的 API,只是实现满足其正常运行的基本功能,并展示它涉及的原理。

ArrayImpl 有三个属性:指针,数组元素的总数,以及已申请内存空间中的剩余容量。只有指针和元素的总数是必须的,但是相比申请更多的内存空间,实时监控剩余容量并按需申请,我们可以避免一大笔昂贵的内存重新分配。下面是类开始的部分:

class ArrayImpl<T> {
var space: Int
var count: Int
var ptr: UnsafeMutablePointer<T>

init 方法中需要一个计数和一个指针,然后将指针所指向的内容复制到新的对象。方法提供了默认值 0 和 nil ,所以 init 可以在不传入任何参数的情况下被用来创建一个拥有空数组的实例:

        init(count: Int = 0, ptr: UnsafeMutablePointer<T> = nil) {
self.count = count
self.space = count

self.ptr = UnsafeMutablePointer<T>.alloc(count)
self.ptr.initializeFrom(ptr, count: count)
}
```

`initializeFrom` 方法可以将数据复制到新的指针。注意区分 `UnsafeMutablePointer` 处于不同赋值方式之间的不同,这对于确保它们正常工作以及避免崩溃十分重要。不同之处在于数据内存是否被处理为初始化或未初始化。在调用 `alloc` 时,生成的指针处于未初始化的状态,并且可能被垃圾数据填满。一个简单的赋值,例如 `ptr.memory = ...` ,此时是不合法的,因为赋值操作将会在复制新的值之前析构已经存在的值。如果是类似 `int` 这样的基础数据类型将没什么问题,但是如果你操作的是一个复杂数据类型它将崩溃。在这里 `initializeFrom` 将目标指针视为未初始化的内存,而这正是它的本质。

接下来是一个改变过的 `append` 方法。它做的第一件事是检查指针是否需要重新分配。如果没有剩余的空间可用,我们需要一块新的内存:

```swift
func append(obj: T) {
if space == count {
// 在新的内存分配中,我们将申请两倍的容量,并且最小值为 16 :
let newSpace = max(space * 2, 16)
let newPtr = UnsafeMutablePointer<T>.alloc(newSpace)
// 从旧的内存中将数据拷贝到新的地址
newPtr.moveInitializeFrom(ptr, count: count)
/*
这是另一种赋值,它不仅把目的指针看做是未初始化的,并且会把数据源销毁。
它节省了我们单独写代码来销毁旧内存的操作,同时可能更加高效。
随着数据的移动完成,旧指针将可以被释放,新的数据将被赋值给类的属性:
*/
ptr.dealloc(space)
ptr = newPtr
space = newSpace
}
// 现在我们确信有足够的空间,所以可以把新的值放在内存的最后面并且递增 count 属性的值:
(ptr + count).initialize(obj)
count++
}

改变过的 remove 方法将更为简洁,因为没有必要重新分配内存。首先,他将在移除一个值之前先将其销毁:

译者注:
这里的 # 号不用理会,在早期的 Swift 版本中它表示内外参数同名)**

func remove(# index: Int) {
(ptr + index).destroy()
// moveInitializeFrom 方法负责将所有在被移除元素之后的元素往前挪一个位置
(ptr + index).moveInitializeFrom(ptr + index + 1, count: count - index - 1)
// 递减 count 属性的值来体现这次删除操作
count--
}

我们同样需要一个 copy 方法来确保当需要的时候可以从数据内存中复制一份。实际关于复制的代码存在于 init 方法中,所以我们只需要创建一个实例也就相当于执行了一次复制:

func copy() -> ArrayImpl<T> {
return ArrayImpl<T>(count: count, ptr: ptr)
}

这样,我们就基本上完成了所有的事情。我们只需要确保在它自己将要被销毁,调用 deinit 方法之后销毁所有数组中的元素并释放指针:

    deinit {
ptr.destroy(count)
ptr.dealloc(space)
}
}

让我们把它移到 Array struct。它唯一的属性就是一个 ArrayImpl

struct Array<T>: SequenceType {
var impl: ArrayImpl<T> = ArrayImpl<T>()

所有 mutating 类型的方法都将以检查 impl 是不是独立的引用为开始,并在不是的时候进行复制操作。将把它封装成一个函数提供给其他的方法使用:

mutating func ensureUnique() {
if !isUniquelyReferencedNonObjC(&impl) {
impl = impl.copy()
}
}

append 方法现在只调用了 ensureUnique 方法,然后调用 ArrayImplappend 方法:

mutating func append(value: T) {
ensureUnique()
impl.append(value)
}

remove 方法也是一样:

mutating func remove(# index: Int) {
ensureUnique()
impl.remove(index: index)
}

count 属性直接通过 ArrayImpl's 来返回:

var count: Int {
return impl.count
}

下标操作直接通过底层指针来进行访问。如果我们是在写真实的代码,在这里我们会需要进行一个范围的检查(remove 方法中也是),但是在这个例子中我们将它省略了:

subscript(index: Int) -> T {
get {
return impl.ptr[index]
}
mutating set {
ensureUnique()
impl.ptr[index] = newValue
}
}

最后,Array 遵循 SequenceType 协议以支持 for in 循环。其必须实现 Generator typealiasgenerate 方法。内置的 GeneratorOf 类型使其很容易实现。GeneratorOf 使用一个代码块确保在其每次被访问的时候返回集合中的下一个元素,或者当到达结尾的时候返回 nil,并创造一个 GeneratorType 来封装该代码块:

typealias Generator = GeneratorOf<T>

generate 方法从 0 开始递增直到运行至结尾,然后开始返回 nil

    func generate() -> Generator {
var index = 0
return GeneratorOf<T>({
if index < self.count {
return self[index++]
} else {
return nil
}
})
}
}

这就是它的全部!

Array

我们的 Array 是一个符合 CollectionType 协议的通用 struct

struct Array<T>: CollectionType {

它唯一拥有的属性是一个底层 ArrayImpl 的引用:

private var impl: ArrayImpl<T> = ArrayImpl<T>()

任何一个方法如果会改变这个数组必须先检查这个 impl 是否为一个独立的引用,并在它不是的时候进行复制。这个功能被封装成一个私有的方法提供给其他的方法使用:

private mutating func ensureUnique() {
if !isUniquelyReferencedNonObjC(&impl) {
impl = impl.copy()
}
}

append 方法会使用 ensureUnique 然后调用 impl 中的 append

mutating func append(value: T) {
ensureUnique()
impl.append(value)
}

remove 的实现基本是相同的:

mutating func remove(# index: Int) {
ensureUnique()
impl.remove(index: index)
}

count 属性是一个计算性属性,它将直接通过 impl 来调用:

var count: Int {
return impl.count
}

下标操作将直接通过 impl 来修改底层的数据存储。通常这种直接从类的外部进行访问的方式是一个坏主意,但是 ArrayArrayImpl 联系的太过紧密,所以看起来并不是很糟糕。subscriptset 的部分会改变数组,所以需要使用 ensureUnique 来保持值语义:

subscript(index: Int) -> T {
get {
return impl.ptr[index]
}
mutating set {
ensureUnique()
impl.ptr[index] = newValue
}
}

CollectionType 协议需要一个 Index typealias。对于 Array 来说,这个索引类型就是 Int

typealias Index = Int

它同时也需要一些属性来提供一个开始和结束的索引。对于 Array 俩说,开始的索引为 0 ,结束的索引就是数组中元素的个数:

var startIndex: Index {
return 0
}

var endIndex: Index {
return count
}

CollectionType 协议包含 SequenceType 协议,它使得对象可以被用于 for/in 循环。它的工作原理是让序列提供一个生成器,该生成器是一个可以返回序列中连续元素的对象。生成器的类型由采用协议的类型来定义。Array 中采用的是 GeneratorOf,它是一个简单的封装用来支持使用一个闭包创建生成器:

typealias Generator = GeneratorOf<T>

generate 方法将会返回一个生成器。它使用 GeneratorOf 并且提供一个闭包来递增下标,直到下标到达数组的结尾。通过在闭包的外面声明一个 index,使它在调用中被捕获,并且它的值持续存在:

    func generate() -> Generator {
var index = 0
return GeneratorOf<T>{
if index < self.count {
return self[index++]
} else {
return nil
}
}
}
}

这样就完成了 Array 的实现。

完整的实现和测试代码

这里提供了完整的实现,附加一些测试来确保所有的这些正常运行,我放在了 GitHub 上面:

https://gist.github.com/mikeash/63a791f2aec3318c7c5c

结语

在 Swift 1.2 中添加的 isUniquelyReferenced 是一个广受好评的改变,它让我们可以实现很多真正有趣的值类型,包括对标准库中值类型集合的复制。

今天就到这里。下次再来找乐趣,功能,以及有趣的功能。如果你有感兴趣的主题,请发给我们!邮箱地址:mike@mikeash.com

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

❌
❌