<转>在Swift中构建assert()

__FILE__ 和 __LINE__这两个神奇的宏定义是C语言中偶尔有用的特性。他们被构建在预处理程序中,并在C语言语法分析程序运行前被展开。尽管Swift没有预处理程序,它却提供了名称相似的类似功能,但隐藏着极其不同的实现方式。

内建标识符

就像在the Swift programming guide中描述的那样,Swift有很多内建标识符,包括__FILE____LINE____COLUMN__, 和 __FUNCTION__。这些表达式可以在任何地方使用,并被语法分析器在源码对应的当前位置展开成字符串或整数字面量。这对手动日志非常管用,比如在退出前打印出当前位置。

然而这并不能帮助我们探索实现assert()。如果我们这样实现断言(assert):

1 func assert(predicate : @autoclosure () -> Bool) {
2 true#if DEBUG
3 truetrueif !predicate() {
4 truetruetrueprintln("assertion failed at \(__FILE__):\(__LINE__)")
5 truetruetrueabort()
6 truetrue}
7 true#endif
8 }

上面的代码将会输出实现assert()方法所在文件的文件/行数(file/line)位置,而不是调用者的位置信息。这并不管用。

获取调用者位置

Swift从D语言借鉴了一个非常聪明的特性:当标识符在默认参数列表中被赋值时,它们会在调用者的位置被展开。为了实现这个行为,assert()被如下这样定义:

1 func assert(condition: @autoclosure () -> Bool, _ message: String = "",
2 truefile: String = __FILE__, line: Int = __LINE__) {
3 truetrue#if DEBUG
4 truetruetrueif !condition() {
5 truetruetruetrueprintln("assertion failed at \(file):\(line): \(message)")
6 truetruetruetrueabort()
7 truetruetrue}
8 truetrue#endif
9 }

Swift的assert()函数第二个参数是一个供你指明的可选字符串,而第三、四个参数默认为调用者上下文的位置。这就让assert()能默认获得调用者的原始位置。如果你想在断言顶层构建你自己的抽象层,你可以将调用者的位置传递下去。作为一个简易的例子,你可以定义一个日志和断言方法:

1 func logAndAssert(condition: @autoclosure () -> Bool, _ message: StaticString = "",
2 truefile: StaticString = __FILE__, line: UWord = __LINE__) {
3 truelogMessage(message)
4 trueassert(condition, message, file: file, line: line)
5 }

这正确的将logAndAssert()调用者的file/line位置传递给了assert()的实现。注意上面代码中的StaticString,它是一个类似于String的类型,用于存储像__FILE__这种不受内存管理约束的字符串字面量。

除此之外,这种实用设计也用于Swift中实现高级XCTest框架,也可以在你自己实现的函数库中发挥作用。

参数的惰性计算(Lazy Evaluation)

当实现Swift的assert()时,我们遇到的第一个挑战是没有明确的方式让一个函数接收一个表达式而不评判它。比如,我们想使用:

1 func assert(x : Bool) {
2 true#if !NDEBUG
3 truetrue/*noop*/
4 true#endif
5 }

甚至当断言失效,应用程序将会在计算表达式时损失性能:

assert(someExpensiveComputation() != 42)

修复这种情况的一种方法是在定义断言的时候让它接收一个闭包(closure):

1 func assert(predicate : () -> Bool) {
2 true#if !NDEBUG
3 truetrueif !predicate() {
4 truetruetrueabort()
5 truetrue}
6 true#endif
7 }

正如我们想要的,只有在断言有效时才计算表达式,但它也给我们留下了一个不幸的调用语法:

assert({ someExpensiveComputation() != 42 })

我们可以用Swift的@autoclosure属性来修复它。这个自动闭包属性可以用在函数的参数上来表明一个未经花括号修饰的表达式可以被隐式的打包成闭包并作为参数传递给函数。比如:

1 func assert(predicate : @autoclosure () -> Bool) {
2 true#if !NDEBUG
3 truetrueif !predicate() {
4 truetruetrueabort()
5 truetrue}
6 true#endif
7 }

这让你更自然的调用断言:

assert(someExpensiveComputation() != 42)

自动闭包是一个强大的特性,因为你可以有条件地计算表达式,多次计算并像使用闭包那样来使用打包的表达式。自动闭包也可以在Swift的其他地方使用。比如,实现简化逻辑运算符:

1 func &&(lhs: BooleanType, rhs: @autoclosure () -> BooleanType) -> Bool {
2 truereturn lhs.boolValue ? rhs().boolValue : false
3 }

通过将右边表达式以闭包形式接收,Swift提供合适的子表达式的惰性计算。

自动闭包

作为C语言的宏,自动闭包要谨慎使用。因为从调用函数的一方看不出来参数的计算受到了影响。自动闭包有意地限制我们不传递参数,所以你不能在类似条件控制流的情形中使用它。在符合人们期望的实用语义情况(可能是“features”API)下使用它,而不是单单为了省略闭包的花括号。

转自:玉令天下的博客

时间: 2024-07-31 21:45:23

<转>在Swift中构建assert()的相关文章

使用Koloda View在Swift中构建类似Tinder(国内的探探社交应用)的卡片

在过去几年中,随着社交网络应用程序的普及,约会应用程序也迅速出现.其中一个最突出的应用是Tinder.它不仅是一个很棒的约会应用程序,而且还在视图动画或过渡方面创建了新的iOS趋势,例如Tinder Card Swipe或Tinder UI 在这个iOS教程中,我们将学习如何在Swift中构建Tinder Swipe Cards,以便您可以将此功能包含在iOS应用程序中.目前有一些图库支持这种类型的可滑动卡片,其中一个是KolodaView.在本教程中,我们将向您展示如何使用代码示例在Swift

Swift中使用构建配置来支持条件编译-b

在Objective-C中,我们经常使用预处理指令来帮助我们根据不同的平台执行不同的代码,以让我们的代码支持不同的平台,如: 1 2 3 4 5 6 7 8 9 #if TARGET_OS_IPHONE     #define MAS_VIEW UIView              #elif TARGET_OS_MAC     #define MAS_VIEW NSView #endif 在swift中,由于对C语言支持没有Objective-C来得那么友好(暂时不知swift 2到C的支持

Swift中的错误处理

前言 任何代码都会发生错误,这些错误有些是可以补救的,有些则只能让程序崩溃.良好的错误处理能够让你的代码健壮性提高,提高程序的稳定性. 本文的Swift版本:Swift 3 Objective C 返回nil 如果出错了,就返回空是Objective C中的一种常见的处理方式.因为在Objective C中,向nil发送消息是安全的.比如: - (instancetype)init { self = [super init]; if (self) { } //如果初始化失败,会返回nil ret

浅谈swift中的内存管理

Swift使用自动引用计数(ARC(Automatic Reference Count))来管理应用程序的内存使用.这表示内存管理已经是Swift的一部分,在大多数情况下,你并不需要考虑内存的管理.当实例并不再被需要时,ARC会自动释放这些实例所使用的内存. 内存管理:针对的是实例的内存占用的管理(放在堆里面) 实例:1:由class类型构建的实例,2:闭包对象 下面我们来写一个实例来证明一下 class Person { var name: String init(name: String )

浅谈swift中的那些类,结构以及初始化的操作

首先呢,我们先声明一个类 class Parent { //声明一个属性 var p1: String = "abc" //声明一个方法 func m() { print("parent m") } //声明一个静态的方法 final func n(){ } } 然后我们new一个Parent类(注意了,在swift中是没有new的,如果想new 一个的话, 直接调用该类就可以了) var par = Parent() 调用parent的方法和属性 par.m()

Swift中的init方法

摘要:Swift有着超级严格的初始化方法,不仅强化了designated初始化方法的地位,所有不加修饰的init方法都需要在方法中确保非Optional的实例变量被赋值初始化,而在子类中,也强制调用super版本的designated初始化. 我们在深入初始化方法之前,不妨先再想想Swift中的初始化想要达到一种怎样的目的. 其实就是安全.在Objective-C中,init方法是非常不安全的:没有人能保证init只被调用一次,也没有人保证在初始化方法调用以后,实例的各个变量都完成初始化,甚至如

如何模块化swift中的状态?

本文和大家分享的主要是模块化swift中状态相关内容,一起来看看吧,希望对大家学习swift有所帮助. 在构建应用或设计系统的时候,最困难的事情之一就是如何建模并处理状态.当应用的某些部分处于我们意料之外的状态时,管理状态的代码也是一个非常常见的 bug 来源. 这周,让我们看一看能更容易处理并响应状态改变的编码技术 - 让代码更加强壮,不容易出错.在本文中,我不会讨论具体的框架或者更大的应用程序架构范围的更改(比如 RxSwift.ReSwift 或者使用 ELM 风格的架构,我会在之后讨论它

Swift中的延迟加载(懒加载)

Swift方式的延迟加载 而在Swift中,你只需一行代码即可实现此机制: lazy var players = String[]() 简单.简洁,直入主题. 但你得记住,你必须使用var关键字来定义延迟加载的属性,不能使用let关键字,因为常量必须在实例构建时赋值. 如果你想给延迟加载加上一些逻辑处理,Swift允许你在属性后面定义一个闭包调用(闭包的返回值会作为属性的默认值): lazy var players: String[] = { var temporaryPlayers = Str

iOS开发——开发总结Swift篇&amp;Swift中的条件编译

Swift中的条件编译 在Objective-C中,我们经常使用预处理指令来帮助我们根据不同的平台执行不同的代码,以让我们的代码支持不同的平台,如: 1 #if TARGET_OS_IPHONE 2 3 #define MAS_VIEW UIView 4 5 #elif TARGET_OS_MAC 6 7 #define MAS_VIEW NSView 8 9 #endif 在swift中,由于对C语言支持没有Objective-C来得那么友好(暂时不知swift 2到C的支持如何),所以我们无