@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +266 -267
- package/package.json +2 -2
- package/pipeline/scripts/_notices.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +68 -68
- package/pipeline/skills/shared/README.md +70 -70
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md
CHANGED
|
@@ -1,518 +1,475 @@
|
|
|
1
|
-
# Swift
|
|
1
|
+
# Extended Swift patterns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Longer examples behind the main skill. Deep Codable work still belongs to
|
|
4
|
+
`swift-codable`, and format styles beyond a custom style to `swift-formatstyle`.
|
|
4
5
|
|
|
5
6
|
## Contents
|
|
6
7
|
|
|
7
|
-
- [Codable
|
|
8
|
-
- [
|
|
9
|
-
- [
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## Codable: Enums with Associated Values
|
|
8
|
+
- [Codable](#codable)
|
|
9
|
+
- [Result builders](#result-builders)
|
|
10
|
+
- [Property wrappers](#property-wrappers)
|
|
11
|
+
- [Advanced Regex](#advanced-regex)
|
|
12
|
+
- [Custom FormatStyle](#custom-formatstyle)
|
|
13
|
+
- [Collection patterns](#collection-patterns)
|
|
14
|
+
- [guard: complex pattern matching](#guard-complex-pattern-matching)
|
|
15
|
+
- [Typed throws in protocols](#typed-throws-in-protocols)
|
|
16
|
+
- [String interpolation](#string-interpolation)
|
|
17
|
+
- [Never: advanced usage](#never-advanced-usage)
|
|
18
|
+
|
|
19
|
+
## Codable
|
|
20
|
+
|
|
21
|
+
### Enums with associated values
|
|
22
|
+
|
|
23
|
+
Encode a discriminator key next to the payload keys, and throw a data
|
|
24
|
+
corruption error for a discriminator you do not recognise.
|
|
25
25
|
|
|
26
26
|
```swift
|
|
27
|
-
enum
|
|
28
|
-
case
|
|
29
|
-
case
|
|
27
|
+
enum Reward: Codable {
|
|
28
|
+
case points(Int)
|
|
29
|
+
case voucher(code: String, percent: Int)
|
|
30
30
|
|
|
31
|
-
enum CodingKeys: String, CodingKey {
|
|
32
|
-
case type, radius, width, height
|
|
33
|
-
}
|
|
31
|
+
private enum CodingKeys: String, CodingKey { case kind, amount, code, percent }
|
|
34
32
|
|
|
35
33
|
func encode(to encoder: Encoder) throws {
|
|
36
|
-
var
|
|
34
|
+
var c = encoder.container(keyedBy: CodingKeys.self)
|
|
37
35
|
switch self {
|
|
38
|
-
case .
|
|
39
|
-
try
|
|
40
|
-
try
|
|
41
|
-
case .
|
|
42
|
-
try
|
|
43
|
-
try
|
|
44
|
-
try
|
|
36
|
+
case .points(let n):
|
|
37
|
+
try c.encode("points", forKey: .kind)
|
|
38
|
+
try c.encode(n, forKey: .amount)
|
|
39
|
+
case .voucher(let code, let percent):
|
|
40
|
+
try c.encode("voucher", forKey: .kind)
|
|
41
|
+
try c.encode(code, forKey: .code)
|
|
42
|
+
try c.encode(percent, forKey: .percent)
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
init(from decoder: Decoder) throws {
|
|
49
|
-
let
|
|
50
|
-
let
|
|
51
|
-
switch
|
|
52
|
-
case "
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
let height = try container.decode(Double.self, forKey: .height)
|
|
58
|
-
self = .rectangle(width: width, height: height)
|
|
47
|
+
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
48
|
+
let kind = try c.decode(String.self, forKey: .kind)
|
|
49
|
+
switch kind {
|
|
50
|
+
case "points":
|
|
51
|
+
self = .points(try c.decode(Int.self, forKey: .amount))
|
|
52
|
+
case "voucher":
|
|
53
|
+
self = .voucher(code: try c.decode(String.self, forKey: .code),
|
|
54
|
+
percent: try c.decode(Int.self, forKey: .percent))
|
|
59
55
|
default:
|
|
60
|
-
throw DecodingError.dataCorruptedError(
|
|
61
|
-
|
|
62
|
-
debugDescription: "Unknown shape type: \(type)")
|
|
56
|
+
throw DecodingError.dataCorruptedError(forKey: .kind, in: c,
|
|
57
|
+
debugDescription: "Unrecognised reward kind \(kind)")
|
|
63
58
|
}
|
|
64
59
|
}
|
|
65
60
|
}
|
|
66
61
|
```
|
|
67
62
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```swift
|
|
71
|
-
// Configure decoder for specific date formats
|
|
72
|
-
let decoder = JSONDecoder()
|
|
63
|
+
### Date strategies
|
|
73
64
|
|
|
74
|
-
|
|
75
|
-
decoder.dateDecodingStrategy = .iso8601
|
|
65
|
+
Set `dateDecodingStrategy` on the `JSONDecoder`:
|
|
76
66
|
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
- `.iso8601` for standard ISO 8601 strings.
|
|
68
|
+
- `.secondsSince1970` for Unix timestamps.
|
|
69
|
+
- `.formatted(formatter)` with a `DateFormatter` whose locale is
|
|
70
|
+
`en_US_POSIX`, so user settings cannot change parsing.
|
|
71
|
+
- `.custom` when the feed mixes formats:
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
let
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
let
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let fallback = DateFormatter()
|
|
95
|
-
fallback.dateFormat = "yyyy-MM-dd"
|
|
96
|
-
fallback.locale = Locale(identifier: "en_US_POSIX")
|
|
97
|
-
if let date = fallback.date(from: string) { return date }
|
|
98
|
-
|
|
99
|
-
throw DecodingError.dataCorruptedError(
|
|
100
|
-
in: container, debugDescription: "Cannot decode date: \(string)")
|
|
73
|
+
```swift
|
|
74
|
+
import Foundation
|
|
75
|
+
|
|
76
|
+
let reader = JSONDecoder()
|
|
77
|
+
reader.dateDecodingStrategy = .custom { decoder in
|
|
78
|
+
let box = try decoder.singleValueContainer()
|
|
79
|
+
let raw = try box.decode(String.self)
|
|
80
|
+
if let stamp = ISO8601DateFormatter().date(from: raw) { return stamp }
|
|
81
|
+
let dayOnly = DateFormatter()
|
|
82
|
+
dayOnly.locale = Locale(identifier: "en_US_POSIX")
|
|
83
|
+
dayOnly.dateFormat = "yyyy-MM-dd"
|
|
84
|
+
if let stamp = dayOnly.date(from: raw) { return stamp }
|
|
85
|
+
throw DecodingError.dataCorruptedError(in: box,
|
|
86
|
+
debugDescription: "Unreadable date \(raw)")
|
|
101
87
|
}
|
|
102
88
|
```
|
|
103
89
|
|
|
104
|
-
|
|
90
|
+
### Unkeyed containers
|
|
105
91
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
struct Location: Decodable {
|
|
109
|
-
let latitude: Double
|
|
110
|
-
let longitude: Double
|
|
92
|
+
A JSON array whose positions carry meaning, such as `[51.5, -0.12, 35]`, maps
|
|
93
|
+
onto named fields by reading it in order:
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
95
|
+
```swift
|
|
96
|
+
struct Fix: Decodable {
|
|
97
|
+
var lat: Double, lon: Double, altitude: Double
|
|
98
|
+
private enum CodingKeys: String, CodingKey { case point }
|
|
115
99
|
|
|
116
100
|
init(from decoder: Decoder) throws {
|
|
117
|
-
let
|
|
118
|
-
var
|
|
119
|
-
|
|
120
|
-
|
|
101
|
+
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
102
|
+
var point = try c.nestedUnkeyedContainer(forKey: .point)
|
|
103
|
+
lat = try point.decode(Double.self)
|
|
104
|
+
lon = try point.decode(Double.self)
|
|
105
|
+
altitude = try point.decode(Double.self)
|
|
121
106
|
}
|
|
122
107
|
}
|
|
123
108
|
```
|
|
124
109
|
|
|
125
|
-
|
|
110
|
+
### Skipping bad elements
|
|
126
111
|
|
|
127
|
-
|
|
112
|
+
One malformed element should not fail the whole array. Decode each element
|
|
113
|
+
with `try?`; when it fails, decode a throwaway value so the container moves
|
|
114
|
+
past it.
|
|
128
115
|
|
|
129
116
|
```swift
|
|
130
|
-
struct
|
|
131
|
-
|
|
117
|
+
struct Tolerant<Element: Decodable>: Decodable {
|
|
118
|
+
var values: [Element] = []
|
|
119
|
+
private struct Skip: Decodable {}
|
|
132
120
|
|
|
133
121
|
init(from decoder: Decoder) throws {
|
|
134
|
-
var
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
result.append(element)
|
|
122
|
+
var list = try decoder.unkeyedContainer()
|
|
123
|
+
while !list.isAtEnd {
|
|
124
|
+
if let item = try? list.decode(Element.self) {
|
|
125
|
+
values.append(item)
|
|
139
126
|
} else {
|
|
140
|
-
_ = try?
|
|
127
|
+
_ = try? list.decode(Skip.self)
|
|
141
128
|
}
|
|
142
129
|
}
|
|
143
|
-
elements = result
|
|
144
130
|
}
|
|
145
131
|
}
|
|
146
|
-
|
|
147
|
-
private struct AnyCodable: Decodable {}
|
|
148
132
|
```
|
|
149
133
|
|
|
150
|
-
|
|
134
|
+
### Dynamic member lookup over loose JSON
|
|
151
135
|
|
|
152
|
-
|
|
136
|
+
For payloads with no fixed shape, a `@dynamicMemberLookup` wrapper gives
|
|
137
|
+
dot-syntax access over a dictionary, with typed helpers:
|
|
153
138
|
|
|
154
139
|
```swift
|
|
155
140
|
@dynamicMemberLookup
|
|
156
|
-
struct
|
|
157
|
-
private let
|
|
158
|
-
|
|
159
|
-
subscript(dynamicMember key: String) -> JSONValue? {
|
|
160
|
-
guard let value = storage[key] as? [String: Any] else { return nil }
|
|
161
|
-
return JSONValue(storage: value)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
func string(for key: String) -> String? {
|
|
165
|
-
storage[key] as? String
|
|
166
|
-
}
|
|
141
|
+
struct LooseJSON {
|
|
142
|
+
private let fields: [String: Any]
|
|
143
|
+
init(_ fields: [String: Any]) { self.fields = fields }
|
|
167
144
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
init(from decoder: Decoder) throws {
|
|
173
|
-
let container = try decoder.singleValueContainer()
|
|
174
|
-
guard let dict = try container.decode([String: AnyCodableValue].self)
|
|
175
|
-
.mapValues({ $0.value }) as? [String: Any] else {
|
|
176
|
-
throw DecodingError.typeMismatch(
|
|
177
|
-
[String: Any].self,
|
|
178
|
-
.init(codingPath: decoder.codingPath,
|
|
179
|
-
debugDescription: "Expected dictionary"))
|
|
180
|
-
}
|
|
181
|
-
storage = dict
|
|
145
|
+
subscript(dynamicMember key: String) -> LooseJSON? {
|
|
146
|
+
(fields[key] as? [String: Any]).map(LooseJSON.init)
|
|
182
147
|
}
|
|
148
|
+
func text(_ key: String) -> String? { fields[key] as? String }
|
|
149
|
+
func number(_ key: String) -> Double? { fields[key] as? Double }
|
|
183
150
|
}
|
|
151
|
+
|
|
152
|
+
let doc = LooseJSON(["owner": ["name": "Lena", "karma": 12.0]])
|
|
153
|
+
let ownerName = doc.owner?.text("name")
|
|
184
154
|
```
|
|
185
155
|
|
|
186
|
-
## Result
|
|
156
|
+
## Result builders
|
|
187
157
|
|
|
188
|
-
A
|
|
158
|
+
A builder that joins markup fragments, with optional, either and loop support:
|
|
189
159
|
|
|
190
160
|
```swift
|
|
191
161
|
@resultBuilder
|
|
192
|
-
|
|
193
|
-
static func buildBlock(_
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
static func
|
|
198
|
-
component ?? ""
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
static func buildEither(first component: String) -> String {
|
|
202
|
-
component
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
static func buildEither(second component: String) -> String {
|
|
206
|
-
component
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
static func buildArray(_ components: [String]) -> String {
|
|
210
|
-
components.joined(separator: "\n")
|
|
211
|
-
}
|
|
162
|
+
enum MarkupBuilder {
|
|
163
|
+
static func buildBlock(_ parts: String...) -> String { parts.joined(separator: "\n") }
|
|
164
|
+
static func buildOptional(_ part: String?) -> String { part ?? "" }
|
|
165
|
+
static func buildEither(first part: String) -> String { part }
|
|
166
|
+
static func buildEither(second part: String) -> String { part }
|
|
167
|
+
static func buildArray(_ parts: [String]) -> String { parts.joined(separator: "\n") }
|
|
212
168
|
}
|
|
213
169
|
|
|
214
|
-
func
|
|
215
|
-
|
|
216
|
-
}
|
|
170
|
+
func section(@MarkupBuilder _ body: () -> String) -> String { "<section>\n\(body())\n</section>" }
|
|
171
|
+
func heading(_ t: String) -> String { "<h2>\(t)</h2>" }
|
|
172
|
+
func para(_ t: String) -> String { "<p>\(t)</p>" }
|
|
217
173
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
p("Hello, world!")
|
|
224
|
-
if showDetails {
|
|
225
|
-
p("Details here")
|
|
226
|
-
}
|
|
174
|
+
let isBeta = true
|
|
175
|
+
let page = section {
|
|
176
|
+
heading("Release notes")
|
|
177
|
+
para("Faster sync.")
|
|
178
|
+
if isBeta { para("Beta build.") }
|
|
227
179
|
}
|
|
228
180
|
```
|
|
229
181
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
182
|
+
`buildFinalResult` converts the accumulated component into a different output
|
|
183
|
+
type at the very end. A builder can collect `AttributedString` pieces and hand
|
|
184
|
+
back a SwiftUI `Text`:
|
|
233
185
|
|
|
234
186
|
```swift
|
|
235
|
-
|
|
236
|
-
struct AttributedStringBuilder {
|
|
237
|
-
static func buildBlock(_ components: AttributedString...) -> AttributedString {
|
|
238
|
-
components.reduce(into: AttributedString()) { $0.append($1) }
|
|
239
|
-
}
|
|
187
|
+
import SwiftUI
|
|
240
188
|
|
|
241
|
-
|
|
242
|
-
|
|
189
|
+
@resultBuilder
|
|
190
|
+
enum StyledTextBuilder {
|
|
191
|
+
static func buildExpression(_ s: AttributedString) -> AttributedString { s }
|
|
192
|
+
static func buildBlock(_ parts: AttributedString...) -> AttributedString {
|
|
193
|
+
parts.reduce(into: AttributedString()) { $0.append($1) }
|
|
243
194
|
}
|
|
195
|
+
static func buildFinalResult(_ joined: AttributedString) -> Text { Text(joined) }
|
|
244
196
|
}
|
|
245
197
|
```
|
|
246
198
|
|
|
247
|
-
## Property
|
|
199
|
+
## Property wrappers
|
|
200
|
+
|
|
201
|
+
### Backed by UserDefaults
|
|
248
202
|
|
|
249
203
|
```swift
|
|
204
|
+
import Foundation
|
|
205
|
+
|
|
250
206
|
@propertyWrapper
|
|
251
|
-
struct
|
|
207
|
+
struct Preference<Value> {
|
|
252
208
|
let key: String
|
|
253
|
-
let
|
|
254
|
-
let
|
|
209
|
+
let fallback: Value
|
|
210
|
+
let defaults: UserDefaults
|
|
255
211
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
212
|
+
init(wrappedValue: Value, _ key: String, defaults: UserDefaults = .standard) {
|
|
213
|
+
self.key = key
|
|
214
|
+
self.fallback = wrappedValue
|
|
215
|
+
self.defaults = defaults
|
|
259
216
|
}
|
|
260
217
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
self.store = store
|
|
218
|
+
var wrappedValue: Value {
|
|
219
|
+
get { defaults.object(forKey: key) as? Value ?? fallback }
|
|
220
|
+
set { defaults.set(newValue, forKey: key) }
|
|
265
221
|
}
|
|
266
222
|
}
|
|
267
223
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
@
|
|
271
|
-
@AppStorage("preferred_theme") var theme = "system"
|
|
224
|
+
struct ReaderSettings {
|
|
225
|
+
@Preference("fontScale") var fontScale = 1.0
|
|
226
|
+
@Preference("nightMode") var nightMode = false
|
|
272
227
|
}
|
|
273
228
|
```
|
|
274
229
|
|
|
275
|
-
|
|
230
|
+
Passing a suite-specific `UserDefaults` in tests keeps them isolated.
|
|
231
|
+
|
|
232
|
+
### Validation with a projected flag
|
|
276
233
|
|
|
277
234
|
```swift
|
|
278
235
|
@propertyWrapper
|
|
279
|
-
struct
|
|
280
|
-
private var
|
|
281
|
-
private let
|
|
282
|
-
private(set) var
|
|
236
|
+
struct Checked {
|
|
237
|
+
private var text = ""
|
|
238
|
+
private let rule: (String) -> Bool
|
|
239
|
+
private(set) var passes = false
|
|
283
240
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
value = newValue
|
|
288
|
-
isValid = validator(newValue)
|
|
289
|
-
}
|
|
241
|
+
init(wrappedValue: String, _ rule: @escaping (String) -> Bool) {
|
|
242
|
+
self.rule = rule
|
|
243
|
+
self.wrappedValue = wrappedValue
|
|
290
244
|
}
|
|
291
245
|
|
|
292
|
-
var
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
self.value = wrappedValue
|
|
296
|
-
self.validator = validator
|
|
297
|
-
self.isValid = validator(wrappedValue)
|
|
246
|
+
var wrappedValue: String {
|
|
247
|
+
get { text }
|
|
248
|
+
set { text = newValue; passes = rule(newValue) }
|
|
298
249
|
}
|
|
250
|
+
var projectedValue: Bool { passes }
|
|
299
251
|
}
|
|
300
252
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
@
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
var canSubmit: Bool { $username && $email }
|
|
253
|
+
struct SignupForm {
|
|
254
|
+
@Checked({ $0.count >= 3 }) var handle = ""
|
|
255
|
+
@Checked({ $0.contains("@") }) var mail = ""
|
|
256
|
+
var canSubmit: Bool { $handle && $mail }
|
|
307
257
|
}
|
|
308
258
|
```
|
|
309
259
|
|
|
310
|
-
## Advanced Regex
|
|
260
|
+
## Advanced Regex
|
|
311
261
|
|
|
312
|
-
|
|
262
|
+
A `Reference` names a capture so it can be read by key instead of by tuple
|
|
263
|
+
position. `ChoiceOf` lists alternatives.
|
|
313
264
|
|
|
314
265
|
```swift
|
|
315
266
|
import RegexBuilder
|
|
316
267
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
268
|
+
let level = Reference(Substring.self)
|
|
269
|
+
let message = Reference(Substring.self)
|
|
270
|
+
let logLine = Regex {
|
|
271
|
+
"["
|
|
272
|
+
Capture(as: level) { ChoiceOf { "DEBUG"; "NOTICE"; "FAULT" } }
|
|
273
|
+
"] "
|
|
274
|
+
Capture(as: message) { OneOrMore(.any) }
|
|
321
275
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
let levelRef = Reference(Substring.self)
|
|
325
|
-
let messageRef = Reference(Substring.self)
|
|
326
|
-
|
|
327
|
-
let logRegex = Regex {
|
|
328
|
-
Capture(as: timestampRef) { /\[.+?\]/ }
|
|
329
|
-
" "
|
|
330
|
-
Capture(as: levelRef) {
|
|
331
|
-
ChoiceOf { "INFO"; "WARN"; "ERROR"; "DEBUG" }
|
|
332
|
-
}
|
|
333
|
-
": "
|
|
334
|
-
Capture(as: messageRef) { OneOrMore(.any) }
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if let match = "[2026-05-28] INFO: Started".firstMatch(of: logRegex) {
|
|
338
|
-
let entry = LogEntry(
|
|
339
|
-
timestamp: String(match[timestampRef]),
|
|
340
|
-
level: String(match[levelRef]),
|
|
341
|
-
message: String(match[messageRef])
|
|
342
|
-
)
|
|
343
|
-
_ = entry
|
|
276
|
+
if let m = "[FAULT] disk full".firstMatch(of: logLine) {
|
|
277
|
+
print(m[level], m[message])
|
|
344
278
|
}
|
|
345
279
|
```
|
|
346
280
|
|
|
347
|
-
|
|
281
|
+
Small pieces compose into larger patterns. An IPv4 octet built once and reused:
|
|
348
282
|
|
|
349
283
|
```swift
|
|
350
|
-
let
|
|
284
|
+
let octet = Regex {
|
|
351
285
|
ChoiceOf {
|
|
352
286
|
Regex { "25"; ("0"..."5") }
|
|
353
|
-
Regex { "2"; ("0"..."4"); .digit }
|
|
354
|
-
Regex { Optionally { ("0"..."1") }; .digit; Optionally { .digit } }
|
|
287
|
+
Regex { "2"; ("0"..."4"); One(.digit) }
|
|
288
|
+
Regex { Optionally { ("0"..."1") }; One(.digit); Optionally { One(.digit) } }
|
|
355
289
|
}
|
|
356
290
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
ipOctet; "."; ipOctet; "."; ipOctet; "."; ipOctet
|
|
291
|
+
let ipv4 = Regex {
|
|
292
|
+
octet; "."; octet; "."; octet; "."; octet
|
|
360
293
|
}
|
|
294
|
+
let isAddress = "10.0.254.1".wholeMatch(of: ipv4) != nil
|
|
361
295
|
```
|
|
362
296
|
|
|
363
|
-
##
|
|
297
|
+
## Custom FormatStyle
|
|
364
298
|
|
|
365
|
-
|
|
299
|
+
Conform to `FormatStyle`, name the input and output types, implement
|
|
300
|
+
`format(_:)`, then add a static member so call sites read naturally.
|
|
366
301
|
|
|
367
302
|
```swift
|
|
368
|
-
|
|
369
|
-
let bytes: Int64
|
|
370
|
-
}
|
|
303
|
+
import Foundation
|
|
371
304
|
|
|
372
|
-
struct
|
|
373
|
-
typealias FormatInput =
|
|
305
|
+
struct PaceStyle: FormatStyle {
|
|
306
|
+
typealias FormatInput = Double
|
|
374
307
|
typealias FormatOutput = String
|
|
375
308
|
|
|
376
|
-
func format(_
|
|
377
|
-
|
|
309
|
+
func format(_ secondsPerKm: Double) -> String {
|
|
310
|
+
let total = Int(secondsPerKm.rounded())
|
|
311
|
+
return "\(total / 60)'\(String(format: "%02d", total % 60))\" /km"
|
|
378
312
|
}
|
|
379
313
|
}
|
|
380
314
|
|
|
381
|
-
extension FormatStyle where Self ==
|
|
382
|
-
static var
|
|
315
|
+
extension FormatStyle where Self == PaceStyle {
|
|
316
|
+
static var pace: PaceStyle { PaceStyle() }
|
|
383
317
|
}
|
|
384
318
|
|
|
385
|
-
|
|
386
|
-
let size = FileSize(bytes: 1_500_000)
|
|
387
|
-
size.formatted(.fileSize) // "1.5 MB"
|
|
319
|
+
let label = 312.0.formatted(.pace)
|
|
388
320
|
```
|
|
389
321
|
|
|
390
|
-
|
|
322
|
+
Anything more (locale-aware parsing, attributed output) goes to
|
|
323
|
+
`swift-formatstyle`.
|
|
324
|
+
|
|
325
|
+
## Collection patterns
|
|
326
|
+
|
|
327
|
+
The Swift Algorithms package (`import Algorithms`) adds sequence helpers:
|
|
391
328
|
|
|
392
329
|
```swift
|
|
393
|
-
// chunks(ofCount:) -- Swift Algorithms package
|
|
394
330
|
import Algorithms
|
|
395
331
|
|
|
396
|
-
let
|
|
397
|
-
for batch in
|
|
398
|
-
try await upload(batch)
|
|
399
|
-
}
|
|
332
|
+
let uploads = Array(1...23)
|
|
333
|
+
for batch in uploads.chunks(ofCount: 10) { print(batch.count) }
|
|
400
334
|
|
|
401
|
-
|
|
402
|
-
let
|
|
403
|
-
window.reduce(0, +) / Double(window.count)
|
|
404
|
-
}
|
|
335
|
+
let readings = [3.0, 5.0, 4.0, 8.0, 6.0]
|
|
336
|
+
let smoothed = readings.windows(ofCount: 3).map { $0.reduce(0, +) / 3 }
|
|
405
337
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if current > previous * 2 {
|
|
409
|
-
print("Spike detected")
|
|
410
|
-
}
|
|
411
|
-
}
|
|
338
|
+
let stops = ["Depot", "Mill Rd", "Harbour"]
|
|
339
|
+
let legs = stops.adjacentPairs().map { "\($0) to \($1)" }
|
|
412
340
|
```
|
|
413
341
|
|
|
414
|
-
|
|
342
|
+
`chunks(ofCount:)` batches, `windows(ofCount:)` slides a fixed window (a
|
|
343
|
+
moving average here), `adjacentPairs()` pairs each element with the next.
|
|
344
|
+
|
|
345
|
+
## guard: complex pattern matching
|
|
346
|
+
|
|
347
|
+
Several clauses in one `guard` can walk untyped JSON. Prefer Codable when the
|
|
348
|
+
shape is known; this is for genuinely dynamic input.
|
|
415
349
|
|
|
416
350
|
```swift
|
|
417
|
-
|
|
418
|
-
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
419
|
-
let userData = json["user"] as? [String: Any],
|
|
420
|
-
let name = userData["name"] as? String,
|
|
421
|
-
let id = userData["id"] as? Int
|
|
422
|
-
else {
|
|
423
|
-
throw ParseError.invalidFormat
|
|
424
|
-
}
|
|
351
|
+
import Foundation
|
|
425
352
|
|
|
426
|
-
|
|
427
|
-
|
|
353
|
+
func firstTrackTitle(_ data: Data) -> String? {
|
|
354
|
+
guard let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
355
|
+
let album = root["album"] as? [String: Any],
|
|
356
|
+
let tracks = album["tracks"] as? [[String: Any]],
|
|
357
|
+
let title = tracks.first?["title"] as? String
|
|
358
|
+
else { return nil }
|
|
359
|
+
return title
|
|
428
360
|
}
|
|
361
|
+
```
|
|
429
362
|
|
|
430
|
-
|
|
431
|
-
func processItems(_ items: [Item]) {
|
|
432
|
-
for item in items {
|
|
433
|
-
guard case .active(let config) = item.status,
|
|
434
|
-
config.isEnabled,
|
|
435
|
-
!config.isExpired
|
|
436
|
-
else { continue }
|
|
363
|
+
Enum payloads plus extra conditions work inside loops:
|
|
437
364
|
|
|
438
|
-
|
|
365
|
+
```swift
|
|
366
|
+
struct Plan { var isEnabled: Bool; var isExpired: Bool }
|
|
367
|
+
enum Status { case active(Plan), paused }
|
|
368
|
+
struct Account { var name: String; var status: Status }
|
|
369
|
+
|
|
370
|
+
func billable(_ accounts: [Account]) -> [String] {
|
|
371
|
+
var names: [String] = []
|
|
372
|
+
for account in accounts {
|
|
373
|
+
guard case .active(let plan) = account.status, plan.isEnabled, !plan.isExpired else {
|
|
374
|
+
continue
|
|
375
|
+
}
|
|
376
|
+
names.append(account.name)
|
|
439
377
|
}
|
|
378
|
+
return names
|
|
440
379
|
}
|
|
441
380
|
```
|
|
442
381
|
|
|
443
|
-
## Typed
|
|
382
|
+
## Typed throws in protocols
|
|
383
|
+
|
|
384
|
+
An associated error type lets each conformer throw its own concrete error
|
|
385
|
+
while callers keep exhaustive handling.
|
|
444
386
|
|
|
445
387
|
```swift
|
|
446
|
-
protocol
|
|
447
|
-
associatedtype
|
|
448
|
-
func
|
|
449
|
-
func
|
|
388
|
+
protocol Vault {
|
|
389
|
+
associatedtype VaultError: Error
|
|
390
|
+
func read(_ key: String) throws(VaultError) -> Data
|
|
391
|
+
func write(_ data: Data, for key: String) throws(VaultError)
|
|
450
392
|
}
|
|
451
393
|
|
|
452
|
-
|
|
453
|
-
enum
|
|
454
|
-
|
|
455
|
-
|
|
394
|
+
final class MemoryVault: Vault {
|
|
395
|
+
enum VaultError: Error { case missing(String), full }
|
|
396
|
+
var slots: [String: Data] = [:]
|
|
397
|
+
let capacity = 8
|
|
456
398
|
|
|
457
|
-
func
|
|
458
|
-
|
|
399
|
+
func read(_ key: String) throws(VaultError) -> Data {
|
|
400
|
+
guard let data = slots[key] else { throw .missing(key) }
|
|
401
|
+
return data
|
|
459
402
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
// ...
|
|
403
|
+
func write(_ data: Data, for key: String) throws(VaultError) {
|
|
404
|
+
guard slots.count < capacity || slots[key] != nil else { throw .full }
|
|
405
|
+
slots[key] = data
|
|
464
406
|
}
|
|
465
407
|
}
|
|
466
408
|
```
|
|
467
409
|
|
|
468
|
-
## String
|
|
410
|
+
## String interpolation
|
|
469
411
|
|
|
470
|
-
|
|
412
|
+
Add `appendInterpolation` overloads to `DefaultStringInterpolation`:
|
|
471
413
|
|
|
472
414
|
```swift
|
|
415
|
+
import Foundation
|
|
416
|
+
|
|
473
417
|
extension DefaultStringInterpolation {
|
|
474
418
|
mutating func appendInterpolation(json value: some Encodable) {
|
|
475
|
-
let
|
|
476
|
-
|
|
477
|
-
if let data = try?
|
|
478
|
-
|
|
479
|
-
|
|
419
|
+
let writer = JSONEncoder()
|
|
420
|
+
writer.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
421
|
+
if let data = try? writer.encode(value), let text = String(data: data, encoding: .utf8) {
|
|
422
|
+
appendLiteral(text)
|
|
423
|
+
} else {
|
|
424
|
+
appendLiteral("<unencodable>")
|
|
480
425
|
}
|
|
481
426
|
}
|
|
482
427
|
|
|
483
|
-
mutating func appendInterpolation(ordinal
|
|
484
|
-
let
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
appendLiteral(result)
|
|
488
|
-
}
|
|
428
|
+
mutating func appendInterpolation(ordinal n: Int) {
|
|
429
|
+
let f = NumberFormatter()
|
|
430
|
+
f.numberStyle = .ordinal
|
|
431
|
+
appendLiteral(f.string(from: NSNumber(value: n)) ?? String(n))
|
|
489
432
|
}
|
|
490
433
|
}
|
|
491
434
|
|
|
492
|
-
|
|
493
|
-
|
|
435
|
+
struct Seat: Encodable { var row: Int; var letter: String }
|
|
436
|
+
let dump = "\(json: Seat(row: 12, letter: "C"))"
|
|
437
|
+
let place = "You finished \(ordinal: 2)"
|
|
494
438
|
```
|
|
495
439
|
|
|
496
|
-
## Never:
|
|
440
|
+
## Never: advanced usage
|
|
497
441
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
442
|
+
- Combine uses `Never` as a failure type: `AnyPublisher<Int, Never>` is a
|
|
443
|
+
stream that cannot fail, which is what `assign(to:)` and SwiftUI bindings
|
|
444
|
+
expect.
|
|
445
|
+
- Empty enums are uninhabited too, which makes them good phantom types. A
|
|
446
|
+
generic parameter can carry state in the type, and constrained extensions
|
|
447
|
+
expose methods only in the right state:
|
|
501
448
|
|
|
502
|
-
|
|
503
|
-
enum
|
|
504
|
-
enum
|
|
449
|
+
```swift
|
|
450
|
+
enum Draft {}
|
|
451
|
+
enum Published {}
|
|
505
452
|
|
|
506
|
-
struct
|
|
507
|
-
|
|
453
|
+
struct Article<Stage> {
|
|
454
|
+
var body: String
|
|
508
455
|
}
|
|
509
456
|
|
|
510
|
-
extension
|
|
511
|
-
func
|
|
457
|
+
extension Article where Stage == Draft {
|
|
458
|
+
func publish() -> Article<Published> { Article<Published>(body: body) }
|
|
512
459
|
}
|
|
513
460
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
// No body needed -- Never has no values, so this is never called
|
|
461
|
+
extension Article where Stage == Published {
|
|
462
|
+
var shareURL: String { "https://example.com/a/\(body.count)" }
|
|
517
463
|
}
|
|
464
|
+
|
|
465
|
+
let draft = Article<Draft>(body: "Hello")
|
|
466
|
+
let live = draft.publish()
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
- Because `Never` has no values, a function taking one can never be called,
|
|
470
|
+
so its body may stay empty with no `return`. An empty `switch impossible {}`
|
|
471
|
+
also type-checks, but the compiler then warns "will never be executed":
|
|
472
|
+
|
|
473
|
+
```swift
|
|
474
|
+
func unreachable<T>(_ impossible: Never) -> T {}
|
|
518
475
|
```
|