Swift & SwiftUI 学习笔记

参考:

The Swift Programming Language (6 beta)

SwiftUI Documentation

SwiftUI Tutorials

App Dev Tutorials

Swift 语法

变量与常量

  • 常量: let a: Double = 0 ;变量使用 var ;默认类型推断为 Int 和 Double;
  • Swift 不会做任何隐式类型转换;可以用 "\(name)\" 表示转化成字符串;
  • 类型别名: typealias newname = type
  • 内置基本数据类型:Int(64位),UInt(一般不用),Float,Double(64位),Bool,String,Character(同样用双引号);
  • 集合数据类型:Array,Dictionary,Set;
  • 引用类型:Class(Struct 是值类型),Closure,Function;
  • 数组声明:var someInts = [Int](repeating: 0, count: 3) ;可以 append() ,可以用 + 合并;
  • 字典:var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]
  • 枚举:
1
2
3
4
5
6
7
8
9
10
11
12
13
enum Student{
case Name(String)
case Mark(Int,Int,Int)
}

var studMarks = Student.Mark(98,97,95)

switch studMarks {
case .Name(let studName):
print("\(studName)。")
case .Mark(let Mark1, let Mark2, let Mark3):
print("\(Mark1),\(Mark2),\(Mark3)")
}
1
2
3
enum Month: Int { // 若不写明为 Int,则不会默认为每个成员分配一个整型数值
case January = 1, February, March, April, May, June, July, August, September, October, November, December
}

Optional 类型

  • 在声明的类型后添加 ? 表示一个 Optional 类型值,当未赋值时,此值将为 nil
  • 在声明的类型后添加 ! 表示一个 Implicity Unwrapped Optional (隐式解包可选类型)值,此后取值不再需要加 ! ,如果取值时值为 nil 会直接运行错误,适合确定值永远不为 nil 时使用;
  • 在确定不为空时使用 ! 强制解包;显式类型转换会返回一个 Optional 已处理转换失败的情况;
  • 可选绑定(Optional Binding):if let name=optionalName {} else {}guard let name=optionalName else {}
  • 合并空值运算符: a ?? b 在 a 有值时展开,a 为 nil 时返回 b;b 表达式返回值必须与 a 中存储值同类型;
  • 可选链: if let a = A.B?.C

语句

  • 行末无需分号,但一行多个表达式间需要分号;判断子句无需小括号;
  • where 子句与 let 配合,多个子句之间用逗号分分隔, if 语句中必须所有子句均满足, switch 子句中只需满足一个子句;
1
if let hello = optionalHello where hello.hasPrefix('H'), let name = optionalName {}
  • 范围(Range): 0..<10 左闭右开, 0...10 全闭;
  • 学习将不需要的循环变量设为 _

函数与闭包

  • 函数声明:func doSomething() -> returnType {}
  • 调用函数时,第一个参数无需写明形参名,其余参数均需写明,除非函数定义中使用 _ 作为 argumentLabel(不指定的话默认使用 parameterName 作为 argumentLabel);
  • 函数签名可以作为类型: var addition: (Int, Int) -> Int = sum
  • 闭包本质是代码块(A chunk of code),函数属于闭包:
1
2
3
4
5
6
7
{(Int, Int) -> Bool in ... }
// 闭包可以使用参数名称缩写并省略定义:
var reversed = names.sorted( by: { $0 > $1 } )
// Swift 定义了对 > 的“接受两个字符串并返回 bool” 的实现:
var reversed = names.sorted(by: >)
// 尾随闭包作参数,支持将其作为最后一个参数调用:
func aFunc(closure: () -> Void) { ... }
  • 形参 inout:相当于变参,调用时传递 &a;
  • 泛型: func swap<T> (_ a: inout T, _ b: inout T) ,可以在尖括号中添加 T 需要继承的父类或遵循的协议;
  • 关联类型:在协议中使用 associatedtype Item 定义一个 Item 类型,在实现时使用 typealias Item = Int 指定关联类型;
  • 可变参数(Variadic Parameters): Double... ,使用 for-in 遍历;
  • Trailing Closures:若函数最后一个参数是闭包,则可以写到 () 之后: aFunc(){ ... }
  • Completion Handler:函数末尾回调,通常使用 Trailing Closures 写;
  • 逃逸闭包(Escaping Closures):闭包可能在函数返回后被调用,如异步操作或被存储在外部变量中,需要标注 @escaping ;此时闭包会被储存在堆中,所捕获的变量生命周期延长;
  • Modifier:一类函数,调用时返回另一个实例,从而允许链式调用:a.f1().f2().f3();

  • 只能单继承;同名函数必须使用 override 关键字,不使用将编译错误;不允许 overwrite;
  • lazy 成员属性在第一次使用时才计算器初始值;
  • 构造器(Initializer): init(name:String) { self.name = name } ;调用构造器时所有参数均需写明形参名;可失败构造器: init? ;析构器: deinit {...} ,没有小括号;
  • 自定义构造器后便不再提供默认构造器,且要求所有属性均被初始化;
  • 使用 super.init(name: name) 调用父类构造函数; final 可防止 override;便利( conveience)构造器指定部分初始值后调用其它构造函数;
  • if let square = shape as? Square 使用 as? 实现向下转换可选解包,使用 as! 实现强制解包;使用 is 判断是否为某个子类;
  • 使用 [Any] 定义可以存储任意不同引用类型的数组,使用 [AnyObject] 定义可以存储不同类类型的数组;
  • 类比结构体增加了:继承,运行时类型判断,释放被分配的资源,多次引用;
  • struct 内的方法不能改变其属性,必须声明为 mutating ;而 class 内的方法可以改变其内部属性而无需 mutating
  • 协议:类似虚函数 / 接口,定义一系列必须被遵循者实现的方法;使用 get set 定义可读写性;同时拥有父类和遵循协议的类,冒号后先写父类再写协议;函数名前 required 关键字要求遵循者必须 override 它(遵循者也应标明 required ,类似纯虚函数);
  • 计算属性(Calculated Properties):不直接存储值,每次通过计算得到;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class sample {
var no1 = 0.0, no2 = 0.0
var length = 300.0, breadth = 150.0

var middle: (Double, Double) {
get{ return (length / 2, breadth / 2); }
set(axis){
no1 = axis.0 - (length / 2)
no2 = axis.1 - (breadth / 2)
}
}

var counter: Int = 0{ // 属性观察器
willSet(newTotal){ print("\(newTotal)"); }
didSet{ print(\(counter - oldValue)"); }
}
}
  • 类型属性(Type Properties): static 属性(Singleton 单例);类型方法(Type Methods): static 方法;
  • 下标脚本(Subscripts):类似 C++ 中重载 [] ;可以重载多个,会自动匹配;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct subexample {
let decrementer: Int
subscript(index: Int) -> Int {
return decrementer / index
get {
// 用于下标脚本值的声明
}
set(newValue) {
// 执行赋值操作
}
}
}

let division = subexample(decrementer: 100)
print("100 除以 9 等于 \(division[9])")
  • 避免循环引用导致引用计数无法归零:可能为 nil 时使用弱引用( weak),反之使用无主引用( unowned );
  • 通过扩展 extension 给类或协议增加新的属性、方法、构造器等;
  • Swift 的四种访问控制:public,internal(默认,同模块内可使用),fileprivate(仅当前源文件可使用),private;
  • 属性包装器(Property Wrapper):分离变量定义与存储,通过 propertyWrapper 注解定义变量行为和合法性判断(类似计算属性);
  • ClassName.self() 代表类类型本身(元类型);

其它

  • Swift 采用 Unicode 编码,变量名可以为中文或 emoji 等;
  • Swift 对空格有要求;
  • Swift 3 后取消了 ++-- 操作以及 switch-for(类似 C 中的 for) 循环;
  • Swift 检查变量使用前初始化、索引检查、整数溢出、Optional 处理 nil 值、内存管理;
  • Swift 的 /*...*/ 注释可以嵌套;
  • 使用 do-throw-catch 异常处理; try? 若抛出错误则返回 niltry! 若错误直接崩溃;
  • some 用于声明隐式返回类型(Opaque Return Types), some Shape 表示返回一个遵循 Shape 协议的对象,但不暴露具体对象;
  • Swift 提供指针类型,但不常用;
  • 一些协议:
    • Encodable 协议确保能转成 JSON 等格式,Decodable 反之,Codable 是二者结合;
    • Hashable 协议使其能执行相等运算;
    • Identifiable 使其能被用于创建 Lists 和 ForEach(传入一个 List 和一个 trailing closure,对每个 List 元素执行闭包);
  • 泛型(Generic Types):类似 C++ 的泛型;可以规定泛型服从的协议,有点类似 C++20 的 Concept;

SwiftUI

框架

  • SwiftUI 遵从 MVVM(Model-View-ViewModel)软件架构模式;声明式语言,用户不指定代码的具体实现;响应式更新,在被观察变量被改变后自动更新组件等状态;
  • 原先的 MVC 架构:ViewController 创建 View 并将其显示,或使用 Outlet( @IBOutlet weak var myLabel: UILabel! )引用 storyboard 或 xib 界面组件,使用 Action( @IBAction func buttonClicked(_ sender: UIButton) { myLabel.text = "1"; } ) 接收组件消息并控制组件行为;
  • MVC 架构初始默认文件:
    • AppDelegate.swift :应用代理类,用于处理应用的生命周期时间;方法有:启动前调用,进入后台后调用,进入前台前调用;
    • SceneDelegate.swift :用于显示和管理用户界面窗口,每个窗口对应一个 UIWindows ;方法有:设置初始 ViewController,场景进入后台后调用;
    • ViewController.swift :用于管理用户界面交互;方法有:视图初始化配置,处理用户输入与交互,管理视图生命周期(视图出现前调用,视图消失后调用);
  • MVVC:
    • View 负责显示组件并接收用户的交互(使用 Combine Framework);
      • 通过@State 声明视图内部(包括子视图)私有状态(不能是计算属性),一般为基本类型和结构体;
      • 通过 @StateObject 自己创建和管理一个可观测对象(ObservableObject,往往是ViewModel);
      • 通过 @ObservedObject观察一个外部的遵循 ObservableObject 协议的实例;
      • 通过 @Published 在 ObservableObject 实例中声明需要其它视图响应的变量;
      • SwiftUI 使用 @State 类型包装器来允许在 Struct 中修改值;使用此注解时,值被移出 struct,移入 SwiftUI 管理的共享存储中;
      • 子视图 View 若想共享 @State 值,可以使用 @Binding (二者类似 @StateObject@ObservedObject 的关系,一个声明一个使用)实现双向绑定,且声明子视图时在传递此值时前面加 $ 符号;
    • ViewModel 充当连接 View 与 Model 的 Binder 角色;
      • ViewModel 的属性使用 @Published 标记以使 View 在这些属性改变时自动刷新;
      • ViewModel 引用 Model 并处理数据;

特性

  • @Environment 用于访问 SwiftUI 中由系统或其他视图提供的环境,通常为全局配置或状态,如颜色方案(亮暗模式)、字体、布局方向等。
  • @EnvironmentObject 用于实现依赖注入,父类可以通过 .environment() modifier 指定子类使用 @EnvironmentObject 定义的可观测对象;类似传参,但会实时更新和共享(类似传指针);
  • 通过 modifier 增加定义的组件的属性和内容;自定义 View 时需要声明一个继承 View 的 struct,然后声明计算属性 body;
  • SwiftUI 使用 ViewBuilder 语法糖,通过重载静态方法等技术允许并列声明组件,更接近自然语言的效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct MyView: View {
var body: some View {
VStack {
Text("Hello, World!")
if Bool.random() {
Text("This is a random text!") // 并列声明两个 Text View,支持 if
}
}
}
}

// Desugar 后:
struct MyView: View {
var body: some View {
VStack {
let content = ViewBuilder.buildBlock(
Text("Hello, World!"),
Bool.random() ? ViewBuilder.buildBlock(Text("This is a random text!")) : ViewBuilder.buildBlock(EmptyView())
)
content
}
}
}