Swfit语法摘要

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)")
    }
}