code-abyss 1.6.15 → 1.7.0

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.
Files changed (82) hide show
  1. package/bin/install.js +25 -4
  2. package/package.json +2 -2
  3. package/skills/SKILL.md +24 -16
  4. package/skills/domains/ai/SKILL.md +2 -2
  5. package/skills/domains/ai/prompt-and-eval.md +279 -0
  6. package/skills/domains/architecture/SKILL.md +2 -3
  7. package/skills/domains/architecture/security-arch.md +87 -0
  8. package/skills/domains/data-engineering/SKILL.md +188 -26
  9. package/skills/domains/development/SKILL.md +1 -4
  10. package/skills/domains/devops/SKILL.md +3 -5
  11. package/skills/domains/devops/performance.md +63 -0
  12. package/skills/domains/devops/testing.md +97 -0
  13. package/skills/domains/frontend-design/SKILL.md +12 -3
  14. package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
  15. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  16. package/skills/domains/frontend-design/engineering.md +287 -0
  17. package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
  18. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  19. package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
  20. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  21. package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
  22. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  23. package/skills/domains/infrastructure/SKILL.md +174 -34
  24. package/skills/domains/mobile/SKILL.md +211 -21
  25. package/skills/domains/orchestration/SKILL.md +1 -0
  26. package/skills/domains/security/SKILL.md +4 -6
  27. package/skills/domains/security/blue-team.md +57 -0
  28. package/skills/domains/security/red-team.md +54 -0
  29. package/skills/domains/security/threat-intel.md +50 -0
  30. package/skills/orchestration/multi-agent/SKILL.md +195 -46
  31. package/skills/run_skill.js +134 -0
  32. package/skills/tools/gen-docs/SKILL.md +6 -4
  33. package/skills/tools/gen-docs/scripts/doc_generator.js +349 -0
  34. package/skills/tools/verify-change/SKILL.md +8 -6
  35. package/skills/tools/verify-change/scripts/change_analyzer.js +270 -0
  36. package/skills/tools/verify-module/SKILL.md +6 -4
  37. package/skills/tools/verify-module/scripts/module_scanner.js +145 -0
  38. package/skills/tools/verify-quality/SKILL.md +5 -3
  39. package/skills/tools/verify-quality/scripts/quality_checker.js +276 -0
  40. package/skills/tools/verify-security/SKILL.md +7 -5
  41. package/skills/tools/verify-security/scripts/security_scanner.js +133 -0
  42. package/skills/domains/COVERAGE_PLAN.md +0 -232
  43. package/skills/domains/ai/model-evaluation.md +0 -790
  44. package/skills/domains/ai/prompt-engineering.md +0 -703
  45. package/skills/domains/architecture/compliance.md +0 -299
  46. package/skills/domains/architecture/data-security.md +0 -184
  47. package/skills/domains/data-engineering/data-pipeline.md +0 -762
  48. package/skills/domains/data-engineering/data-quality.md +0 -894
  49. package/skills/domains/data-engineering/stream-processing.md +0 -791
  50. package/skills/domains/development/dart.md +0 -963
  51. package/skills/domains/development/kotlin.md +0 -834
  52. package/skills/domains/development/php.md +0 -659
  53. package/skills/domains/development/swift.md +0 -755
  54. package/skills/domains/devops/e2e-testing.md +0 -914
  55. package/skills/domains/devops/performance-testing.md +0 -734
  56. package/skills/domains/devops/testing-strategy.md +0 -667
  57. package/skills/domains/frontend-design/build-tools.md +0 -743
  58. package/skills/domains/frontend-design/performance.md +0 -734
  59. package/skills/domains/frontend-design/testing.md +0 -699
  60. package/skills/domains/infrastructure/gitops.md +0 -735
  61. package/skills/domains/infrastructure/iac.md +0 -855
  62. package/skills/domains/infrastructure/kubernetes.md +0 -1018
  63. package/skills/domains/mobile/android-dev.md +0 -979
  64. package/skills/domains/mobile/cross-platform.md +0 -795
  65. package/skills/domains/mobile/ios-dev.md +0 -931
  66. package/skills/domains/security/secrets-management.md +0 -834
  67. package/skills/domains/security/supply-chain.md +0 -931
  68. package/skills/domains/security/threat-modeling.md +0 -828
  69. package/skills/run_skill.py +0 -88
  70. package/skills/tests/README.md +0 -225
  71. package/skills/tests/SUMMARY.md +0 -362
  72. package/skills/tests/__init__.py +0 -3
  73. package/skills/tests/test_change_analyzer.py +0 -558
  74. package/skills/tests/test_doc_generator.py +0 -538
  75. package/skills/tests/test_module_scanner.py +0 -376
  76. package/skills/tests/test_quality_checker.py +0 -516
  77. package/skills/tests/test_security_scanner.py +0 -426
  78. package/skills/tools/gen-docs/scripts/doc_generator.py +0 -491
  79. package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
  80. package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
  81. package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
  82. package/skills/tools/verify-security/scripts/security_scanner.py +0 -368
@@ -1,931 +0,0 @@
1
- ---
2
- name: ios-dev
3
- description: iOS 开发。SwiftUI、UIKit、Combine、MVVM、VIPER、Auto Layout、iOS架构。当用户提到 iOS 开发、SwiftUI、UIKit、Combine 时使用。
4
- ---
5
-
6
- # 🍎 iOS 开发 · iOS Development
7
-
8
- ## SwiftUI 基础
9
-
10
- ### View 组件
11
- ```swift
12
- import SwiftUI
13
-
14
- struct ContentView: View {
15
- var body: some View {
16
- VStack(spacing: 20) {
17
- Text("Hello, SwiftUI")
18
- .font(.largeTitle)
19
- .foregroundColor(.blue)
20
-
21
- Image(systemName: "star.fill")
22
- .resizable()
23
- .frame(width: 50, height: 50)
24
-
25
- Button("Tap Me") {
26
- print("Button tapped")
27
- }
28
- .buttonStyle(.borderedProminent)
29
- }
30
- .padding()
31
- }
32
- }
33
- ```
34
-
35
- ### State 管理
36
- ```swift
37
- struct CounterView: View {
38
- @State private var count = 0
39
- @State private var isOn = false
40
-
41
- var body: some View {
42
- VStack {
43
- Text("Count: \(count)")
44
- .font(.title)
45
-
46
- HStack {
47
- Button("-") { count -= 1 }
48
- Button("+") { count += 1 }
49
- }
50
-
51
- Toggle("Enable", isOn: $isOn)
52
- }
53
- }
54
- }
55
- ```
56
-
57
- ### Binding 双向绑定
58
- ```swift
59
- struct ParentView: View {
60
- @State private var text = ""
61
-
62
- var body: some View {
63
- VStack {
64
- Text("Input: \(text)")
65
- ChildView(text: $text)
66
- }
67
- }
68
- }
69
-
70
- struct ChildView: View {
71
- @Binding var text: String
72
-
73
- var body: some View {
74
- TextField("Enter text", text: $text)
75
- .textFieldStyle(.roundedBorder)
76
- .padding()
77
- }
78
- }
79
- ```
80
-
81
- ## SwiftUI 高级
82
-
83
- ### ObservableObject
84
- ```swift
85
- class UserViewModel: ObservableObject {
86
- @Published var username = ""
87
- @Published var isLoading = false
88
- @Published var users: [User] = []
89
-
90
- func fetchUsers() async {
91
- isLoading = true
92
- defer { isLoading = false }
93
-
94
- do {
95
- let url = URL(string: "https://api.example.com/users")!
96
- let (data, _) = try await URLSession.shared.data(from: url)
97
- users = try JSONDecoder().decode([User].self, from: data)
98
- } catch {
99
- print("Error: \(error)")
100
- }
101
- }
102
- }
103
-
104
- struct UserListView: View {
105
- @StateObject private var viewModel = UserViewModel()
106
-
107
- var body: some View {
108
- List(viewModel.users) { user in
109
- Text(user.name)
110
- }
111
- .task {
112
- await viewModel.fetchUsers()
113
- }
114
- .overlay {
115
- if viewModel.isLoading {
116
- ProgressView()
117
- }
118
- }
119
- }
120
- }
121
- ```
122
-
123
- ### Environment
124
- ```swift
125
- // 自定义 Environment Key
126
- struct ThemeKey: EnvironmentKey {
127
- static let defaultValue = Theme.light
128
- }
129
-
130
- extension EnvironmentValues {
131
- var theme: Theme {
132
- get { self[ThemeKey.self] }
133
- set { self[ThemeKey.self] = newValue }
134
- }
135
- }
136
-
137
- // 使用
138
- struct RootView: View {
139
- @State private var theme = Theme.dark
140
-
141
- var body: some View {
142
- ContentView()
143
- .environment(\.theme, theme)
144
- }
145
- }
146
-
147
- struct ContentView: View {
148
- @Environment(\.theme) var theme
149
-
150
- var body: some View {
151
- Text("Hello")
152
- .foregroundColor(theme.textColor)
153
- }
154
- }
155
- ```
156
-
157
- ### Custom ViewModifier
158
- ```swift
159
- struct CardModifier: ViewModifier {
160
- func body(content: Content) -> some View {
161
- content
162
- .padding()
163
- .background(Color.white)
164
- .cornerRadius(10)
165
- .shadow(radius: 5)
166
- }
167
- }
168
-
169
- extension View {
170
- func cardStyle() -> some View {
171
- modifier(CardModifier())
172
- }
173
- }
174
-
175
- // 使用
176
- Text("Card Content")
177
- .cardStyle()
178
- ```
179
-
180
- ## UIKit 集成
181
-
182
- ### UIViewController 包装
183
- ```swift
184
- import UIKit
185
- import SwiftUI
186
-
187
- struct CameraView: UIViewControllerRepresentable {
188
- @Binding var image: UIImage?
189
- @Environment(\.dismiss) var dismiss
190
-
191
- func makeUIViewController(context: Context) -> UIImagePickerController {
192
- let picker = UIImagePickerController()
193
- picker.sourceType = .camera
194
- picker.delegate = context.coordinator
195
- return picker
196
- }
197
-
198
- func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
199
-
200
- func makeCoordinator() -> Coordinator {
201
- Coordinator(self)
202
- }
203
-
204
- class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
205
- let parent: CameraView
206
-
207
- init(_ parent: CameraView) {
208
- self.parent = parent
209
- }
210
-
211
- func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
212
- if let image = info[.originalImage] as? UIImage {
213
- parent.image = image
214
- }
215
- parent.dismiss()
216
- }
217
- }
218
- }
219
- ```
220
-
221
- ### UIView 包装
222
- ```swift
223
- struct MapView: UIViewRepresentable {
224
- @Binding var region: MKCoordinateRegion
225
-
226
- func makeUIView(context: Context) -> MKMapView {
227
- let mapView = MKMapView()
228
- mapView.delegate = context.coordinator
229
- return mapView
230
- }
231
-
232
- func updateUIView(_ uiView: MKMapView, context: Context) {
233
- uiView.setRegion(region, animated: true)
234
- }
235
-
236
- func makeCoordinator() -> Coordinator {
237
- Coordinator(self)
238
- }
239
-
240
- class Coordinator: NSObject, MKMapViewDelegate {
241
- var parent: MapView
242
-
243
- init(_ parent: MapView) {
244
- self.parent = parent
245
- }
246
- }
247
- }
248
- ```
249
-
250
- ### Auto Layout
251
- ```swift
252
- class CustomViewController: UIViewController {
253
- let titleLabel = UILabel()
254
- let button = UIButton()
255
-
256
- override func viewDidLoad() {
257
- super.viewDidLoad()
258
- setupUI()
259
- }
260
-
261
- func setupUI() {
262
- titleLabel.translatesAutoresizingMaskIntoConstraints = false
263
- button.translatesAutoresizingMaskIntoConstraints = false
264
-
265
- view.addSubview(titleLabel)
266
- view.addSubview(button)
267
-
268
- NSLayoutConstraint.activate([
269
- titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
270
- titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
271
-
272
- button.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
273
- button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
274
- button.widthAnchor.constraint(equalToConstant: 200),
275
- button.heightAnchor.constraint(equalToConstant: 44)
276
- ])
277
- }
278
- }
279
- ```
280
-
281
- ## Combine 响应式
282
-
283
- ### Publisher 基础
284
- ```swift
285
- import Combine
286
-
287
- class DataService {
288
- func fetchData() -> AnyPublisher<[Item], Error> {
289
- URLSession.shared
290
- .dataTaskPublisher(for: URL(string: "https://api.example.com/items")!)
291
- .map(\.data)
292
- .decode(type: [Item].self, decoder: JSONDecoder())
293
- .eraseToAnyPublisher()
294
- }
295
- }
296
-
297
- class ViewModel: ObservableObject {
298
- @Published var items: [Item] = []
299
- @Published var error: Error?
300
-
301
- private var cancellables = Set<AnyCancellable>()
302
- private let service = DataService()
303
-
304
- func load() {
305
- service.fetchData()
306
- .receive(on: DispatchQueue.main)
307
- .sink(
308
- receiveCompletion: { [weak self] completion in
309
- if case .failure(let error) = completion {
310
- self?.error = error
311
- }
312
- },
313
- receiveValue: { [weak self] items in
314
- self?.items = items
315
- }
316
- )
317
- .store(in: &cancellables)
318
- }
319
- }
320
- ```
321
-
322
- ### Operators
323
- ```swift
324
- // Map & Filter
325
- let numbers = [1, 2, 3, 4, 5].publisher
326
- numbers
327
- .map { $0 * 2 }
328
- .filter { $0 > 5 }
329
- .sink { print($0) }
330
- .store(in: &cancellables)
331
-
332
- // Debounce (搜索防抖)
333
- @Published var searchText = ""
334
-
335
- $searchText
336
- .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
337
- .removeDuplicates()
338
- .sink { text in
339
- self.performSearch(text)
340
- }
341
- .store(in: &cancellables)
342
-
343
- // CombineLatest
344
- Publishers.CombineLatest($username, $password)
345
- .map { username, password in
346
- !username.isEmpty && password.count >= 6
347
- }
348
- .assign(to: &$isValid)
349
- ```
350
-
351
- ### Subject
352
- ```swift
353
- class EventBus {
354
- static let shared = EventBus()
355
-
356
- let userLoggedIn = PassthroughSubject<User, Never>()
357
- let dataUpdated = CurrentValueSubject<[Item], Never>([])
358
-
359
- private init() {}
360
- }
361
-
362
- // 发送
363
- EventBus.shared.userLoggedIn.send(user)
364
-
365
- // 订阅
366
- EventBus.shared.userLoggedIn
367
- .sink { user in
368
- print("User logged in: \(user.name)")
369
- }
370
- .store(in: &cancellables)
371
- ```
372
-
373
- ## MVVM 架构
374
-
375
- ### Model
376
- ```swift
377
- struct User: Codable, Identifiable {
378
- let id: Int
379
- let name: String
380
- let email: String
381
- }
382
-
383
- struct LoginRequest: Codable {
384
- let username: String
385
- let password: String
386
- }
387
-
388
- struct LoginResponse: Codable {
389
- let token: String
390
- let user: User
391
- }
392
- ```
393
-
394
- ### Repository
395
- ```swift
396
- protocol UserRepository {
397
- func login(username: String, password: String) async throws -> LoginResponse
398
- func fetchProfile() async throws -> User
399
- }
400
-
401
- class UserRepositoryImpl: UserRepository {
402
- private let apiClient: APIClient
403
-
404
- init(apiClient: APIClient = .shared) {
405
- self.apiClient = apiClient
406
- }
407
-
408
- func login(username: String, password: String) async throws -> LoginResponse {
409
- let request = LoginRequest(username: username, password: password)
410
- return try await apiClient.post("/auth/login", body: request)
411
- }
412
-
413
- func fetchProfile() async throws -> User {
414
- try await apiClient.get("/user/profile")
415
- }
416
- }
417
- ```
418
-
419
- ### ViewModel
420
- ```swift
421
- @MainActor
422
- class LoginViewModel: ObservableObject {
423
- @Published var username = ""
424
- @Published var password = ""
425
- @Published var isLoading = false
426
- @Published var error: String?
427
- @Published var isLoggedIn = false
428
-
429
- private let repository: UserRepository
430
-
431
- init(repository: UserRepository = UserRepositoryImpl()) {
432
- self.repository = repository
433
- }
434
-
435
- var isValid: Bool {
436
- !username.isEmpty && password.count >= 6
437
- }
438
-
439
- func login() async {
440
- guard isValid else { return }
441
-
442
- isLoading = true
443
- error = nil
444
-
445
- do {
446
- let response = try await repository.login(username: username, password: password)
447
- TokenManager.shared.save(response.token)
448
- isLoggedIn = true
449
- } catch {
450
- self.error = error.localizedDescription
451
- }
452
-
453
- isLoading = false
454
- }
455
- }
456
- ```
457
-
458
- ### View
459
- ```swift
460
- struct LoginView: View {
461
- @StateObject private var viewModel = LoginViewModel()
462
-
463
- var body: some View {
464
- VStack(spacing: 20) {
465
- TextField("Username", text: $viewModel.username)
466
- .textFieldStyle(.roundedBorder)
467
-
468
- SecureField("Password", text: $viewModel.password)
469
- .textFieldStyle(.roundedBorder)
470
-
471
- if let error = viewModel.error {
472
- Text(error)
473
- .foregroundColor(.red)
474
- .font(.caption)
475
- }
476
-
477
- Button("Login") {
478
- Task {
479
- await viewModel.login()
480
- }
481
- }
482
- .disabled(!viewModel.isValid || viewModel.isLoading)
483
- .buttonStyle(.borderedProminent)
484
-
485
- if viewModel.isLoading {
486
- ProgressView()
487
- }
488
- }
489
- .padding()
490
- .fullScreenCover(isPresented: $viewModel.isLoggedIn) {
491
- HomeView()
492
- }
493
- }
494
- }
495
- ```
496
-
497
- ## VIPER 架构
498
-
499
- ```
500
- View ←→ Presenter ←→ Interactor
501
- ↓ ↓
502
- Router Entity
503
- ```
504
-
505
- ### Entity
506
- ```swift
507
- struct Article: Codable {
508
- let id: Int
509
- let title: String
510
- let content: String
511
- }
512
- ```
513
-
514
- ### Interactor
515
- ```swift
516
- protocol ArticleInteractorProtocol {
517
- func fetchArticles() async throws -> [Article]
518
- }
519
-
520
- class ArticleInteractor: ArticleInteractorProtocol {
521
- private let repository: ArticleRepository
522
-
523
- init(repository: ArticleRepository) {
524
- self.repository = repository
525
- }
526
-
527
- func fetchArticles() async throws -> [Article] {
528
- try await repository.fetchArticles()
529
- }
530
- }
531
- ```
532
-
533
- ### Presenter
534
- ```swift
535
- @MainActor
536
- protocol ArticlePresenterProtocol: ObservableObject {
537
- var articles: [Article] { get }
538
- var isLoading: Bool { get }
539
- func loadArticles()
540
- func didSelectArticle(_ article: Article)
541
- }
542
-
543
- @MainActor
544
- class ArticlePresenter: ArticlePresenterProtocol {
545
- @Published var articles: [Article] = []
546
- @Published var isLoading = false
547
-
548
- private let interactor: ArticleInteractorProtocol
549
- private let router: ArticleRouterProtocol
550
-
551
- init(interactor: ArticleInteractorProtocol, router: ArticleRouterProtocol) {
552
- self.interactor = interactor
553
- self.router = router
554
- }
555
-
556
- func loadArticles() {
557
- Task {
558
- isLoading = true
559
- do {
560
- articles = try await interactor.fetchArticles()
561
- } catch {
562
- print("Error: \(error)")
563
- }
564
- isLoading = false
565
- }
566
- }
567
-
568
- func didSelectArticle(_ article: Article) {
569
- router.navigateToDetail(article)
570
- }
571
- }
572
- ```
573
-
574
- ### View
575
- ```swift
576
- struct ArticleListView<Presenter: ArticlePresenterProtocol>: View {
577
- @ObservedObject var presenter: Presenter
578
-
579
- var body: some View {
580
- List(presenter.articles, id: \.id) { article in
581
- Button(article.title) {
582
- presenter.didSelectArticle(article)
583
- }
584
- }
585
- .task {
586
- presenter.loadArticles()
587
- }
588
- }
589
- }
590
- ```
591
-
592
- ### Router
593
- ```swift
594
- protocol ArticleRouterProtocol {
595
- func navigateToDetail(_ article: Article)
596
- }
597
-
598
- class ArticleRouter: ArticleRouterProtocol {
599
- weak var viewController: UIViewController?
600
-
601
- func navigateToDetail(_ article: Article) {
602
- let detailVC = ArticleDetailBuilder.build(article: article)
603
- viewController?.navigationController?.pushViewController(detailVC, animated: true)
604
- }
605
- }
606
- ```
607
-
608
- ## 网络层
609
-
610
- ### APIClient
611
- ```swift
612
- class APIClient {
613
- static let shared = APIClient()
614
- private let baseURL = "https://api.example.com"
615
-
616
- func get<T: Decodable>(_ path: String) async throws -> T {
617
- try await request(path, method: "GET")
618
- }
619
-
620
- func post<T: Decodable, B: Encodable>(_ path: String, body: B) async throws -> T {
621
- try await request(path, method: "POST", body: body)
622
- }
623
-
624
- private func request<T: Decodable, B: Encodable>(_ path: String, method: String, body: B? = nil as String?) async throws -> T {
625
- guard let url = URL(string: baseURL + path) else {
626
- throw APIError.invalidURL
627
- }
628
-
629
- var request = URLRequest(url: url)
630
- request.httpMethod = method
631
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
632
-
633
- if let token = TokenManager.shared.token {
634
- request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
635
- }
636
-
637
- if let body = body {
638
- request.httpBody = try JSONEncoder().encode(body)
639
- }
640
-
641
- let (data, response) = try await URLSession.shared.data(for: request)
642
-
643
- guard let httpResponse = response as? HTTPURLResponse else {
644
- throw APIError.invalidResponse
645
- }
646
-
647
- guard (200...299).contains(httpResponse.statusCode) else {
648
- throw APIError.httpError(httpResponse.statusCode)
649
- }
650
-
651
- return try JSONDecoder().decode(T.self, from: data)
652
- }
653
- }
654
-
655
- enum APIError: Error {
656
- case invalidURL
657
- case invalidResponse
658
- case httpError(Int)
659
- }
660
- ```
661
-
662
- ## 数据持久化
663
-
664
- ### UserDefaults
665
- ```swift
666
- class SettingsManager {
667
- static let shared = SettingsManager()
668
-
669
- @UserDefault(key: "isDarkMode", defaultValue: false)
670
- var isDarkMode: Bool
671
-
672
- @UserDefault(key: "language", defaultValue: "en")
673
- var language: String
674
- }
675
-
676
- @propertyWrapper
677
- struct UserDefault<T> {
678
- let key: String
679
- let defaultValue: T
680
-
681
- var wrappedValue: T {
682
- get {
683
- UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
684
- }
685
- set {
686
- UserDefaults.standard.set(newValue, forKey: key)
687
- }
688
- }
689
- }
690
- ```
691
-
692
- ### Keychain
693
- ```swift
694
- class KeychainManager {
695
- static let shared = KeychainManager()
696
-
697
- func save(_ value: String, forKey key: String) {
698
- let data = value.data(using: .utf8)!
699
- let query: [String: Any] = [
700
- kSecClass as String: kSecClassGenericPassword,
701
- kSecAttrAccount as String: key,
702
- kSecValueData as String: data
703
- ]
704
-
705
- SecItemDelete(query as CFDictionary)
706
- SecItemAdd(query as CFDictionary, nil)
707
- }
708
-
709
- func get(forKey key: String) -> String? {
710
- let query: [String: Any] = [
711
- kSecClass as String: kSecClassGenericPassword,
712
- kSecAttrAccount as String: key,
713
- kSecReturnData as String: true
714
- ]
715
-
716
- var result: AnyObject?
717
- SecItemCopyMatching(query as CFDictionary, &result)
718
-
719
- guard let data = result as? Data else { return nil }
720
- return String(data: data, encoding: .utf8)
721
- }
722
- }
723
- ```
724
-
725
- ### Core Data
726
- ```swift
727
- class CoreDataManager {
728
- static let shared = CoreDataManager()
729
-
730
- lazy var persistentContainer: NSPersistentContainer = {
731
- let container = NSPersistentContainer(name: "Model")
732
- container.loadPersistentStores { _, error in
733
- if let error = error {
734
- fatalError("Core Data error: \(error)")
735
- }
736
- }
737
- return container
738
- }()
739
-
740
- var context: NSManagedObjectContext {
741
- persistentContainer.viewContext
742
- }
743
-
744
- func save() {
745
- if context.hasChanges {
746
- try? context.save()
747
- }
748
- }
749
- }
750
-
751
- // 使用
752
- let user = User(context: CoreDataManager.shared.context)
753
- user.name = "John"
754
- CoreDataManager.shared.save()
755
- ```
756
-
757
- ## 性能优化
758
-
759
- ### LazyVStack
760
- ```swift
761
- // 大列表优化
762
- ScrollView {
763
- LazyVStack {
764
- ForEach(items) { item in
765
- ItemRow(item: item)
766
- }
767
- }
768
- }
769
- ```
770
-
771
- ### Task 优先级
772
- ```swift
773
- Task(priority: .high) {
774
- await loadCriticalData()
775
- }
776
-
777
- Task(priority: .background) {
778
- await syncData()
779
- }
780
- ```
781
-
782
- ### Image 缓存
783
- ```swift
784
- class ImageCache {
785
- static let shared = ImageCache()
786
- private var cache = NSCache<NSString, UIImage>()
787
-
788
- func get(forKey key: String) -> UIImage? {
789
- cache.object(forKey: key as NSString)
790
- }
791
-
792
- func set(_ image: UIImage, forKey key: String) {
793
- cache.setObject(image, forKey: key as NSString)
794
- }
795
- }
796
-
797
- struct CachedAsyncImage: View {
798
- let url: URL
799
- @State private var image: UIImage?
800
-
801
- var body: some View {
802
- Group {
803
- if let image = image {
804
- Image(uiImage: image)
805
- .resizable()
806
- } else {
807
- ProgressView()
808
- }
809
- }
810
- .task {
811
- if let cached = ImageCache.shared.get(forKey: url.absoluteString) {
812
- image = cached
813
- } else {
814
- let (data, _) = try? await URLSession.shared.data(from: url)
815
- if let data = data, let downloaded = UIImage(data: data) {
816
- ImageCache.shared.set(downloaded, forKey: url.absoluteString)
817
- image = downloaded
818
- }
819
- }
820
- }
821
- }
822
- }
823
- ```
824
-
825
- ## 测试
826
-
827
- ### Unit Test
828
- ```swift
829
- import XCTest
830
- @testable import MyApp
831
-
832
- class LoginViewModelTests: XCTestCase {
833
- var viewModel: LoginViewModel!
834
- var mockRepository: MockUserRepository!
835
-
836
- override func setUp() {
837
- mockRepository = MockUserRepository()
838
- viewModel = LoginViewModel(repository: mockRepository)
839
- }
840
-
841
- func testLoginSuccess() async {
842
- mockRepository.loginResult = .success(LoginResponse(token: "token", user: User(id: 1, name: "Test", email: "test@example.com")))
843
-
844
- viewModel.username = "test"
845
- viewModel.password = "password"
846
-
847
- await viewModel.login()
848
-
849
- XCTAssertTrue(viewModel.isLoggedIn)
850
- XCTAssertNil(viewModel.error)
851
- }
852
-
853
- func testLoginFailure() async {
854
- mockRepository.loginResult = .failure(APIError.httpError(401))
855
-
856
- viewModel.username = "test"
857
- viewModel.password = "wrong"
858
-
859
- await viewModel.login()
860
-
861
- XCTAssertFalse(viewModel.isLoggedIn)
862
- XCTAssertNotNil(viewModel.error)
863
- }
864
- }
865
-
866
- class MockUserRepository: UserRepository {
867
- var loginResult: Result<LoginResponse, Error>!
868
-
869
- func login(username: String, password: String) async throws -> LoginResponse {
870
- try loginResult.get()
871
- }
872
-
873
- func fetchProfile() async throws -> User {
874
- User(id: 1, name: "Test", email: "test@example.com")
875
- }
876
- }
877
- ```
878
-
879
- ### UI Test
880
- ```swift
881
- class LoginUITests: XCTestCase {
882
- var app: XCUIApplication!
883
-
884
- override func setUp() {
885
- app = XCUIApplication()
886
- app.launch()
887
- }
888
-
889
- func testLoginFlow() {
890
- let usernameField = app.textFields["Username"]
891
- usernameField.tap()
892
- usernameField.typeText("testuser")
893
-
894
- let passwordField = app.secureTextFields["Password"]
895
- passwordField.tap()
896
- passwordField.typeText("password123")
897
-
898
- app.buttons["Login"].tap()
899
-
900
- XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 5))
901
- }
902
- }
903
- ```
904
-
905
- ## 工具清单
906
-
907
- | 工具 | 用途 |
908
- |------|------|
909
- | Xcode | IDE |
910
- | SwiftLint | 代码规范 |
911
- | Fastlane | 自动化部署 |
912
- | CocoaPods | 依赖管理 |
913
- | Swift Package Manager | 官方依赖管理 |
914
- | Instruments | 性能分析 |
915
- | Charles | 网络抓包 |
916
- | Reveal | UI 调试 |
917
-
918
- ## 最佳实践
919
-
920
- - ✅ 使用 SwiftUI 优先,UIKit 按需集成
921
- - ✅ MVVM 架构分离关注点
922
- - ✅ async/await 替代回调地狱
923
- - ✅ Combine 处理响应式流
924
- - ✅ 依赖注入提升可测试性
925
- - ✅ 使用 @MainActor 确保 UI 线程安全
926
- - ✅ LazyVStack 优化大列表
927
- - ✅ 图片缓存减少内存压力
928
- - ✅ Keychain 存储敏感数据
929
- - ✅ 单元测试覆盖核心逻辑
930
-
931
- ---