Model 核心逻辑代码,UI 各种View组件的拼接布局。
二者应该较好的分离开,就像 React
的函数组件返回<></>和内部的代码逻辑分开放一样。
Model 可以是结构体、SQL 数据库、网络连接处理、REST API查询、某个正在执行机器学习任务的程序等等,模型通常是和用户界面无关的。用户界面只关注接受显示内容然后将新内容刷新到界面上就好了,复杂的逻辑不应和UI缠绕在一起。
SwiftUI 模型是 MVVM。
MVVM(Model-View-ViewModel)是一种常见的软件架构模式,在 iOS(SwiftUI)、Android、WPF、前端框架(Vue)等领域都非常流行。 它的核心目标只有一个: 让界面(UI)和业务逻辑分离。
毕竟把网络请求、数据处理、按钮点击、界面刷新全塞进 ViewController 里,最后会长成一个几千行的怪物文件。人类管它叫架构失控,编译器管它叫正常现象。
MVVM 可以理解为:
先知道是什么就好了,它们各有非常多的特性与注意事项,我们一时半会学不完的。
struct Player {
var name: String
var level: Int
}
let p = Player(name: "Tom", level: 10)不是 stored property,而是 computed property(计算属性)。它不占用额外存储空间。每次访问会重新计算。
struct Rectangle {
var width: Double
var height: Double
var area: Double {
width * height
}
}在 Swift 中,struct 里的 constant lets 通常指使用 let 声明的存储属性(Stored Property)。
即使属性是 var,如果实例本身是 let,也不能修改
struct Person {
let name: String
let age: Int
}
// 创建时必须初始化:
let p = Person(name: "Tom", age: 20)
// 初始化后不能修改:
p.name = "Jerry" // 编译错误
p.age = 30 // 编译错误Swift 中 struct 不仅可以有存储属性(stored properties),还可以定义函数(Methods)。和很多语言不同,Swift 的结构体是“一等公民”,功能非常完整,甚至可以实现协议、扩展、泛型等。
struct Player {
var name: String
var hp: Int
func printInfo() {
print("name=\(name), hp=\(hp)")
}
}
let p = Player(name: "Tom", hp: 100)
p.printInfo()函数参数 内参名 和 外参名 能有两套
func multiply2(_ operand: Int, by otherOperand: Int) -> Int {
return operand * otherOperand
}
struct SwiftUIView3: View {
var body: some View {
let p = Player(name: "Tom", level: 10, constIntNum: 100)
// 2
Text(String(p.multiLevel))
// 2
Text(String(p.multiply1(operand: 1, by: 2)))
// 6
Text(String(p.multiply2(2, by: 3)))
}
}可以理解为构造函数
struct Player2 {
var name: String
var age: Int
var isStudent: Bool = false // 默认值
}
// _ = Player2(name: "gwl", age: 24)
struct Player3 {
var name: String
var age: Int = 0
init(name: String) {
self.name = name
}
}
// _ = Player3(name: "gwl")所以函数传参就要注意了
如果传结构体里面东西特别大,这会不会成为传参拷贝效率问题,swift 中有写时拷贝优化一般不用担心,我们以为拷贝了但背后不一定拷贝出副本。
truct Player4{
var age: Int
}
// let p4 = Player4(age: 100)
// 不合法: p4.age = 99
class Player5{
var age: Int = 0
}
// let p5 = Player5()
// p5是引用类型可以改,p5.age = 99View 都是 struct,不是 class。View 是值类型,View在顶层是 let 的 不可修改的。
你有一系列视图,每当发生某些变化导致var body 不同时,系统就会调用变量 body, 获取新的值,并将其放置到位。
如果想在视图中实现可变状态,就必须使用 @State
这种特殊的表示方法。
非常像 React.js 的 useState
struct CounterView: View {
// 声明一个状态变量:初始值为 0
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("当前计数: \(count)")
.font(.largeTitle)
Button("增加") {
count += 1 // 修改 @State 值会自动触发视图更新!
}
.buttonStyle(.borderedProminent)
Button("重置") {
count = 0
}
}
.padding()
}
}@Binding 是 SwiftUI 中的一个属性包装器(Property
Wrapper),用于在视图之间双向绑定(two-way
binding)数据。它允许子视图读取并修改父视图中的状态(@State
或 @StateObject 等管理的数据),从而实现数据流的更新和 UI
同步。
父视图 持有状态
struct ParentView: View {
@State private var isOn = false // 父视图的可变状态
var body: some View {
VStack {
Text(isOn ? "✅ 开启" : "❌ 关闭")
// 传递绑定(注意:使用 $ 符号)
ChildView(isOn: $isOn)
}
}
}子视图 接收绑定
struct ChildView: View {
@Binding var isOn: Bool // 声明为 @Binding
var body: some View {
Toggle("切换开关", isOn: $isOn) // 直接绑定到本地属性,自动同步
// 或手动修改:
// Button("切换") { isOn.toggle() }
}
}优势是 sharing、同时也是弱势带来风险使得它们是可变的。
class 可以用自身的指针哈希地址标识自己,而 struct 就要自己想办法了,比如在 ForEach 中指定 id 标识问题。
init 是一个特殊的 static func 当创建实例时被调用。
enum FastFoodMenuItem {
case hamburger
case fries
case drink
case cookie
}在 Swift 中,枚举(enum)的关联值(Associated Values) 是一个非常强大且灵活的特性,允许你为枚举的每个具体 case 携带额外的数据(类型安全的元组)。这些数据是临时的、每次实例化时可变的,并只存在于该枚举实例的生命周期中。
enum Shape {
case circle(radius: Double)
case rectangle(width: Double, height: Double)
case triangle(points: (Double, Double), (Double, Double), (Double, Double))
func area() -> Double {
switch self {
case .circle(let r):
return .pi * r * r
case .rectangle(let w, let h):
return w * h
// 允许使用 _ 省略参数
case .triangle(_, let p2, let p3):
// 三角形面积计算(省略具体实现)
return 0.0
}
}
}
//let c = Shape.circle(radius: 2.0)
//print(c.area()) // 12.566370614359172let menuItem = FastFoodMenuItem.hamburger(patties: 2)
var otherItem: FastFoodMenuItem = .cookie
// 下面的是不允许的
// var yetAnotherItem = .cookieenum TeslaModel: CaseIterable {
case X
case S
case Three
case Y
}for model in TeslaModel.allCases {
reportSalesNumbers(for: model)
}
func reportSalesNumbers(for model: TeslaModel) {
switch model { ... }
}switch menuItem {
case .hamburger: print("burger")
case .fries: print("fries")
case .drink: print("drink")
case .cookie: print("cookie")
}什么都不做
var menuItem = FastFoodMenuItem.hamburger(patties: 2)
switch menuItem {
case .hamburger: break
case .fries: print("fries")
case .drink: print("drink")
case .cookie: print("cookie")
}很常规,switch case 中的 default
var menuItem = FastFoodMenuItem.cookie
switch menuItem {
case .hamburger: break
case .fries: print("fries")
case .drink: print("drink")
default: print("other")
}let s: String = "hello"
switch s {
case "goodbye": ...
case "hello": ...
default: ...
}每个case可以多行代码,不允许 fall through到下一个case
默认不贯穿,每个 case 执行完自动退出,用 fallthrough 明确声明(但通常不推荐)
var menuItem = FastFoodMenuItem.fries(size: .large)
switch menuItem {
case .hamburger: print("burger")
case .fries:
print("yummy")
print("fries")
case .drink:
print("drink")
case .cookie: print("cookie")
}C++ 与 swift 对比
// C++:默认贯穿(易错!)
switch(x) {
case 1:
cout << "One"; // 没有 break → 继续执行 case 2!
case 2:
cout << "Two";
}
// Swift:不会贯穿(安全)
switch x {
case 1:
print("One") // 自动 break,不会执行 case 2
case 2:
print("Two")
}Swift是个强类型语言,支持泛型数据结构,如之前用到的 Array
如
struct Array<Element>{
...
func append(_ element: Element) { ... }
}
var a = Array<Int>()
a.append(5)
a.append(22)Swift 要求每个变量必须始终具有值。Optional适用于任何变量。
和其他语言 -1、null pointer 相似。Swift不使用空指针,也不用-1 表示无效号码。使用Optional,Optional是一种类型。
Optional只能处于两种状态,已设置和未设置。
enum Optional<T> {
case none
case some(T)
}使用场景如
let colors = [Color.blue, .red, .green, .yellow]
let index: Optional<Int> = colors.firstIndex(of: Color.orange)// var hello: Optional<String> = .none
var hello: String?
// var hello: Optional<String> = .some("hello")
var hello: String> = "hello"
// var hello: Optional<String> = .none
var hello: String> = nillet hello: String? = "Hello"
print(hello!) // 强制解包(force unwrapping)
// ! 是 强制解包运算符(Force Unwrapping Operator),作用是:
// 将可选类型(String?)直接解包为非可选类型(String),前提是该可选值确实有值(not nil)使用了 Swift 的 可选绑定(Optional Binding),具体是 if let 语法,作用是: 安全地解包(unwrap)可选值(Optional),并仅在 hello 有值(即非 nil)时执行 if 块中的代码。
let hello: String? = "Hello"
if let safehello = hello {
print(safehello)
} else {
// do something else
}switch hello {
case .none: // raise an exception(crash)
case .some(let data): print(data)
}
switch hello {
case .none: { //do something else }
case .some(let data): print(data)
}let x: String? = ...
// x不为 .none 就x赋给y,否则用 "foo"
let y = x ?? "foo"
// 形如
switch x {
case .none: y = "foo"
case .some(let data): y = data
}struct Person {
let name: String
init?(name: String) {
guard !name.isEmpty else { return nil } // fails if name is empty
self.name = name
}
}
let p1 = Person(name: "Ada") // Person? = Person(name: "Ada")
let p2 = Person(name: "") // Person? = nilstruct Vehicle {
init?(type: String) {
// return nil if there is no Vehicle with that type
}
}
if let car = Vehicle(type: "car") {
// do something with the car
}在 Swift 中,扩展(Extensions) 是一种强大的机制,允许你在不修改原始源码的前提下,为已有的类型(如类、结构体、枚举,甚至协议)添加新功能。
尤其这对 protocols 及其重要。
扩展 Int
extension Int {
// 计算当前数字是否为偶数
var isEven: Bool {
self % 2 == 0
}
// 计算当前数字是否为奇数
var isOdd: Bool {
!isEven
}
}
let num = 7
print(num.isEven) // false
print(num.isOdd) // trueextension Color {
var name: String {
// figure out and return ad name for this Color
}
}扩展 String
extension String {
/// 将字符串重复指定次数并返回新字符串
func repeated(times: Int) -> String {
guard times > 0 else { return "" }
return String(repeating: self, count: times)
}
/// 去除首尾空格(类似 `trim()`,但更安全)
func trimmed() -> String {
self.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
let result = "swift".repeated(times: 3) // "swiftswiftswift"
let clean = " hello ".trimmed() // "hello"扩展 Array
extension Array {
/// 安全下标访问(越界返回 nil)
subscript(safe index: Index) -> Element? {
indices.contains(index) ? self[index] : nil
}
}
let arr = [10, 20, 30]
print(arr[safe: 1]) // Optional(20)
print(arr[safe: 5]) // nil(安全避免崩溃)扩展 Color
extension Color {
init?(named name: String) {
switch name {
case "green": self = Color.green
...
default: return nil
}
}
var name: String? {
// return the name of this color (if it has one, else nil)
}
}