Swift语法简要

Swift 5.0 与ABI稳定

Swift 版本 Xcode 版本 发布时间 重大事件
Swift 1.0 ~ 1.2 6.x 2014 语⾔发布
Swift 2.0 ~ 2.2.1 7.x 2015 对协议,泛型能力进一步扩展,开始支持 Linux,随后出现了以 Swift 语言为核心的后端框架 Perfect,Vapor,Kitura
Swift 3.0 ~ 3.3.1 8.x 2016 发布了 Swift Package Manager,同时以 GCD,Core Graphics 为代表的基础库 API 风格发生了大幅度转变,摆脱了 Objective-C 时代的烙印
Swift 4.0 ~ 4.1.3 9.x 2017 在整体的语法,使用和理念上基本定型,提出了 Codable 协议,同时 Xcode 的 Swift Syntax Mirgration 的最低版本固定为 4
Swift 4.2 ~ 4.2.4 10.x 2018 Swift社区从邮件列表转向论坛,语言小幅升级,主要是功能完善,性能提升,同年 Swift for TensorFlow 发布并开源
Swift 5.0 ~ 5.0.3 10.2.x 2019 ABI 稳定,iOS 12 开始内置 Swift 运行时
Swift 5.1 ~ 5.2 11.x 2020 新增 Property Wrapper ,Opaque Type 等新的语法功能,同年 WWDC 上,Apple 发布了 SwiftUI,Combine,Catalyst 等 Swift 语言的专属 SDK

历经了5年,Swift终于在2019年3月发布了5.0版本,带来了ABI稳定。从RedMonk公布的编程语言排名看,Swift自2017年开始跃到10名左右,而Objective-C则持续下降,目前Swift排名与Objective-C相近,并列排在了11位。

从现在的发展趋势看,Swift势必替代Objective-C成为iOS/MacOS的主要应用开发语言。开发社区在项目演进尤其是新项目的开发上,更多地选择了Swift,苹果也确实在应用框架上越来越多的倾向了Swift。SwiftUI尽管还在发展中,但这也是苹果在思考融合声明式响应式UI框架的结果。

站在这个时间节点,无论是企业还是个人,显然Swift不再是一个Option,而是新的基础建设的砖头和铲子。

只从一方面去评价语言的优劣是不够的,语言的流行程度受到应用场景与需求的限定。很多人不一定喜欢JavaScript,但不妨碍JavaScript作为Web开发的主流语言。对于Swift,尽管吸纳了很多其他语言的特性,但为了兼容Objective-C以及其他考虑,显然也做了很多compromise。语言的发展都是平衡的产物。Chris Lattner在WWDC 2017上有过一些对Swift语言特点及后续发展的看法,有兴趣可以查阅这里了解。

本文将整理Swift的一些语法概要,方便一些小伙伴可以30分钟内对Swift语言特性有一个基本的认知。

语言文档

官方文档:https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html

中译文档:https://www.cnswift.org/

基本概念

Swift是一门编译型静态类型强类型的编程语言。

编译型是指会源码直接编译为(特定平台架构下的)二进制可执行文件,解释型(通常说的脚本语言)则并不会。解释型语言(在不同平台下)有一个源码解释器,其在运行过程中才将需要执行的源码(或者中间字节码)转换为机器码。因此编译型语言性能更高,解释型语言则更容易实现跨平台。

数据类型是语言定义的一部分(其定义了该类型数据可进行的操作),值是运行时的数据。静态/动态类型是以Type Check的时机进行区分的。通俗来说,动态类型选择相信编译期的数据类型声明,并在运行时才去确认数据的类型是否合法(合法是指不会因为出现与其类型定义不一致的操作而导致错误),而静态类型则在编译器是明确的。静态类型的代表有C++/Java/Go,动态类型的代表有Python、Ruby等。

强/弱类型则看编译器是否支持隐式类型转换。

1
2
3
4
5
6
7
var object: NSObject?
func aha() {
  self.object = String("haha") // Cannot assign value of type 'String' to type 'NSObject'
}

// 不允许隐式类型转换
var text: Int = 1234.00 // Cannot convert value of type 'Double' to specified type 'Int'

在编译阶段检查类型问题,可以避免运行时不可预测的错误。

基本数据类型

常量与变量 Swift中通过let/var来限定值的可变性,通过let可以声明常量var声明变量。举例,let list = []是不可变的,而var list = []则是可变的。这跟Objc常见的Mutable的集合类型是不同的,Objective-C是通过类型来支持可变性,Mutable类型是可变的,而非可变的是另一个类型,比如NSMutableArrayNSArray。Swift通过将Array这些集合使用值类型来实现以达到这个要求。

基本数据类型 有数值(Int、Int8、Int16、Int32、Int64,float,double)、Bool、字符(串)(String、Character)。

集合类型(数组Array、字典Dictionary、集合、元组)。

String是字符串类型,比如"hello, world",String的内容可以通过很多方式来访问。多行字符串使用"""来包括。为了能强化理解,你可以打开Xcode在Playground中来实践一下。

字面值,基础数据类型都可以直接使用字面值Literals,比如let someString = “Some string literal value”

类型推断,比如上面的例子let someString = “Some string literal value”,Swift可以推断someString的类型,因为它使用了字符串字面量来初始化了。

类型别名typealias CountType = Int,可以在代码中直接使用别名,let count: CountType = 10

Swift还引入了Optional(通常译为可空类型,其实是使用Enum实现的)——要么有值,要么为空(也就是值的缺失)。通常在变量声明时,可以在类型后加上?,来声明这是一个Optional类型,例如:var name: String?声明了一个名为name的变量,这个变量要么有值并且为String类型的值,要么为空。另外还有隐式解析可空类型,声明时在类型后加上!,比如let xNotNil: String! = “aaa”,指定该变量不应为空。普通可空类型可以为空,取值时必须变量名!(但通常我们会配合if letguard let等方式来取值);隐式解析可空类型可以直接取值。Optional Binding是在if等语句中,可用来对可空类型取值的,如果没有就是nil。可选链不赘述。

值类型与引用类型 Swift中的数据,要么是值类型,要么是引用类型。引用类型实例内存是按引用计数的,这是不同之处。struct是值类型,class是引用类型。

重复提醒:Swift是一门强类型语言,这意味着值不能进行隐式类型转换,不同类型之间转换必须通过显式的类型实例化过程。比如IntInt64两个类型之间赋值,是需要显式的类型转换的,let a: Int = Int(a64)

另外注释与C系列的一样,单行注释//、多行注释/**/,但Swift的注释支持嵌套多行注释了。

基本运算符

Swift是类C的语言,基础运算符跟C或其他语言都类似。

  • 算术运算符:+ - * / %

  • 比较运算符:== != > < >= <=

  • 逻辑运算符:! && ||

  • 三元运算符:? :

  • 区间运算符:a..<b半开区间、a...b闭区间、[..i]或[i..]单侧区间

集合类型(数组、字典、元组)

几种集合类型:数组Array、字典Dictionary、集合Set、元组。

集合是序列的一种,序列Sequence协议:

1
2
3
4
5
6
7
8
9
protocol Sequence {
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
}

public protocol IteratorProtocol {
associatedtype Element
public mutating func next() -> Self.Element?
}

Sequence协议要求集合类型支持迭代遍历,比如

1
2
3
4
let alphabet = ["A", "B", "C", "D"]
for c in alphabet {
print(c)
}

会被编译器翻译为:

1
2
3
4
var cIterator = alphabet.makeIterator()
while let c = cIterator.next() {
print(c)
}

集合类型遵循Collection协议,Collection继承于Sequence

1
2
3
4
5
6
7
8
9
10
public protocol Collection : Sequence {
associatedtype Index : Comparable
var startIndex: Index { get }
var endIndex: Index { get }
var isEmpty: Bool { get }
var count: Int { get }

subscript(position: Index) -> Element { get }
subscript(bounds: Range<Index>) -> SubSequence { get }
}

集合类型在Sequence的基础上扩展了下标访问、元素个数能特性。我们常用的集合类型Array,Dictionary,Set都遵循该协议。

流程控制

for-in、while、if、guard、switch、continue、break、fallthrough 含义都比较直观,只是switch多分支判断中,case内结束默认是break了,除非fallthroght。

1
2
3
4
5
6
7
8
func startTalking(_ wage: Float) {
  guard let wage = wage where wage > 0 else {
    print("nothing to say")
    return
  }
 
  print("hahaha")
}

函数

function类型在swift语言中是first class citizen,其语法规范大概如下:

1
2
func 函数名(参数标签 参数名: 类型=默认参数值) -> 返回值 {
}

参数标签若与参数名一致,则可省略;参数标签也可以使用_,调用时可省略参数名(标签)。
函数支持return隐式返回、默认参数值、可变参数、inout参数(&),并且支持嵌套函数。

闭包

“闭包就是一个匿名的函数体” 🆚 “函数是一个有名称的闭包”

1
2
3
{(parameters) -> returnType in  
执行体
}

这是一个闭包表达式的语法。

闭包利用上下文推断参数及返回值,支持尾随闭包、隐式返回单表达式闭包,使用$0引用参数,这些都是编译器的一些语法糖。

比函数体晚执行的(通常是异步处理)称为逃逸闭包,需要使用@escaping进行修饰。

Class与Struct

class与struct是Swift中使用最广泛的类型。

0、类型声明、初始化、实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
var nickName: String = ""
var age: Int

init(age: Int) {
self.age = age
}

func work() -> Float {
 
}
}

var p = Person(age: 20)
p.nickName = "Jason"
print(p)

1、存储属性与计算属性

计算属性就是提供get/set方法的属性方法,依赖其他数据,一般作为便捷访问方法;

存储属性拥有存储空间,相当于Objc类中拥有实例变量的属性。extension中只能声明计算属性,而没有存储属性。

2、方法

其实class、struct、enum中都可以声明方法,方法就是类型中定义的函数。类型、属性与方法都有权限修饰(见下文)。

3、对象模型

Swift中的类(class)可以继承,也可以不继承其他Swift类。不继承其他类的,实际上他们都是SwiftObject的派生类。也就是在Swift中有SwiftObjectNSObject两个根类)。Swift跟Objc一样其实是单一继承链。但不一样的是,Swift中的协议可以通过extension来提供默认的协议方法实现,使得Swift中的类能获得类似Mixin的能力。

struct是值类型,不支持继承。

1
2
3
4
5
6
7
8
#if __has_attribute(objc_root_class)
__attribute__((__objc_root_class__))
#endif
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
@private
  Class isa;
  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}

4、内存模型

struct与class的区别,首要在于值与引用的区别。引用意味着class的实例可以是共享的,而struct的”实例”都是copy的。值与引用各有其适用场景,值可以避免数据跨域的污染,而引用则有利于内存优化。

内存管理上,Swift类依然使用引用计数。如上面提到的,Swift为了支持与Objective-C的互操作,设计了SwiftObject的根类。可以参考Swift的Github站点上对class的TypeMetadata的说明

destructor(-2)/isa(0)/super(1)//reserved(2,3)/rodata(4,低位1是swift)/class(5,32bit)/instance addrp(32bit)/instance size(32bit)/…

具体的内存布局我们会另外再说明。

5、权限修饰:open/public/internal/fileprivate/private

open:访问不受限,外部类可以继承、override方法,对于模块定义的类,如果允许外部进行继承是需要修饰为open的;
public:访问不受限,但仅本模块可以继承、修改方法/属性;
internal:访问限制在模块内部;
fileprivate:访问限制在同一文件内部;
private:访问限制在类内部;

一些常见协议

自定义输出支持:

1
2
3
public protocol CustomStringConvertible {
var description: String { get }
}

调试输出支持:

1
2
3
public protocol CustomDebugStringConvertible {
var debugDescription: String { get }
}

可散列化支持:

1
2
3
4
5
6
7
8
public protocol Hashable : Equatable {
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
}

public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}

作为集合的key需要是可哈希的(hashable),实现Hashable协议能使集合数据的查找复杂度降低到O(1)。

自定义类型实现Hashable的方式,可以重写hash(into:)方法。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Color: Hashable {
let red: UInt8
let green: UInt8
let blue: UInt8

// Synthesized by compiler
func hash(into hasher: inout Hasher) {
hasher.combine(self.red)
hasher.combine(self.green)
hasher.combine(self.blue)
}

// Default implementation from protocol extension
var hashValue: Int {
var hasher = Hasher()
self.hash(into: &hasher)
return hasher.finalize()
}
}// 案例来自nshipster

基本数据类型是可哈希的。

比较运算支持:

1
2
3
4
5
6
7
8
9
10
public protocol Comparable : Equatable {

static func < (lhs: Self, rhs: Self) -> Bool

static func <= (lhs: Self, rhs: Self) -> Bool

static func >= (lhs: Self, rhs: Self) -> Bool

static func > (lhs: Self, rhs: Self) -> Bool
}

序列化编码支持:

1
2
3
4
5
6
7
8
9
public typealias Codable = Decodable & Encodable

public protocol Decodable {
init(from decoder: Decoder) throws
}

public protocol Encodable {
func encode(to encoder: Encoder) throws
}

Swift 4.0引入Codable使得可原生支持模型JSON转换。

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Person: Codable {
let name: String
let age: Int
var additionInfo: String?

enum CodingKeys: String, CodingKey {
case name, age
case additionInfo = "ext"
}

init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
age = try values.decode(Int.self, forKey: .age)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
}
}

// 编解码过程

1
2
3
4
let person = try JSONDecoder().decode(Person.self, from: json.data(using: .utf8)!)

let data = try? JSONEncoder().encode(pObj)
let dataObject = try? JSONSerialization.jsonObject(with: data!, options: [])

对于需要自定义key的可以内置一个CodingKeys的enum定义来提供。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum Video: Codable {
case youTube(id: String)
case vimeo(id: String)
case hosted(url: URL)
case local(LocalVideo)
}

extension Video {
enum CodingKeys: String, CodingKey {
case youTube
case vimeo
case hosted = "custom"
}
}

// 关联case的key自定义
extension Video {
enum YouTubeCodingKeys: String, CodingKey {
case id = "youTubeID"
}
}

特性修饰词

特性修饰词是进入Swift开发非常常见的。

@main

声明为Swift程序入口

1
2
3
4
5
6
@main
struct MyTopLevel {
static func main() {
// Top-level code goes here
}
}

@available

1
2
@available(platform name version number, *)
@available(swift version number)

nonobjc

废除一个隐含的objc特性。

objc

用 objc 特性标记的类必须继承自一个 Objective-C 中定义的类。如果你把 objc 用到类或协议中,它会隐式地应用于该类或协议中 Objective-C 兼容的成员上。

objcMembers

Swift中定义的方法默认是不能被OC调用的,除非我们手动添加@objc标识。但如果一个类的方法属性较多,这样会很麻烦,于是有了这样一个标识符@objcMembers,它可以让整个类的属性方法都隐式添加@objc。

dynamicCallable与dynamicMemberLookup

前者实现dynamicallyCall(withArguments:)dynamicallyCall(withKeywordArguments:),实例可视为可调用函数,也就是instance(…)是可以的。

后者类型必须实现一个 subscript(dynamicMemberLookup:) 下标脚本方法,动态的通过.属性的方法来访问查询。
比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@dynamicMemberLookup
struct DynamicStruct {
let dictionary = ["someDynamicMember": 325,
"someOtherMember": 787]
subscript(dynamicMember member: String) -> Int {
return dictionary[member] ?? 1054
}
}
let s = DynamicStruct()

// Using dynamic member lookup
let dynamic = s.someDynamicMember
print(dynamic)
// Prints "325"

escaping

逃逸闭包声明。

propertyWrapper

Swift 5.1开始支持@propertyWrapper,属性封装器。@propertyWrapper是Swift 5之后非常重要的特性,应用场景也很多。SwiftUI的推出,@State,@Binding,@EnvironmentObject 等这些都依赖于属性封装器的支持。

包装器必须定义一个 wrappedValue 实例属性。包装属性的值就是这个属性暴露的 getter 和 setter。大多数情况下, wrappedValue 是一个计算值,但也可以是存储值。

包装器定义和管理任何被包装值需要的存储。编译器会通过在包装的属性名称前添加下划线( _ )来合成对应实例所需要的存储——比如说, someProperty 的包装器会以 _someProperty 的形式存储。包装器合成存储的访问控制权限为 private 级。

propertyWrapper大概有4个用例场景:

  • 值约束
  • 属性赋值时进行值转换
  • 改变合成的比较语义
  • 属性访问检查

详细可查看nshipster

包装属性的投射值就是属性包装器可以用于暴露额外功能的第二个值。属性包装器类型的作者负责决定投射值的意义并且定义暴露的投射值的接口。要从属性包装器中投射值,在包装器类型中定义 projectedValue 实例属性。编译器会通过在包装属性名称前添加一个美元符号( $ )来为投射的值合成标记

testable

允许测试模块像public一样访问internal的的代码。

@State、@Binding、@ObservedObject、@EnvironmentObject

SwiftUI内容。

Comments