初窥与语法速览
Swift 初窥与语法速览
Apple 在推出 Swift 时就将其冠以先进,安全和高效的新一代编程语言之名。前两点在 Swift 的语法和语言特性中已经表现得淋漓尽致:像是尾随闭包,枚举关联值,可选值和强制的类型安全等都是 Swift 显而易见的优点。
Comparison
近来 Swift 与 Rust 都挺好的,一个背靠 Apple,一个是 Mozilla 的亲儿子。不可否认这二者都是工程领域的集大成者,不过笔者认为 Swift 是会比 D 或者 Rust 具有更大的可用性与吸引力,当然,他们的瞄准的目标点也不一样。D 与 Rust 适合于那些长期使用 C++并且已经适应了要去掌握 N 多的语法与概念的,但是想要使用些更加清晰明了与安全的语言。这类型的开发者往往从事着类似于游戏引擎、编译器、加解密库、HTML 渲染引擎等等类似的工作。 Swift 呢,更多意义上是一门面向于应用编程的语言,它很容易上手,在某些方面它应该与 Go、Java、Python 以及 C#相提并论。不过 Swift 比这些会更容易开始学习,它的 helloworld 只需要一行代码就好了,并且它是支持函数的(不像 Java 那样完全的 OO)。你不需要学习任何的类与对象的知识就可以开始撰写简易的 Swift 的代码。基于此,Swift 是更合适用作一种教学语言的,它还有像脚本语言一样的交互环境,也就是 REPL 以及 Xcode 本身提供的 PlayGround。 综上所述,Swift 拥有着被广泛使用以及当做第一学习语言的潜质。并且,Swift 并不是像 PHP 那样的语法特性较少的语言,它拥有足够的深度来满足 Rust 或者 D 这样的用户的需求。与 Go 相比的话,Go 背靠 Google,也是个非常容易上手的语言,并且号称自带并发。Swift 在语法层次上会更加高级,并且 Swift 并没有使用 GC 机制,因此可以与 C 更好地相兼容。也就是说,你可以用 Swift 编写任何的库来供任何语言使用,只要这些语言可以使用 C 的库。这些特质保证了 Swift 拥有着比 Java、C#、Python、Ruby 以及 Go 更广阔的适用范围。后面这几个家伙,因为有 GC 的存在,不适合为其他语言提供基本库。我也很喜欢 Go,但是毫无疑问,Swift 会是个更加严谨与安全的语言。类型检测系统会帮助处理很多的错误,Go 目前是很合适于 Web 开发但是不适合科学计算与游戏引擎这些你必须要大量自定义操作符来处理向量啊、矩阵运算这样的。 因此,Swift 可以被定义为一个安全并且用户友好的语言,并且可以像脚本语言那样方便实验与使用。
Performance
Swift 具有一门高效语言所需要具备的绝大部分特点。与 Ruby 或者 Python 这样的解释型语言不需要再做什么对比了,相较于其前辈的 Objective-C,Swift 在编译期间就完成了方法的绑定,因此方法调用上不再是类似于 Smalltalk 的消息发送,而是直接获取方法地址并进行调用。虽然 Objective-C 对运行时查找方法的过程进行了缓存和大量的优化,但是不可否认 Swift 的调用方式会更加迅速和高效。 另外,与 Objective-C 不同,Swift 是一门强类型的语言,这意味 Swift 的运行时和代码编译期间的类型是一致的,这样编译器可以得到足够的信息来在生成中间码和机器码时进行优化。虽然都使用 LLVM 工具链进行编译,但是 Swift 的编译过程相比于 Objective-C 要多一个环节 – 生成 Swift 中间代码 (Swift Intermediate Language,SIL)。SIL 中包含有很多根据类型假定的转换,这为之后进一步在更低层级优化提供了良好的基础,分析 SIL 也是我们探索 Swift 性能的有效方法。 最后,Swift 具有良好的内存使用的策略和结构。Swift 标准库中绝大部分类型都是 struct,对值类型的 使用范围之广,在近期的编程语言中可谓首屈一指。原本值类型不可变性的特点,往往导致对于值的使用和修改意味着创建新的对象,但是 Swift 巧妙地规避了不必要的值类型复制,而仅只在必要时进行内存分配。这使得 Swift 在享受不可变性带来的便利以及避免不必要的共享状态的同时,还能够保持性能上的优秀。
编译器优化
Swift 编译器十分智能,它能在编译期间帮助我们移除不需要的代码,或者将某些方法进行内联 (inline) 处理。编译器优化的强度可以在编译时通过参数进行控制,Xcode 工程默认情况下有 Debug 和 Release 两种编译配置,在 Debug 模式下,LLVM Code Generation 和 Swift Code Generation 都不开启优化,这能保证编译速度。而在 Release 模式下,LLVM 默认使用 “Fastest, Smallest [-Os]",Swift Compiler 默认使用 “Fast [-O]",作为优化级别。我们另外还有几个额外的优化级别可以选择,优化级别越高,编译器对于源码的改动幅度和开启的优化力度也就越大,同时编译期间消 耗的时间也就越多。虽然绝大部分情况下没有问题,但是仍然需要当心的是,一些优化等级采用的是激进的优化策略,而禁用了一些检查。这可能在源码很复杂的情 况下导致潜在的错误。如果你使用了很高的优化级别,请再三测试 Release 和 Debug 条件下程序运行的逻辑,以防止编译器优化所带来的问题。 值得一提的是,Swift 编译器有一个很有用的优化等级:“Fast, Whole Module Optimization”,也即 -O -whole-module-optimization。在这个优化等级下,Swift 编译器将会同时考虑整个 module 中所有源码的情况,并将那些没有被继承和重载的类型和方法标记为 final,这将尽可能地避免动态派发的调用,或者甚至将方法进行内联处理以加速运行。开启这个额外的优化将会大幅增加编译时间,所以应该只在应用要发布的时候打开这个选项。 虽然现在编译器在进行优化的时候已经足够智能了,但是在面对编写得非常复杂的情况时,很多本应实施的优化可能失效。因此保持代码的整洁、干净和简单,可以让编译器优化良好工作,以得到高效的机器码。
注释与换行
注释
请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。
Swift 中的注释与 C 语言的注释非常相似。单行注释以双正斜杠作(//)为起始标记:
// 这是一个注释
你也可以进行多行注释,其起始标记为单个正斜杠后跟随一个星号(/),终止标记为一个星号后跟随单个正斜杠(/):
/* 这是一个, 多行注释 */
与 C 语言多行注释不同,Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记:
/* 这是第一个多行注释的开头 /* 这是第二个被嵌套的多行注释 */ 这是第一个多行注释的结尾 */
通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。
语法要点
! VS ?
在 Swift 中经常看到!与?两个操作符,譬如在类型转换、可选类型构造中都用到,用 Apple 官方的话说:
It may be easiest to remember the pattern for these operators in Swift as: ! implies “this might trap,” while ?indicates “this might be nil.”
就是!操作符表示我不管你编译器,我肯定要这么做,那么有可能导致运行时崩溃。而?操作符表示这个可能是 nil,你帮我查查有没有进行完备的空检查。
Quick Start
Objective-C 混合编程
参考资料
Swift 类引用 Objective-C 文件
因为 Swift 没有内嵌的头文件机制,因此 Swift 调用 Objective-C 需要一个名为“<工程名>-Bridging-Header.h”的桥接头文件。桥接头文件的作用是为 Swift 调用 Objective-C 对象搭建一个桥,它的命名必须是“<工程名>- Bridging-Header.h”,我们需要在桥接头文件中引入 Objective-C 头文件,所有的 Swift 类会自动引用这个头文件。
- OJC 类如下:
//
// ObjcFunc.h
//
# import <Foundation/Foundation.h>
@interface ObjcFunc : NSObject
-(NSString*)sayHello:(NSString*)greeting withName: (NSString*)name;
@end
//
// ObjcFunc.m
//
# import "ObjcFunc.h"
# import "CombinedProgram-Swift.h"
@implementation ObjcFunc
- (NSString*)sayHello:(NSString*)greeting withName: (NSString*)name{
NSString *string = [NSString stringWithFormat:@"Hi,%@ %@.",name,greeting];
return string;
}
@end
- Swift 类中调用
import Foundation
@objc class SwiftFunc: NSObject {
func sayHello() -> Void {
var obj : ObjcFunc = ObjcFunc()
println(obj.sayHello("Hello", withName: "Swift"));
return
}
}
Objective-C 类引用 Swift 文件
(1)在 Building Settings -> Packaging -> Defining 中选定 Module Name;
(2)在 OJC 的头文件中引入:#import "{ModuleName}-swift.h"
SwiftFunc* obj = [[SwiftFunc alloc] init];
[obj sayHello];
有时候会发现 Xcode 无法自动生成*-Swift.h 文件,可以参考StackOverflow上的这篇文章。该文章总结下来,我们需要进行以下两大步检测:
(1)检测你的 Xcode 的配置
Product Module Name : myproject
Defines Module : YES
Embedded Content Contains Swift : YES
Install Objective-C Compatibility Header : YES
sObjective-C Bridging Header : $(SRCROOT)/Sources/SwiftBridging.h
(2)检查你的 Swift 类是否正规
要保证你的 Swfit 类中已经使用@objc 关键字声明了一个继承自 NSObject 的类。Xcode 不会为存在任何编译错误的类进行编译操作。
(3)忽略 Xcode 的报错,先编译一下
流程控制
分支选择
if
if 1+1 == 2 {
println("The math checks out")
}
if-let
可以用 if-let 语句检查一个可选变量是否包 含值。如果包含,则将这个值指定给一个常量变量,然后运行某段代码。这样可以减少很多行代码,同时又能够保证安全性
var conditionalString : String? = "a string"
if let theString = conditionalString {
println("The string is '\(theString)'")
}
else {
println("The string is nil")
}
// 输出 "The string is 'a string'"
同时 if-let 也支持更复杂一点的 where 判断:
var conditionalString : String? = "a string"
if let theString = conditionalString where theString == "a"{
print("The string is '\(theString)'")
} else {
print("The string is nil")
}
guard:保镖模式
与 if 语句相同的是,guard 也是基于一个表达式的布尔值去判断一段代码是否该被执行。与 if 语句不同的是,guard 只有在条件不满足的时候才会执行这段代码。你可以把 guard 近似的看做是 Assert,但是你可以优雅的退出而非崩溃。
func fooGuard(x: Int?) {
guard let x = x where x > 0 else {
// 变量不符合条件判断时,执行下面代码
return
}
// 使用x
x.description
}
是对你所期望的条件做检查,而非不符合你期望的。又是和 assert 很相似。如果条件不符合,guard 的 else 语句就运行,从而退出这个函数。 如果通过了条件判断,可选类型的变量在 guard 语句被调用的范围内会被自动的拆包 - 这个例子中该范围是 fooGuard 函数内部。这是一个很重要,却有点奇怪的特性,但让 guard 语句十分实用。 对你所不期望的情况早做检查,使得你写的函数更易读,更易维护。 对非可选类型的变量这种用法也是奏效的:
func fooNonOptionalGood(x: Int) {
guard x > 0 else {
// 变量不符合条件判断时,执行下面代码
return
}
// 使用x
}
func fooNonOptionalBad(x: Int) {
if x <= 0 {
// 变量不符合条件判断时,执行下面代码
return
}
// 使用x
}
循环
for
for-in
let loopingArray = [1,2,3,4,5]
var loopSum = 0
for number in loopingArray {
loopSum += number
}
loopSum // = 15
var firstCounter = 0
for index in 1 ..< 10 {
firstCounter++
}
// 循环9次
var secondCounter = 0
for index in 1 ... 10 { // 注意是三个句点,不是两个
secondCounter++
}
// 循环10次
var sum = 0
for var i = 0; i < 3; i++ {
sum += 1
}
sum // = 3
while
var countDown = 5
while countDown > 0 {
countDown--
}
countDown // = 0
var countUP = 0
do {
countUp++
} while countUp < 5
countUp // = 5
defer:代码块延迟执行
defer 关键字可以用来包裹一段代码,这个代码块将会在当前作用域结束的时候被调用。这通常被用来对当前的代码进行一些清理工作,比如关闭打开的文件等。
可以在同一个作用域中指定多个 defer 代码块,在当前作用域结束时,它们会以相反的顺序被调用,即先定义的后执行,后定义的先执行。
例如下面的代码:
openFile()
defer {
// defer block 1
closeFile()
}
startPortListener(42)
defer {
// defer block 2
stopPortListener(42)
}
这段代码在作用域结束的时候,第二个 defer 块将首先被调用,其次再调用第一个 defer 块。
异常处理
异常捕获
try?
可以使用 try?通过将错误转换成一个可选值来处理错误。如果在评估 try?表达式时一个错误被抛出,那么表达式的值就是 nil。例如下面代码中的 x 和 y 具有相同的值:
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
如果 someThrowingFunction()抛出一个错误,x 和 y 的值是 nil。否则 x 和 y 的值就是该函数的返回值。注意,无论 someThrowingFunction()的返回值类型是什么类型,x 和 y 都是这个类型的可选类型。例子中此函数返回一个整型,所以 x 和 y 是可选整型。 如果你想对所有的错误都采用同样的方式来处理,用 try?就可以让你写出简洁的错误处理代码。例如,下面的代码用几种方式来获取数据,如果所有方式都失败了则返回 nil:
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
譬如以 JSON 解析为例,说明 try?用法的便捷性:
func parse(fromData data: NSData) throws -> TodoList {
do {
guard let jsonDict = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String : AnyObject],
// todoListDict is now moved up here
todoListDict = jsonDict["todos"] as? [[String : AnyObject]] else {
throw Error.InvalidJSON
}
let todoItems = todoListDict.flatMap { TodoItemParser().parse(fromData: $0) }
return TodoList(items: todoItems)
} catch {
throw Error.InvalidJSON
}
struct TodoListParser {
enum Error: ErrorType {
case InvalidJSON
}
func parse(fromData data: NSData) throws -> TodoList {
guard let jsonDict = try? NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String : AnyObject],
// Notice the extra question mark here!
todoListDict = jsonDict?["todos"] as? [[String : AnyObject]] else {
throw Error.InvalidJSON
}
let todoItems = todoListDict.flatMap { TodoItemParser().parse(fromData: $0) }
return TodoList(items: todoItems)
}
}