code-abyss 1.6.16 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/bin/install.js +59 -163
- package/bin/lib/ccline.js +82 -0
- package/bin/lib/utils.js +61 -0
- package/package.json +5 -2
- package/skills/SKILL.md +24 -16
- package/skills/domains/ai/SKILL.md +2 -2
- package/skills/domains/ai/prompt-and-eval.md +279 -0
- package/skills/domains/architecture/SKILL.md +2 -3
- package/skills/domains/architecture/security-arch.md +87 -0
- package/skills/domains/data-engineering/SKILL.md +188 -26
- package/skills/domains/development/SKILL.md +1 -4
- package/skills/domains/devops/SKILL.md +3 -5
- package/skills/domains/devops/performance.md +63 -0
- package/skills/domains/devops/testing.md +97 -0
- package/skills/domains/frontend-design/SKILL.md +12 -3
- package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
- package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
- package/skills/domains/frontend-design/engineering.md +287 -0
- package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
- package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
- package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
- package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
- package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
- package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
- package/skills/domains/infrastructure/SKILL.md +174 -34
- package/skills/domains/mobile/SKILL.md +211 -21
- package/skills/domains/orchestration/SKILL.md +1 -0
- package/skills/domains/security/SKILL.md +4 -6
- package/skills/domains/security/blue-team.md +57 -0
- package/skills/domains/security/red-team.md +54 -0
- package/skills/domains/security/threat-intel.md +50 -0
- package/skills/orchestration/multi-agent/SKILL.md +195 -46
- package/skills/run_skill.js +139 -0
- package/skills/tools/gen-docs/SKILL.md +6 -4
- package/skills/tools/gen-docs/scripts/doc_generator.js +363 -0
- package/skills/tools/lib/shared.js +98 -0
- package/skills/tools/verify-change/SKILL.md +8 -6
- package/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
- package/skills/tools/verify-module/SKILL.md +6 -4
- package/skills/tools/verify-module/scripts/module_scanner.js +171 -0
- package/skills/tools/verify-quality/SKILL.md +5 -3
- package/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
- package/skills/tools/verify-security/SKILL.md +7 -5
- package/skills/tools/verify-security/scripts/security_scanner.js +283 -0
- package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
- package/skills/domains/COVERAGE_PLAN.md +0 -232
- package/skills/domains/ai/model-evaluation.md +0 -790
- package/skills/domains/ai/prompt-engineering.md +0 -703
- package/skills/domains/architecture/compliance.md +0 -299
- package/skills/domains/architecture/data-security.md +0 -184
- package/skills/domains/data-engineering/data-pipeline.md +0 -762
- package/skills/domains/data-engineering/data-quality.md +0 -894
- package/skills/domains/data-engineering/stream-processing.md +0 -791
- package/skills/domains/development/dart.md +0 -963
- package/skills/domains/development/kotlin.md +0 -834
- package/skills/domains/development/php.md +0 -659
- package/skills/domains/development/swift.md +0 -755
- package/skills/domains/devops/e2e-testing.md +0 -914
- package/skills/domains/devops/performance-testing.md +0 -734
- package/skills/domains/devops/testing-strategy.md +0 -667
- package/skills/domains/frontend-design/build-tools.md +0 -743
- package/skills/domains/frontend-design/performance.md +0 -734
- package/skills/domains/frontend-design/testing.md +0 -699
- package/skills/domains/infrastructure/gitops.md +0 -735
- package/skills/domains/infrastructure/iac.md +0 -855
- package/skills/domains/infrastructure/kubernetes.md +0 -1018
- package/skills/domains/mobile/android-dev.md +0 -979
- package/skills/domains/mobile/cross-platform.md +0 -795
- package/skills/domains/mobile/ios-dev.md +0 -931
- package/skills/domains/security/secrets-management.md +0 -834
- package/skills/domains/security/supply-chain.md +0 -931
- package/skills/domains/security/threat-modeling.md +0 -828
- package/skills/run_skill.py +0 -153
- package/skills/tests/README.md +0 -225
- package/skills/tests/SUMMARY.md +0 -362
- package/skills/tests/__init__.py +0 -3
- package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
- package/skills/tests/test_change_analyzer.py +0 -558
- package/skills/tests/test_doc_generator.py +0 -538
- package/skills/tests/test_module_scanner.py +0 -376
- package/skills/tests/test_quality_checker.py +0 -516
- package/skills/tests/test_security_scanner.py +0 -426
- package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
- package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
- package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
- package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
- package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
- package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
- package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
- package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
|
@@ -1,755 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: swift
|
|
3
|
-
description: Swift 开发技术。SwiftUI、UIKit、Combine、Swift Concurrency、ARC 内存管理。当用户提到 Swift、SwiftUI、UIKit、Combine、iOS 开发、async/await 时使用。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 🍎 Swift 开发 · Swift Development
|
|
7
|
-
|
|
8
|
-
## 生态架构
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
Swift Concurrency
|
|
12
|
-
│
|
|
13
|
-
┌─────────┼─────────┐
|
|
14
|
-
│ │ │
|
|
15
|
-
SwiftUI UIKit Combine
|
|
16
|
-
│ │ │
|
|
17
|
-
└─────────┼─────────┘
|
|
18
|
-
│
|
|
19
|
-
Foundation
|
|
20
|
-
│
|
|
21
|
-
┌─────────┼─────────┐
|
|
22
|
-
CoreData Network ARC
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## SwiftUI 基础
|
|
26
|
-
|
|
27
|
-
### 视图声明
|
|
28
|
-
```swift
|
|
29
|
-
import SwiftUI
|
|
30
|
-
|
|
31
|
-
struct ContentView: View {
|
|
32
|
-
@State private var count = 0
|
|
33
|
-
@State private var isPresented = false
|
|
34
|
-
|
|
35
|
-
var body: some View {
|
|
36
|
-
VStack(spacing: 20) {
|
|
37
|
-
Text("Count: \(count)")
|
|
38
|
-
.font(.largeTitle)
|
|
39
|
-
.foregroundColor(.blue)
|
|
40
|
-
|
|
41
|
-
Button("Increment") {
|
|
42
|
-
count += 1
|
|
43
|
-
}
|
|
44
|
-
.buttonStyle(.borderedProminent)
|
|
45
|
-
|
|
46
|
-
Button("Show Sheet") {
|
|
47
|
-
isPresented = true
|
|
48
|
-
}
|
|
49
|
-
.sheet(isPresented: $isPresented) {
|
|
50
|
-
DetailView()
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
.padding()
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### 状态管理
|
|
59
|
-
```swift
|
|
60
|
-
// @State - 视图内部状态
|
|
61
|
-
struct CounterView: View {
|
|
62
|
-
@State private var count = 0
|
|
63
|
-
|
|
64
|
-
var body: some View {
|
|
65
|
-
Button("Count: \(count)") {
|
|
66
|
-
count += 1
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// @Binding - 双向绑定
|
|
72
|
-
struct ChildView: View {
|
|
73
|
-
@Binding var text: String
|
|
74
|
-
|
|
75
|
-
var body: some View {
|
|
76
|
-
TextField("Enter text", text: $text)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// @ObservedObject - 外部可观察对象
|
|
81
|
-
class ViewModel: ObservableObject {
|
|
82
|
-
@Published var items: [Item] = []
|
|
83
|
-
@Published var isLoading = false
|
|
84
|
-
|
|
85
|
-
func fetchItems() async {
|
|
86
|
-
isLoading = true
|
|
87
|
-
defer { isLoading = false }
|
|
88
|
-
|
|
89
|
-
items = await APIClient.shared.fetchItems()
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
struct ListView: View {
|
|
94
|
-
@StateObject private var viewModel = ViewModel()
|
|
95
|
-
|
|
96
|
-
var body: some View {
|
|
97
|
-
List(viewModel.items) { item in
|
|
98
|
-
Text(item.name)
|
|
99
|
-
}
|
|
100
|
-
.task {
|
|
101
|
-
await viewModel.fetchItems()
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// @EnvironmentObject - 环境对象
|
|
107
|
-
struct ParentView: View {
|
|
108
|
-
@StateObject private var settings = AppSettings()
|
|
109
|
-
|
|
110
|
-
var body: some View {
|
|
111
|
-
ChildView()
|
|
112
|
-
.environmentObject(settings)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### 列表与导航
|
|
118
|
-
```swift
|
|
119
|
-
struct ItemListView: View {
|
|
120
|
-
let items: [Item]
|
|
121
|
-
|
|
122
|
-
var body: some View {
|
|
123
|
-
NavigationStack {
|
|
124
|
-
List(items) { item in
|
|
125
|
-
NavigationLink(value: item) {
|
|
126
|
-
ItemRow(item: item)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
.navigationTitle("Items")
|
|
130
|
-
.navigationDestination(for: Item.self) { item in
|
|
131
|
-
ItemDetailView(item: item)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 自定义行视图
|
|
138
|
-
struct ItemRow: View {
|
|
139
|
-
let item: Item
|
|
140
|
-
|
|
141
|
-
var body: some View {
|
|
142
|
-
HStack {
|
|
143
|
-
AsyncImage(url: item.imageURL) { image in
|
|
144
|
-
image.resizable()
|
|
145
|
-
} placeholder: {
|
|
146
|
-
ProgressView()
|
|
147
|
-
}
|
|
148
|
-
.frame(width: 50, height: 50)
|
|
149
|
-
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
150
|
-
|
|
151
|
-
VStack(alignment: .leading) {
|
|
152
|
-
Text(item.name)
|
|
153
|
-
.font(.headline)
|
|
154
|
-
Text(item.description)
|
|
155
|
-
.font(.caption)
|
|
156
|
-
.foregroundColor(.secondary)
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### 动画与过渡
|
|
164
|
-
```swift
|
|
165
|
-
struct AnimatedView: View {
|
|
166
|
-
@State private var isExpanded = false
|
|
167
|
-
@State private var rotation = 0.0
|
|
168
|
-
|
|
169
|
-
var body: some View {
|
|
170
|
-
VStack {
|
|
171
|
-
Rectangle()
|
|
172
|
-
.fill(.blue)
|
|
173
|
-
.frame(width: isExpanded ? 200 : 100,
|
|
174
|
-
height: isExpanded ? 200 : 100)
|
|
175
|
-
.rotationEffect(.degrees(rotation))
|
|
176
|
-
.animation(.spring(response: 0.5, dampingFraction: 0.6), value: isExpanded)
|
|
177
|
-
|
|
178
|
-
Button("Toggle") {
|
|
179
|
-
withAnimation {
|
|
180
|
-
isExpanded.toggle()
|
|
181
|
-
rotation += 180
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 自定义过渡
|
|
189
|
-
extension AnyTransition {
|
|
190
|
-
static var slideAndFade: AnyTransition {
|
|
191
|
-
.asymmetric(
|
|
192
|
-
insertion: .move(edge: .trailing).combined(with: .opacity),
|
|
193
|
-
removal: .move(edge: .leading).combined(with: .opacity)
|
|
194
|
-
)
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
## UIKit 核心
|
|
200
|
-
|
|
201
|
-
### 视图控制器
|
|
202
|
-
```swift
|
|
203
|
-
import UIKit
|
|
204
|
-
|
|
205
|
-
class UserViewController: UIViewController {
|
|
206
|
-
private let tableView = UITableView()
|
|
207
|
-
private var users: [User] = []
|
|
208
|
-
|
|
209
|
-
override func viewDidLoad() {
|
|
210
|
-
super.viewDidLoad()
|
|
211
|
-
setupUI()
|
|
212
|
-
fetchUsers()
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private func setupUI() {
|
|
216
|
-
view.backgroundColor = .systemBackground
|
|
217
|
-
|
|
218
|
-
tableView.delegate = self
|
|
219
|
-
tableView.dataSource = self
|
|
220
|
-
tableView.register(UserCell.self, forCellReuseIdentifier: "UserCell")
|
|
221
|
-
|
|
222
|
-
view.addSubview(tableView)
|
|
223
|
-
tableView.translatesAutoresizingMaskIntoConstraints = false
|
|
224
|
-
NSLayoutConstraint.activate([
|
|
225
|
-
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
|
226
|
-
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
227
|
-
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
228
|
-
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
229
|
-
])
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private func fetchUsers() {
|
|
233
|
-
Task {
|
|
234
|
-
users = await APIClient.shared.fetchUsers()
|
|
235
|
-
tableView.reloadData()
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
extension UserViewController: UITableViewDataSource {
|
|
241
|
-
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
242
|
-
users.count
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
246
|
-
let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell
|
|
247
|
-
cell.configure(with: users[indexPath.row])
|
|
248
|
-
return cell
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### Auto Layout
|
|
254
|
-
```swift
|
|
255
|
-
class CustomView: UIView {
|
|
256
|
-
private let titleLabel = UILabel()
|
|
257
|
-
private let imageView = UIImageView()
|
|
258
|
-
|
|
259
|
-
override init(frame: CGRect) {
|
|
260
|
-
super.init(frame: frame)
|
|
261
|
-
setupViews()
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
required init?(coder: NSCoder) {
|
|
265
|
-
fatalError("init(coder:) has not been implemented")
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
private func setupViews() {
|
|
269
|
-
addSubview(imageView)
|
|
270
|
-
addSubview(titleLabel)
|
|
271
|
-
|
|
272
|
-
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
273
|
-
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
274
|
-
|
|
275
|
-
NSLayoutConstraint.activate([
|
|
276
|
-
imageView.topAnchor.constraint(equalTo: topAnchor, constant: 16),
|
|
277
|
-
imageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
|
|
278
|
-
imageView.widthAnchor.constraint(equalToConstant: 60),
|
|
279
|
-
imageView.heightAnchor.constraint(equalToConstant: 60),
|
|
280
|
-
|
|
281
|
-
titleLabel.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 12),
|
|
282
|
-
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
|
|
283
|
-
titleLabel.centerYAnchor.constraint(equalTo: imageView.centerYAnchor)
|
|
284
|
-
])
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### 导航与生命周期
|
|
290
|
-
```swift
|
|
291
|
-
class MainViewController: UIViewController {
|
|
292
|
-
override func viewDidLoad() {
|
|
293
|
-
super.viewDidLoad()
|
|
294
|
-
title = "Main"
|
|
295
|
-
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
|
296
|
-
barButtonSystemItem: .add,
|
|
297
|
-
target: self,
|
|
298
|
-
action: #selector(addTapped)
|
|
299
|
-
)
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
override func viewWillAppear(_ animated: Bool) {
|
|
303
|
-
super.viewWillAppear(animated)
|
|
304
|
-
// 视图即将显示
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
override func viewDidAppear(_ animated: Bool) {
|
|
308
|
-
super.viewDidAppear(animated)
|
|
309
|
-
// 视图已显示
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
@objc private func addTapped() {
|
|
313
|
-
let detailVC = DetailViewController()
|
|
314
|
-
navigationController?.pushViewController(detailVC, animated: true)
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
## Combine 响应式编程
|
|
320
|
-
|
|
321
|
-
### Publisher 与 Subscriber
|
|
322
|
-
```swift
|
|
323
|
-
import Combine
|
|
324
|
-
|
|
325
|
-
class DataService {
|
|
326
|
-
private var cancellables = Set<AnyCancellable>()
|
|
327
|
-
|
|
328
|
-
func fetchData() {
|
|
329
|
-
URLSession.shared.dataTaskPublisher(for: url)
|
|
330
|
-
.map(\.data)
|
|
331
|
-
.decode(type: [Item].self, decoder: JSONDecoder())
|
|
332
|
-
.receive(on: DispatchQueue.main)
|
|
333
|
-
.sink(
|
|
334
|
-
receiveCompletion: { completion in
|
|
335
|
-
switch completion {
|
|
336
|
-
case .finished:
|
|
337
|
-
print("Completed")
|
|
338
|
-
case .failure(let error):
|
|
339
|
-
print("Error: \(error)")
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
receiveValue: { items in
|
|
343
|
-
print("Received \(items.count) items")
|
|
344
|
-
}
|
|
345
|
-
)
|
|
346
|
-
.store(in: &cancellables)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### 操作符链
|
|
352
|
-
```swift
|
|
353
|
-
class SearchViewModel: ObservableObject {
|
|
354
|
-
@Published var searchText = ""
|
|
355
|
-
@Published var results: [Result] = []
|
|
356
|
-
|
|
357
|
-
private var cancellables = Set<AnyCancellable>()
|
|
358
|
-
|
|
359
|
-
init() {
|
|
360
|
-
$searchText
|
|
361
|
-
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
|
362
|
-
.removeDuplicates()
|
|
363
|
-
.filter { !$0.isEmpty }
|
|
364
|
-
.flatMap { query in
|
|
365
|
-
self.search(query: query)
|
|
366
|
-
.catch { _ in Just([]) }
|
|
367
|
-
}
|
|
368
|
-
.assign(to: &$results)
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private func search(query: String) -> AnyPublisher<[Result], Error> {
|
|
372
|
-
URLSession.shared.dataTaskPublisher(for: searchURL(query))
|
|
373
|
-
.map(\.data)
|
|
374
|
-
.decode(type: [Result].self, decoder: JSONDecoder())
|
|
375
|
-
.eraseToAnyPublisher()
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
### Subject 类型
|
|
381
|
-
```swift
|
|
382
|
-
import Combine
|
|
383
|
-
|
|
384
|
-
class EventBus {
|
|
385
|
-
static let shared = EventBus()
|
|
386
|
-
|
|
387
|
-
let userLoggedIn = PassthroughSubject<User, Never>()
|
|
388
|
-
let dataUpdated = CurrentValueSubject<[Item], Never>([])
|
|
389
|
-
|
|
390
|
-
private init() {}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// 使用
|
|
394
|
-
EventBus.shared.userLoggedIn
|
|
395
|
-
.sink { user in
|
|
396
|
-
print("User logged in: \(user.name)")
|
|
397
|
-
}
|
|
398
|
-
.store(in: &cancellables)
|
|
399
|
-
|
|
400
|
-
EventBus.shared.userLoggedIn.send(currentUser)
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
## Swift Concurrency
|
|
404
|
-
|
|
405
|
-
### async/await
|
|
406
|
-
```swift
|
|
407
|
-
// 异步函数
|
|
408
|
-
func fetchUser(id: String) async throws -> User {
|
|
409
|
-
let url = URL(string: "https://api.example.com/users/\(id)")!
|
|
410
|
-
let (data, _) = try await URLSession.shared.data(from: url)
|
|
411
|
-
return try JSONDecoder().decode(User.self, from: data)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// 并发调用
|
|
415
|
-
func fetchMultipleUsers(ids: [String]) async throws -> [User] {
|
|
416
|
-
try await withThrowingTaskGroup(of: User.self) { group in
|
|
417
|
-
for id in ids {
|
|
418
|
-
group.addTask {
|
|
419
|
-
try await fetchUser(id: id)
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
var users: [User] = []
|
|
424
|
-
for try await user in group {
|
|
425
|
-
users.append(user)
|
|
426
|
-
}
|
|
427
|
-
return users
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// 在视图中使用
|
|
432
|
-
struct UserView: View {
|
|
433
|
-
@State private var user: User?
|
|
434
|
-
|
|
435
|
-
var body: some View {
|
|
436
|
-
Group {
|
|
437
|
-
if let user = user {
|
|
438
|
-
Text(user.name)
|
|
439
|
-
} else {
|
|
440
|
-
ProgressView()
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
.task {
|
|
444
|
-
user = try? await fetchUser(id: "123")
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
### Actor 并发安全
|
|
451
|
-
```swift
|
|
452
|
-
actor DatabaseManager {
|
|
453
|
-
private var cache: [String: Data] = [:]
|
|
454
|
-
|
|
455
|
-
func getData(key: String) async -> Data? {
|
|
456
|
-
if let cached = cache[key] {
|
|
457
|
-
return cached
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
let data = await fetchFromNetwork(key: key)
|
|
461
|
-
cache[key] = data
|
|
462
|
-
return data
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
func clearCache() {
|
|
466
|
-
cache.removeAll()
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// 使用
|
|
471
|
-
let db = DatabaseManager()
|
|
472
|
-
let data = await db.getData(key: "user_123")
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### AsyncSequence
|
|
476
|
-
```swift
|
|
477
|
-
struct AsyncLineReader: AsyncSequence {
|
|
478
|
-
typealias Element = String
|
|
479
|
-
|
|
480
|
-
let url: URL
|
|
481
|
-
|
|
482
|
-
func makeAsyncIterator() -> AsyncIterator {
|
|
483
|
-
AsyncIterator(url: url)
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
struct AsyncIterator: AsyncIteratorProtocol {
|
|
487
|
-
let url: URL
|
|
488
|
-
private var lines: [String]?
|
|
489
|
-
private var index = 0
|
|
490
|
-
|
|
491
|
-
mutating func next() async throws -> String? {
|
|
492
|
-
if lines == nil {
|
|
493
|
-
let content = try String(contentsOf: url)
|
|
494
|
-
lines = content.components(separatedBy: .newlines)
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
guard let lines = lines, index < lines.count else {
|
|
498
|
-
return nil
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
defer { index += 1 }
|
|
502
|
-
return lines[index]
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// 使用
|
|
508
|
-
for try await line in AsyncLineReader(url: fileURL) {
|
|
509
|
-
print(line)
|
|
510
|
-
}
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
## 内存管理 (ARC)
|
|
514
|
-
|
|
515
|
-
### 强引用循环
|
|
516
|
-
```swift
|
|
517
|
-
class Person {
|
|
518
|
-
let name: String
|
|
519
|
-
var apartment: Apartment?
|
|
520
|
-
|
|
521
|
-
init(name: String) {
|
|
522
|
-
self.name = name
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
deinit {
|
|
526
|
-
print("\(name) is being deinitialized")
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
class Apartment {
|
|
531
|
-
let unit: String
|
|
532
|
-
weak var tenant: Person? // weak 避免循环引用
|
|
533
|
-
|
|
534
|
-
init(unit: String) {
|
|
535
|
-
self.unit = unit
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
deinit {
|
|
539
|
-
print("Apartment \(unit) is being deinitialized")
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### 闭包捕获列表
|
|
545
|
-
```swift
|
|
546
|
-
class ViewController: UIViewController {
|
|
547
|
-
var name = "View Controller"
|
|
548
|
-
|
|
549
|
-
func setupHandler() {
|
|
550
|
-
// ❌ 强引用循环
|
|
551
|
-
someAsyncOperation {
|
|
552
|
-
print(self.name)
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// ✅ 使用 weak
|
|
556
|
-
someAsyncOperation { [weak self] in
|
|
557
|
-
guard let self = self else { return }
|
|
558
|
-
print(self.name)
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// ✅ 使用 unowned (确定不会为 nil)
|
|
562
|
-
someAsyncOperation { [unowned self] in
|
|
563
|
-
print(self.name)
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
### 值类型 vs 引用类型
|
|
570
|
-
```swift
|
|
571
|
-
// 值类型 (struct, enum) - 复制语义
|
|
572
|
-
struct Point {
|
|
573
|
-
var x: Int
|
|
574
|
-
var y: Int
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
var p1 = Point(x: 0, y: 0)
|
|
578
|
-
var p2 = p1
|
|
579
|
-
p2.x = 10
|
|
580
|
-
print(p1.x) // 0 (未改变)
|
|
581
|
-
|
|
582
|
-
// 引用类型 (class) - 共享语义
|
|
583
|
-
class Rectangle {
|
|
584
|
-
var width: Int
|
|
585
|
-
var height: Int
|
|
586
|
-
|
|
587
|
-
init(width: Int, height: Int) {
|
|
588
|
-
self.width = width
|
|
589
|
-
self.height = height
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
let r1 = Rectangle(width: 10, height: 20)
|
|
594
|
-
let r2 = r1
|
|
595
|
-
r2.width = 30
|
|
596
|
-
print(r1.width) // 30 (已改变)
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
## 网络请求
|
|
600
|
-
|
|
601
|
-
### URLSession
|
|
602
|
-
```swift
|
|
603
|
-
class APIClient {
|
|
604
|
-
static let shared = APIClient()
|
|
605
|
-
|
|
606
|
-
func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
|
|
607
|
-
let (data, response) = try await URLSession.shared.data(from: url)
|
|
608
|
-
|
|
609
|
-
guard let httpResponse = response as? HTTPURLResponse,
|
|
610
|
-
(200...299).contains(httpResponse.statusCode) else {
|
|
611
|
-
throw APIError.invalidResponse
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
return try JSONDecoder().decode(T.self, from: data)
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
func post<T: Encodable, R: Decodable>(
|
|
618
|
-
_ endpoint: String,
|
|
619
|
-
body: T
|
|
620
|
-
) async throws -> R {
|
|
621
|
-
var request = URLRequest(url: URL(string: endpoint)!)
|
|
622
|
-
request.httpMethod = "POST"
|
|
623
|
-
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
624
|
-
request.httpBody = try JSONEncoder().encode(body)
|
|
625
|
-
|
|
626
|
-
let (data, _) = try await URLSession.shared.data(for: request)
|
|
627
|
-
return try JSONDecoder().decode(R.self, from: data)
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
## CoreData 持久化
|
|
633
|
-
|
|
634
|
-
### 数据模型
|
|
635
|
-
```swift
|
|
636
|
-
import CoreData
|
|
637
|
-
|
|
638
|
-
@objc(Task)
|
|
639
|
-
class Task: NSManagedObject {
|
|
640
|
-
@NSManaged var id: UUID
|
|
641
|
-
@NSManaged var title: String
|
|
642
|
-
@NSManaged var isCompleted: Bool
|
|
643
|
-
@NSManaged var createdAt: Date
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
class PersistenceController {
|
|
647
|
-
static let shared = PersistenceController()
|
|
648
|
-
|
|
649
|
-
let container: NSPersistentContainer
|
|
650
|
-
|
|
651
|
-
init() {
|
|
652
|
-
container = NSPersistentContainer(name: "Model")
|
|
653
|
-
container.loadPersistentStores { _, error in
|
|
654
|
-
if let error = error {
|
|
655
|
-
fatalError("Core Data failed: \(error)")
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
func save() {
|
|
661
|
-
let context = container.viewContext
|
|
662
|
-
if context.hasChanges {
|
|
663
|
-
try? context.save()
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// 使用
|
|
669
|
-
let context = PersistenceController.shared.container.viewContext
|
|
670
|
-
let task = Task(context: context)
|
|
671
|
-
task.id = UUID()
|
|
672
|
-
task.title = "New Task"
|
|
673
|
-
task.isCompleted = false
|
|
674
|
-
task.createdAt = Date()
|
|
675
|
-
PersistenceController.shared.save()
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
## 测试
|
|
679
|
-
|
|
680
|
-
### XCTest 单元测试
|
|
681
|
-
```swift
|
|
682
|
-
import XCTest
|
|
683
|
-
@testable import MyApp
|
|
684
|
-
|
|
685
|
-
class CalculatorTests: XCTestCase {
|
|
686
|
-
var calculator: Calculator!
|
|
687
|
-
|
|
688
|
-
override func setUp() {
|
|
689
|
-
super.setUp()
|
|
690
|
-
calculator = Calculator()
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
override func tearDown() {
|
|
694
|
-
calculator = nil
|
|
695
|
-
super.tearDown()
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
func testAddition() {
|
|
699
|
-
let result = calculator.add(2, 3)
|
|
700
|
-
XCTAssertEqual(result, 5)
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
func testAsyncOperation() async throws {
|
|
704
|
-
let result = try await calculator.fetchResult()
|
|
705
|
-
XCTAssertGreaterThan(result, 0)
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
### UI 测试
|
|
711
|
-
```swift
|
|
712
|
-
class UITests: XCTestCase {
|
|
713
|
-
func testLoginFlow() {
|
|
714
|
-
let app = XCUIApplication()
|
|
715
|
-
app.launch()
|
|
716
|
-
|
|
717
|
-
let emailField = app.textFields["Email"]
|
|
718
|
-
emailField.tap()
|
|
719
|
-
emailField.typeText("test@example.com")
|
|
720
|
-
|
|
721
|
-
let passwordField = app.secureTextFields["Password"]
|
|
722
|
-
passwordField.tap()
|
|
723
|
-
passwordField.typeText("password123")
|
|
724
|
-
|
|
725
|
-
app.buttons["Login"].tap()
|
|
726
|
-
|
|
727
|
-
XCTAssertTrue(app.staticTexts["Welcome"].exists)
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
## 框架对比
|
|
733
|
-
|
|
734
|
-
| 特性 | SwiftUI | UIKit |
|
|
735
|
-
|------|---------|-------|
|
|
736
|
-
| 声明式 | ✅ | ❌ |
|
|
737
|
-
| 学习曲线 | 平缓 | 陡峭 |
|
|
738
|
-
| 性能 | 优秀 | 优秀 |
|
|
739
|
-
| 兼容性 | iOS 13+ | iOS 2+ |
|
|
740
|
-
| 自定义能力 | 中等 | 强大 |
|
|
741
|
-
| 预览功能 | ✅ | ❌ |
|
|
742
|
-
|
|
743
|
-
## 工具清单
|
|
744
|
-
|
|
745
|
-
| 工具 | 用途 |
|
|
746
|
-
|------|------|
|
|
747
|
-
| Xcode | 官方 IDE |
|
|
748
|
-
| Swift Package Manager | 依赖管理 |
|
|
749
|
-
| CocoaPods | 依赖管理 |
|
|
750
|
-
| Carthage | 依赖管理 |
|
|
751
|
-
| Instruments | 性能分析 |
|
|
752
|
-
| SwiftLint | 代码规范 |
|
|
753
|
-
| Fastlane | 自动化部署 |
|
|
754
|
-
|
|
755
|
-
---
|