Swift作为苹果生态力推的Object-C的后继者,借鉴了许多先进的设计,这里汇总记录Swift语法的摘要。
可选项绑定 Optional
可选类型大量应用于Swift的开发中,为此Swift为其设计了语法糖: ! 、?、和??(??实际上是Optional类型的自定义操作符) 。
var i: Int? //等同于 let i: Optional<Int>,使用时需要用 ! 显示展开
var j: Int! //j的类型是 Optional<Int>,使用时会隐式展开,如果是nil,则会运行时错误
print(i) //Output: nil
print(j) //Output: nil
print(i + 2) //Compile Error: 没有显示展开
print(i! + 2) //Runtime Error
print(j + 2) //Runtime Error
i = 1
j = 2
print(i) //Output: Optional(1)
print(j) //Output: Optional(2)
print(i + 2) //Compile Error: 没有显示展开
print(i! + 2) //Output: 3
print(j + 2) //Output: 4
使用 ? 和 ! 可以定义Optional类型,使用时后者会隐式展开(所以除非可以确保代码会赋有效值,否则尽量不使用。在使用时,可以使用 ! 显示的展开,如果值为nil,运行时报错。
可以使用 ?? 来设置默认值,如果某个可选类型是nil,则会使用默认值:
var s: String?
let s2 = s ?? "default"
print(s, s2)
let s3: String?
let s = s3 ?? "AAA" //Compile Error:会提示常量没有初始化
可选链
使用可选链式调用可以大大简化代码的判断逻辑,在任何一个调用出现nil时,整体返回nil,然后利用let、while、guard可以有效处理发生nil的情形。
struct Score {
var value: Int = 0
func valid () -> Bool {
if self.value >= 60 {
return true
}
return false
}
}
struct Person {
var name: String
var score: Score?
}
var p = Person(name: "John")
if let _ = p.score?.valid() {
print("valid: \(p.name)")
} else {
print("invalid: \(p.name)")
}
guard
let v = p.score?.value,
v >= 0
else {
print("invalid: \(p.name)")
exit(1)
}
print(v)
异常处理
try也可以利用 ! 和 ? 来区别处理异常发生时的逻辑:try! 在发生异常时会运行时错误,try? 则会返回nil。
enum MyError: Error {
case Unknown
case Invalid
}
func divd(_ a: Int) throws -> Int {
if a == 0 {
throw MyError.Unknown
} else if a == 1 {
throw MyError.Invalid
}
return 0
}
do {
let m = try divd(0)
} catch {
print("Err: \(error.self) \(error.localizedDescription)")
}
try! print(divd(1)) //runtime error
let v = try? divd(0)
print(v) // v is nil
类型转换
类型转换操作符 as? as! 可以把一个对象从父类型转换为其子类型。as? 在转换失败时返回nil,as! 则会在转换失败时产生运行时错误。
闭包
闭包默认写法:
let fn = { (a: Int, b: Int) -> Int in
return a + b
}
print(fn(5,3))
缩写规则:
- 利用类型推理,参数类型和返回值类型可以省略
- 参数的圆括号可以省略
- 无参数时 in 关键字都可以省略
- 只有一行时 return 可以省略
- 参数可以省略,以\$0 \$1代替
根据以上规则,下面的写法和上述的fn都是完全相同的:
let fn1 = { (a: Int, b: Int) -> Int in
return a + b * 2
}
let fn2 = { (a, b) in
a + b * 2
}
let fn3 = { $0 + $1 * 2 }
print(fn1(5, 3), fn2(5, 3), fn3(5, 3))
若闭包是函数调用时的最后一个参数,可以使用尾随闭包,放在 () 之外,参数名可以省略。若有多个闭包作为参数,也可以放在 () 之外,第一个闭包的参数名可以忽略。
var scores = [5, 2, 9]
let sorted_scores = scores.sorted { $0 > $1 }
print(sorted_scores)
func fnn(_ v: Int, f1: (_ a: Int) -> Int, f2: (_ b: Int) -> Int) -> Int {
f1(v) + f2(v)
}
print(fnn(5) {b in
b - 2
} f2: {x in
x * x
})
上述代码中sorted方法的闭包也可以进一步简化成运算符方法,swift会自动推断实现的代码:
let sorted_scores = scores.sorted(by: >)
逃逸闭包: 闭包作为函数参数时,@escaping 特性可以让闭包在函数之外执行,这意味着即使函数退出了,闭包依然可以正常执行。
自动闭包: 是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值,即只有在调用时才会执行。在作为函数参数时,使用 @autoclosure 特性。
特性
@ 特性可以用于修饰声明和类型,以下是声明特性
- @available 只在特定平台或版本上可用
- dicardableResult 返回值没有使用时不触发警告
- @frozen 枚举或结构体类型禁止修改
- @main 表示它是程序的顶级入口,其中必须包含一个不接受任何参数的返回值为void类型的main函数
- @propertyWrapper 属性包装器,属性的自定义特性,用以封装属性的处理逻辑。它必须提供一个属性,wrappedValue,读取和设置属性时会默认使用这个wrappedValue的get和set方法。同时包装器还提供projectedValue,使用$符来读取projectedValue,projectedValue可以是任何类型。具体范例可以参考文档。
- @resultBuilder 结构构造器
- …
类型特性
-
@autoclosure :参考闭包
-
@escaping: 参考闭包
-
@Sendable
-
@convention 用于修饰函数类型,它指出了函数调用的约定。包括swift、block、c。
switch特有特性
- @unknown 用于default,编译器可以在没有全部匹配已知的case时,给出报警。这对经常需要补充的enum非常有意义,可以避免switch漏掉未处理的情况。
其他语法点
-
区间运算符
for i in (1...5) { print(i) } // 后闭区间 for i in (1..<5) { print(i) } // 后开区间
-
溢出运算符
&+ &- &* &/ let ii: Int = Int.max print(ii + 1) //runtime error: overflow print(ii, ii &+ 1, ii &* 2)
-
不透明类型
使用 some 关键字,可以使函数或者变量类型不再提供具体的类型作为返回类型,而是根据它支持的协议来描述返回值。但编译器会强制约束返回的类型任何时候都必须是单一类型,不能动态变化。
-
inout 关键字
函数参数适应inout关键字可以传递引用参数,函数中可以改变这个变量的值。
func inc2(_ a: inout Int, _ b: inout Int) { a = a + 1 b = b + 1 } var u = 3 var w = 4 inc2(&u, &w) print(u, w)
-
下标及自定义运算符
利用subsript可以定义下标的get和set方法,具体略。
利用扩展可以重载定义运算符,甚至可以自定义运算符(并不是所有运算符都可以自定义)。
extension Int { static func + (left: Int, right: Int) -> Int { return left * right } } print(5 + 3) prefix operator *** infix operator +++ postfix operator ~ extension Int { static prefix func *** (v: Int) -> Int { return v * v * v } static func +++ (left: Int, right: Int) -> Int { left * left * right } static postfix func ~ (v: Int) -> Int { var ret = 1 for i in (1...v) { ret *= i } return ret } } print(***5) print(3 +++ 5) print(5~)
-
隐式成员表达式
隐形成员表达式是类型推理的进阶,在调用函数时可以大大简化参数的书写:
func fa (_ a: Int, _ b: Float) -> Float { Float(a) + b } let x: Float = 2.15 //下面两行是完全等价的 print(fa(.max - 999, .minimum(.pi, x))) print(fa(Int.max - 999, Float.minimum(Float.pi, x)))
-
其他
疑难备注
1、struct和class的区别
-
struct是值类型,赋值时是深拷贝;class是引用类型,赋值时是浅拷贝,提供自动引用计数机制,允许多次引用。作为类比,函数和闭包也是引用类型。
-
struct不支持继承、类型转换(as 和 is)和析构器deinit,class则支持。
-
struct 和class都支持构造器init,struct会自动生成一个默认的成员逐一构造器,class则不提供。
在使用上,建议如下:
- 默认使用struct
- 优先使用结构和协议来实现继承和共享行为。
- 在需要Object-C互操作性时,使用Class。
- 在需要身份控制时,使用class。class是引用类型,因此即便是所有属性都相同的两个实例,=== 也会返回false。
2、Any 和AnyObject
Any可以代表任何类型,包括函数类型;AnyObject则可以表示任何类类型的实例。
let v: [Any] = [1, 1.0, 3.14, "Hello", String.sorted]
for item in v {
switch item {
case 1 as Int:
print("Int 1: \(item)")
case 1 as Double:
print("Double 1: \(item)")
case let o as Float:
print("Dec: \(o)")
case let o as String:
print("Str: \(o)")
case let o as ():
print("Func: \(o)")
default:
print("Other: \(item)")
}
}