@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
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# Network
|
|
1
|
+
# Network Framework
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
path monitoring
|
|
6
|
-
Swift 6.3
|
|
3
|
+
Reach for Network.framework when URLSession is the wrong layer: raw TCP or
|
|
4
|
+
UDP, a WebSocket at the protocol level, Bonjour discovery, a local server, or
|
|
5
|
+
detailed path monitoring. Ordinary HTTP APIs stay on URLSession. Examples
|
|
6
|
+
assume Swift 6.3 and iOS 26.
|
|
7
7
|
|
|
8
8
|
## Contents
|
|
9
9
|
|
|
@@ -14,575 +14,350 @@ Swift 6.3 / iOS 26+.
|
|
|
14
14
|
- [NWParameters Configuration](#nwparameters-configuration)
|
|
15
15
|
- [TLS Configuration](#tls-configuration)
|
|
16
16
|
- [WebSocket Support](#websocket-support)
|
|
17
|
-
- [
|
|
17
|
+
- [The iOS 26 NetworkConnection API](#the-ios-26-networkconnection-api)
|
|
18
18
|
|
|
19
19
|
## NWConnection
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
**Documentation:**
|
|
25
|
-
[sosumi.ai/documentation/network/nwconnection](https://sosumi.ai/documentation/network/nwconnection)
|
|
26
|
-
|
|
27
|
-
### TCP Connection
|
|
21
|
+
`NWConnection` is one bidirectional connection. It speaks TCP, UDP, TLS,
|
|
22
|
+
DTLS, QUIC and WebSocket depending on its parameters.
|
|
23
|
+
API reference: [NWConnection docs](https://developer.apple.com/documentation/network/nwconnection).
|
|
28
24
|
|
|
29
25
|
```swift
|
|
26
|
+
import Foundation
|
|
30
27
|
import Network
|
|
28
|
+
import OSLog
|
|
31
29
|
|
|
32
|
-
let
|
|
33
|
-
host: "api.example.com",
|
|
34
|
-
port: 443,
|
|
35
|
-
using: .tls
|
|
36
|
-
)
|
|
30
|
+
extension Logger { static let net = Logger(subsystem: "app.networking", category: "net") }
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
let link = NWConnection(host: "telemetry.example.net", port: 443, using: .tls)
|
|
33
|
+
link.stateUpdateHandler = { state in
|
|
39
34
|
switch state {
|
|
40
|
-
case .ready:
|
|
41
|
-
|
|
42
|
-
case .
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
print("Failed: \(error.localizedDescription)")
|
|
46
|
-
case .cancelled:
|
|
47
|
-
print("Cancelled")
|
|
48
|
-
default:
|
|
49
|
-
break
|
|
35
|
+
case .ready: Logger.net.info("connected")
|
|
36
|
+
case .waiting(let error): Logger.net.notice("waiting for a path: \(error.localizedDescription)")
|
|
37
|
+
case .failed(let error): Logger.net.error("failed: \(error.localizedDescription)"); link.cancel()
|
|
38
|
+
case .cancelled: Logger.net.info("closed")
|
|
39
|
+
default: break
|
|
50
40
|
}
|
|
51
41
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
func
|
|
60
|
-
connection.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if let error {
|
|
66
|
-
print("Send error: \(error)")
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Receiving Data
|
|
74
|
-
|
|
75
|
-
```swift
|
|
76
|
-
func receive(on connection: NWConnection) {
|
|
77
|
-
connection.receive(
|
|
78
|
-
minimumIncompleteLength: 1,
|
|
79
|
-
maximumLength: 65536
|
|
80
|
-
) { content, contentContext, isComplete, error in
|
|
81
|
-
if let data = content {
|
|
82
|
-
handleData(data)
|
|
83
|
-
}
|
|
84
|
-
if isComplete {
|
|
85
|
-
connection.cancel()
|
|
86
|
-
} else if let error {
|
|
87
|
-
print("Receive error: \(error)")
|
|
88
|
-
} else {
|
|
89
|
-
// Continue receiving
|
|
90
|
-
receive(on: connection)
|
|
42
|
+
link.start(queue: .main)
|
|
43
|
+
|
|
44
|
+
link.send(content: Data("HELLO\n".utf8), contentContext: .defaultMessage, isComplete: true,
|
|
45
|
+
completion: .contentProcessed { error in
|
|
46
|
+
if let error { Logger.net.error("send failed: \(error.localizedDescription)") }
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
func readLoop(on connection: NWConnection) {
|
|
50
|
+
connection.receive(minimumIncompleteLength: 1, maximumLength: 65_536) { data, _, isComplete, error in
|
|
51
|
+
if let data, !data.isEmpty { consume(data) }
|
|
52
|
+
if let error {
|
|
53
|
+
Logger.net.error("receive failed: \(error.localizedDescription)")
|
|
54
|
+
return
|
|
91
55
|
}
|
|
56
|
+
if isComplete { connection.cancel(); return }
|
|
57
|
+
readLoop(on: connection)
|
|
92
58
|
}
|
|
93
59
|
}
|
|
94
60
|
```
|
|
95
61
|
|
|
96
|
-
|
|
62
|
+
UDP uses the same type:
|
|
97
63
|
|
|
98
64
|
```swift
|
|
99
|
-
let
|
|
100
|
-
|
|
101
|
-
port: 5000,
|
|
102
|
-
using: .udp
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
udpConnection.stateUpdateHandler = { state in
|
|
65
|
+
let beacon = NWConnection(host: "239.255.10.20", port: 6200, using: .udp)
|
|
66
|
+
beacon.stateUpdateHandler = { state in
|
|
106
67
|
if case .ready = state {
|
|
107
|
-
|
|
108
|
-
udpConnection.send(
|
|
109
|
-
content: message,
|
|
110
|
-
completion: .contentProcessed { _ in }
|
|
111
|
-
)
|
|
68
|
+
beacon.send(content: Data("ping".utf8), completion: .contentProcessed { _ in })
|
|
112
69
|
}
|
|
113
70
|
}
|
|
114
|
-
|
|
115
|
-
udpConnection.start(queue: .main)
|
|
71
|
+
beacon.start(queue: .global())
|
|
116
72
|
```
|
|
117
73
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Always cancel connections when done:
|
|
125
|
-
|
|
126
|
-
```swift
|
|
127
|
-
connection.cancel() // Releases resources, transitions to .cancelled
|
|
128
|
-
```
|
|
74
|
+
- States run `setup`, `preparing`, `ready`, and end in `cancelled` or
|
|
75
|
+
`failed`.
|
|
76
|
+
- `waiting(NWError)` means no usable path right now. The connection tries
|
|
77
|
+
again by itself when the network comes back.
|
|
78
|
+
- Call `cancel()` when you are done. It frees the resources and moves the
|
|
79
|
+
connection to `cancelled`.
|
|
129
80
|
|
|
130
81
|
## NWListener
|
|
131
82
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
**Documentation:**
|
|
136
|
-
[sosumi.ai/documentation/network/nwlistener](https://sosumi.ai/documentation/network/nwlistener)
|
|
137
|
-
|
|
138
|
-
### TCP Server
|
|
83
|
+
`NWListener` accepts incoming connections, which is how you run a small TCP
|
|
84
|
+
or UDP server on the device.
|
|
85
|
+
API reference: [NWListener docs](https://developer.apple.com/documentation/network/nwlistener).
|
|
139
86
|
|
|
140
87
|
```swift
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
listener.stateUpdateHandler = { state in
|
|
88
|
+
func startServer() throws -> NWListener {
|
|
89
|
+
let port: NWEndpoint.Port = 8080
|
|
90
|
+
let server = try NWListener(using: .tcp, on: port)
|
|
91
|
+
server.stateUpdateHandler = { state in
|
|
147
92
|
switch state {
|
|
148
|
-
case .ready:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
print("Listener failed: \(error)")
|
|
152
|
-
listener.cancel()
|
|
153
|
-
default:
|
|
154
|
-
break
|
|
93
|
+
case .ready: Logger.net.info("listening on \(server.port?.rawValue ?? 0)")
|
|
94
|
+
case .failed(let error): Logger.net.error("\(error.localizedDescription)"); server.cancel()
|
|
95
|
+
default: break
|
|
155
96
|
}
|
|
156
97
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
connection.stateUpdateHandler = { state in
|
|
161
|
-
if case .ready = state {
|
|
162
|
-
receive(on: connection)
|
|
163
|
-
}
|
|
98
|
+
server.newConnectionHandler = { incoming in
|
|
99
|
+
incoming.stateUpdateHandler = { state in
|
|
100
|
+
if case .ready = state { readLoop(on: incoming) }
|
|
164
101
|
}
|
|
165
|
-
|
|
102
|
+
incoming.start(queue: .global())
|
|
166
103
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Advertising via Bonjour
|
|
174
|
-
|
|
175
|
-
```swift
|
|
176
|
-
let listener = try NWListener(using: .tcp)
|
|
177
|
-
listener.service = NWListener.Service(
|
|
178
|
-
name: "MyApp",
|
|
179
|
-
type: "_myapp._tcp"
|
|
180
|
-
)
|
|
181
|
-
listener.serviceRegistrationUpdateHandler = { change in
|
|
182
|
-
switch change {
|
|
183
|
-
case .add(let endpoint):
|
|
184
|
-
print("Service registered: \(endpoint)")
|
|
185
|
-
default:
|
|
186
|
-
break
|
|
104
|
+
server.service = NWListener.Service(name: "Workbench", type: "_workbench._tcp")
|
|
105
|
+
server.serviceRegistrationUpdateHandler = { change in
|
|
106
|
+
if case .add(let endpoint) = change { Logger.net.info("advertised \(String(describing: endpoint))") }
|
|
187
107
|
}
|
|
108
|
+
server.start(queue: .global())
|
|
109
|
+
return server
|
|
188
110
|
}
|
|
189
|
-
listener.start(queue: .main)
|
|
190
111
|
```
|
|
191
112
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
113
|
+
`NWEndpoint.Port` is expressible by an integer literal, so a fixed port
|
|
114
|
+
needs no optional. For a port from user input, use
|
|
115
|
+
`NWEndpoint.Port(rawValue:)` and handle `nil`. Setting `service` advertises
|
|
116
|
+
the listener over Bonjour.
|
|
195
117
|
|
|
196
|
-
|
|
197
|
-
[sosumi.ai/documentation/network/nwbrowser](https://sosumi.ai/documentation/network/nwbrowser)
|
|
118
|
+
## NWBrowser
|
|
198
119
|
|
|
199
|
-
|
|
120
|
+
`NWBrowser` discovers peers that advertise over Bonjour (multicast DNS) on the LAN.
|
|
121
|
+
API reference: [NWBrowser docs](https://developer.apple.com/documentation/network/nwbrowser).
|
|
200
122
|
|
|
201
123
|
```swift
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
let browser = NWBrowser(
|
|
205
|
-
for: .bonjour(type: "_myapp._tcp", domain: nil),
|
|
206
|
-
using: .tcp
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
browser.stateUpdateHandler = { state in
|
|
210
|
-
if case .failed(let error) = state {
|
|
211
|
-
print("Browser failed: \(error)")
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
browser.browseResultsChangedHandler = { results, changes in
|
|
216
|
-
for result in results {
|
|
217
|
-
switch result.endpoint {
|
|
218
|
-
case .service(let name, let type, let domain, _):
|
|
219
|
-
print("Found: \(name).\(type)\(domain)")
|
|
220
|
-
default:
|
|
221
|
-
break
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
124
|
+
let scanner = NWBrowser(for: .bonjour(type: "_workbench._tcp", domain: nil), using: .tcp)
|
|
125
|
+
scanner.browseResultsChangedHandler = { results, changes in
|
|
225
126
|
for change in changes {
|
|
226
127
|
switch change {
|
|
227
|
-
case .added(let
|
|
228
|
-
|
|
229
|
-
case .removed(let
|
|
230
|
-
|
|
231
|
-
default:
|
|
232
|
-
break
|
|
128
|
+
case .added(let found):
|
|
129
|
+
if case .service(let name, _, _, _) = found.endpoint { Logger.net.info("found \(name)") }
|
|
130
|
+
case .removed(let gone):
|
|
131
|
+
Logger.net.info("lost \(String(describing: gone.endpoint))")
|
|
132
|
+
default: break
|
|
233
133
|
}
|
|
234
134
|
}
|
|
235
135
|
}
|
|
136
|
+
scanner.start(queue: .main)
|
|
236
137
|
|
|
237
|
-
|
|
138
|
+
func connect(to found: NWBrowser.Result) -> NWConnection {
|
|
139
|
+
NWConnection(to: found.endpoint, using: .tcp)
|
|
140
|
+
}
|
|
238
141
|
```
|
|
239
142
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
```swift
|
|
243
|
-
// result is an NWBrowser.Result from browseResultsChangedHandler
|
|
244
|
-
let connection = NWConnection(to: result.endpoint, using: .tcp)
|
|
245
|
-
connection.start(queue: .main)
|
|
246
|
-
```
|
|
143
|
+
The handler receives the full current `results` set and the `changes`
|
|
144
|
+
(`.added`, `.removed`) since the last call.
|
|
247
145
|
|
|
248
146
|
## NWPathMonitor
|
|
249
147
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
**Documentation:**
|
|
255
|
-
[sosumi.ai/documentation/network/nwpathmonitor](https://sosumi.ai/documentation/network/nwpathmonitor)
|
|
256
|
-
|
|
257
|
-
### Basic Reachability with AsyncSequence
|
|
148
|
+
`NWPathMonitor` replaces the deprecated `SCNetworkReachability`. It reports
|
|
149
|
+
whether a path exists and whether it is expensive or constrained.
|
|
150
|
+
API reference: [NWPathMonitor docs](https://developer.apple.com/documentation/network/nwpathmonitor).
|
|
258
151
|
|
|
259
152
|
```swift
|
|
260
|
-
import Network
|
|
261
|
-
|
|
262
153
|
@MainActor @Observable
|
|
263
|
-
class
|
|
264
|
-
var
|
|
265
|
-
var
|
|
266
|
-
var
|
|
267
|
-
|
|
268
|
-
func
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
isExpensive = path.isExpensive // Cellular
|
|
274
|
-
isConstrained = path.isConstrained // Low Data Mode
|
|
154
|
+
final class LinkQuality {
|
|
155
|
+
private(set) var online = true
|
|
156
|
+
private(set) var metered = false
|
|
157
|
+
private(set) var lowData = false
|
|
158
|
+
|
|
159
|
+
func watch() async {
|
|
160
|
+
for await path in NWPathMonitor() {
|
|
161
|
+
online = path.status == .satisfied
|
|
162
|
+
metered = path.isExpensive
|
|
163
|
+
lowData = path.isConstrained
|
|
275
164
|
}
|
|
276
165
|
}
|
|
166
|
+
|
|
167
|
+
var imagePolicy: ImagePolicy {
|
|
168
|
+
if lowData { return ImagePolicy(quality: .low, prefetch: false, preloadVideo: false) }
|
|
169
|
+
if metered { return ImagePolicy(quality: .standard, prefetch: true, preloadVideo: false) }
|
|
170
|
+
return ImagePolicy(quality: .high, prefetch: true, preloadVideo: true)
|
|
171
|
+
}
|
|
277
172
|
}
|
|
278
173
|
```
|
|
279
174
|
|
|
280
|
-
|
|
175
|
+
Start `watch()` from a `.task` so the loop ends with the view. To watch one
|
|
176
|
+
interface type, or on an SDK where the monitor is not yet an async sequence,
|
|
177
|
+
use the handler API:
|
|
281
178
|
|
|
282
179
|
```swift
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
print("Wi-Fi: \(path.status)")
|
|
180
|
+
let wifiOnly = NWPathMonitor(requiredInterfaceType: .wifi)
|
|
181
|
+
wifiOnly.pathUpdateHandler = { path in
|
|
182
|
+
Logger.net.info("wifi path: \(String(describing: path.status))")
|
|
287
183
|
}
|
|
288
|
-
|
|
184
|
+
wifiOnly.start(queue: .global())
|
|
289
185
|
```
|
|
290
186
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
| Property | Description |
|
|
187
|
+
| `NWPath` member | Meaning |
|
|
294
188
|
|---|---|
|
|
295
|
-
| `status` | `.satisfied`, `.unsatisfied
|
|
296
|
-
| `isExpensive` |
|
|
297
|
-
| `isConstrained` |
|
|
298
|
-
| `availableInterfaces` |
|
|
299
|
-
| `supportsDNS` |
|
|
300
|
-
| `supportsIPv4
|
|
189
|
+
| `status` | `.satisfied`, `.unsatisfied` or `.requiresConnection` |
|
|
190
|
+
| `isExpensive` | Cellular or a personal hotspot |
|
|
191
|
+
| `isConstrained` | Low Data Mode is on |
|
|
192
|
+
| `availableInterfaces` | The `NWInterface` values the path can use |
|
|
193
|
+
| `supportsDNS` | DNS resolution is available |
|
|
194
|
+
| `supportsIPv4`, `supportsIPv6` | Address families the path supports |
|
|
301
195
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
let monitor = NWPathMonitor()
|
|
306
|
-
|
|
307
|
-
for await path in monitor {
|
|
308
|
-
if path.isConstrained {
|
|
309
|
-
// Low Data Mode: reduce image quality, skip prefetch
|
|
310
|
-
imageQuality = .low
|
|
311
|
-
} else if path.isExpensive {
|
|
312
|
-
// Cellular: use standard quality, skip video preload
|
|
313
|
-
imageQuality = .standard
|
|
314
|
-
} else {
|
|
315
|
-
// Wi-Fi: full quality, prefetch aggressively
|
|
316
|
-
imageQuality = .high
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
```
|
|
196
|
+
Adapt to the path: on a constrained path fetch low-quality images and skip
|
|
197
|
+
prefetching; on an expensive path keep standard quality and skip video
|
|
198
|
+
preloading; otherwise use high quality and prefetch freely.
|
|
320
199
|
|
|
321
200
|
## NWParameters Configuration
|
|
322
201
|
|
|
323
|
-
`NWParameters`
|
|
202
|
+
`NWParameters` describes the protocol stack and path rules for a connection
|
|
203
|
+
or listener. Build options objects directly and pass them in, which avoids
|
|
204
|
+
casting them back out of the stack:
|
|
324
205
|
|
|
325
206
|
```swift
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
let connection = NWConnection(
|
|
341
|
-
host: "api.example.com",
|
|
342
|
-
port: 8080,
|
|
343
|
-
using: tcpParams
|
|
344
|
-
)
|
|
207
|
+
let tcp = NWProtocolTCP.Options()
|
|
208
|
+
tcp.connectionTimeout = 10
|
|
209
|
+
tcp.enableKeepalive = true
|
|
210
|
+
tcp.keepaliveInterval = 30
|
|
211
|
+
|
|
212
|
+
let lanOnly = NWParameters(tls: nil, tcp: tcp)
|
|
213
|
+
lanOnly.requiredInterfaceType = .wifi
|
|
214
|
+
lanOnly.prohibitExpensivePaths = true
|
|
215
|
+
lanOnly.prohibitConstrainedPaths = true
|
|
216
|
+
|
|
217
|
+
let udp = NWProtocolUDP.Options()
|
|
218
|
+
udp.preferNoChecksum = true
|
|
219
|
+
let datagrams = NWParameters(dtls: nil, udp: udp)
|
|
345
220
|
```
|
|
346
221
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
```swift
|
|
350
|
-
let udpParams = NWParameters.udp
|
|
351
|
-
let udpOptions = udpParams.defaultProtocolStack
|
|
352
|
-
.transportProtocol as! NWProtocolUDP.Options
|
|
353
|
-
udpOptions.preferNoChecksum = true
|
|
354
|
-
|
|
355
|
-
let connection = NWConnection(
|
|
356
|
-
host: "239.0.0.1",
|
|
357
|
-
port: 5000,
|
|
358
|
-
using: udpParams
|
|
359
|
-
)
|
|
360
|
-
```
|
|
222
|
+
If you must adjust a stack you did not build, use a conditional cast:
|
|
223
|
+
`if let tcp = params.defaultProtocolStack.transportProtocol as? NWProtocolTCP.Options`.
|
|
361
224
|
|
|
362
225
|
## TLS Configuration
|
|
363
226
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
handling for that protocol stack; keep deep certificate-trust and SPKI pinning
|
|
369
|
-
implementation in `swift-security`.
|
|
227
|
+
TLS is configured with `NWProtocolTLS.Options`. ATS belongs to the URL
|
|
228
|
+
Loading System and does not police this layer, so the TLS version, cipher
|
|
229
|
+
choices and trust evaluation are yours to set. Deep trust work and SPKI
|
|
230
|
+
pinning belong in `swift-security`.
|
|
370
231
|
|
|
371
232
|
```swift
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
let
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
.
|
|
383
|
-
)
|
|
384
|
-
|
|
385
|
-
let connection = NWConnection(
|
|
386
|
-
host: "secure.example.com",
|
|
387
|
-
port: 443,
|
|
388
|
-
using: tlsParams
|
|
389
|
-
)
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Inspecting TLS Metadata
|
|
393
|
-
|
|
394
|
-
```swift
|
|
395
|
-
connection.stateUpdateHandler = { state in
|
|
396
|
-
if case .ready = state {
|
|
397
|
-
if let metadata = connection.metadata(
|
|
398
|
-
definition: NWProtocolTLS.definition
|
|
399
|
-
) as? NWProtocolTLS.Metadata {
|
|
400
|
-
let secMetadata = metadata.securityProtocolMetadata
|
|
401
|
-
let negotiatedProtocol = sec_protocol_metadata_get_negotiated_tls_protocol_version(secMetadata)
|
|
402
|
-
print("TLS version: \(negotiatedProtocol)")
|
|
403
|
-
}
|
|
404
|
-
}
|
|
233
|
+
let tls = NWProtocolTLS.Options()
|
|
234
|
+
sec_protocol_options_set_min_tls_protocol_version(tls.securityProtocolOptions, .TLSv13)
|
|
235
|
+
let secure = NWParameters(tls: tls)
|
|
236
|
+
|
|
237
|
+
let session = NWConnection(host: "vault.example.net", port: 8443, using: secure)
|
|
238
|
+
session.stateUpdateHandler = { state in
|
|
239
|
+
guard case .ready = state else { return }
|
|
240
|
+
let raw = session.metadata(definition: NWProtocolTLS.definition)
|
|
241
|
+
guard let meta = raw as? NWProtocolTLS.Metadata else { return }
|
|
242
|
+
let version = sec_protocol_metadata_get_negotiated_tls_protocol_version(meta.securityProtocolMetadata)
|
|
243
|
+
Logger.net.info("negotiated TLS \(version.rawValue)")
|
|
405
244
|
}
|
|
245
|
+
session.start(queue: .global())
|
|
406
246
|
```
|
|
407
247
|
|
|
408
248
|
## WebSocket Support
|
|
409
249
|
|
|
410
|
-
Network.framework
|
|
411
|
-
|
|
412
|
-
### WebSocket Client
|
|
250
|
+
Network.framework exposes WebSocket through `NWProtocolWebSocket`, layered on
|
|
251
|
+
top of TLS and TCP.
|
|
413
252
|
|
|
414
253
|
```swift
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
let connection = NWConnection(
|
|
427
|
-
host: NWEndpoint.Host(url),
|
|
428
|
-
port: 443,
|
|
429
|
-
using: params
|
|
430
|
-
)
|
|
431
|
-
|
|
432
|
-
return connection
|
|
254
|
+
let ws = NWProtocolWebSocket.Options()
|
|
255
|
+
ws.autoReplyPing = true
|
|
256
|
+
let params = NWParameters.tls
|
|
257
|
+
params.defaultProtocolStack.applicationProtocols.insert(ws, at: 0)
|
|
258
|
+
let socket = NWConnection(host: "stream.example.net", port: 443, using: params)
|
|
259
|
+
|
|
260
|
+
func sendText(_ message: String, over connection: NWConnection) {
|
|
261
|
+
let meta = NWProtocolWebSocket.Metadata(opcode: .text)
|
|
262
|
+
let context = NWConnection.ContentContext(identifier: "text", metadata: [meta])
|
|
263
|
+
connection.send(content: Data(message.utf8), contentContext: context, isComplete: true,
|
|
264
|
+
completion: .contentProcessed { _ in })
|
|
433
265
|
}
|
|
434
|
-
```
|
|
435
266
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
contentContext: context,
|
|
450
|
-
isComplete: true,
|
|
451
|
-
completion: .contentProcessed { error in
|
|
452
|
-
if let error {
|
|
453
|
-
print("WebSocket send error: \(error)")
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
)
|
|
457
|
-
}
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
### Receiving WebSocket Messages
|
|
461
|
-
|
|
462
|
-
```swift
|
|
463
|
-
func receiveWebSocketMessage(on connection: NWConnection) {
|
|
464
|
-
connection.receiveMessage { data, context, isComplete, error in
|
|
465
|
-
if let data,
|
|
466
|
-
let metadata = context?.protocolMetadata(
|
|
467
|
-
definition: NWProtocolWebSocket.definition
|
|
468
|
-
) as? NWProtocolWebSocket.Metadata {
|
|
469
|
-
switch metadata.opcode {
|
|
470
|
-
case .text:
|
|
471
|
-
let text = String(data: data, encoding: .utf8) ?? ""
|
|
472
|
-
print("Received text: \(text)")
|
|
473
|
-
case .binary:
|
|
474
|
-
print("Received binary: \(data.count) bytes")
|
|
475
|
-
case .close:
|
|
476
|
-
print("Connection closed")
|
|
477
|
-
return
|
|
478
|
-
default:
|
|
479
|
-
break
|
|
480
|
-
}
|
|
267
|
+
func listen(on connection: NWConnection) {
|
|
268
|
+
connection.receiveMessage { data, context, _, error in
|
|
269
|
+
guard error == nil else { return }
|
|
270
|
+
let meta = context?.protocolMetadata(definition: NWProtocolWebSocket.definition)
|
|
271
|
+
as? NWProtocolWebSocket.Metadata
|
|
272
|
+
switch meta?.opcode {
|
|
273
|
+
case .text?, .binary?:
|
|
274
|
+
if let data { consume(data) }
|
|
275
|
+
case .close?:
|
|
276
|
+
connection.cancel()
|
|
277
|
+
return
|
|
278
|
+
default:
|
|
279
|
+
break
|
|
481
280
|
}
|
|
482
|
-
|
|
483
|
-
receiveWebSocketMessage(on: connection)
|
|
281
|
+
listen(on: connection)
|
|
484
282
|
}
|
|
485
283
|
}
|
|
486
284
|
```
|
|
487
285
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
over protocol stacks, supports structured concurrency patterns, and integrates
|
|
493
|
-
with async/await via closure-based state handlers.
|
|
286
|
+
With `autoReplyPing` the framework answers pings for you. For app-level
|
|
287
|
+
WebSockets, `URLSessionWebSocketTask` (see
|
|
288
|
+
[background-websocket.md](background-websocket.md#urlsessionwebsockettask))
|
|
289
|
+
is usually simpler.
|
|
494
290
|
|
|
495
|
-
|
|
496
|
-
[sosumi.ai/documentation/network/networkconnection](https://sosumi.ai/documentation/network/networkconnection)
|
|
291
|
+
## The iOS 26 NetworkConnection API
|
|
497
292
|
|
|
498
|
-
|
|
293
|
+
`NetworkConnection` is the Swift-first connection type added in iOS 26. It is
|
|
294
|
+
generic over its protocol stack, uses async/await, and reports state through
|
|
295
|
+
closures. API reference: [NetworkConnection docs](https://developer.apple.com/documentation/network/networkconnection).
|
|
499
296
|
|
|
500
297
|
| | `NWConnection` | `NetworkConnection` |
|
|
501
298
|
|---|---|---|
|
|
502
|
-
|
|
|
503
|
-
|
|
|
504
|
-
|
|
|
505
|
-
|
|
|
506
|
-
|
|
|
507
|
-
|
|
|
508
|
-
|
|
509
|
-
### Basic Usage Shape
|
|
299
|
+
| Availability | iOS 12+ | iOS 26+ |
|
|
300
|
+
| Stack typing | Untyped parameters | Generic `ApplicationProtocol` |
|
|
301
|
+
| Streams | One connection | Many QUIC streams share one connection |
|
|
302
|
+
| State | `stateUpdateHandler` | `onStateUpdate` and `onPathUpdate` closures |
|
|
303
|
+
| Wi-Fi Aware | Not available | `wifiAware` property |
|
|
304
|
+
| Style | Callbacks on a queue | Async calls, `Sendable`-aware closures |
|
|
510
305
|
|
|
511
306
|
```swift
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
let endpoint = NWEndpoint.hostPort(host: "api.example.com", port: 443)
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
Use the initializer variant that matches your protocol stack. The documented
|
|
518
|
-
forms are:
|
|
307
|
+
let endpoint = NWEndpoint.hostPort(host: "relay.example.net", port: 9443)
|
|
519
308
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
NetworkConnection(to: endpoint) {
|
|
523
|
-
// ProtocolStackBuilder<ApplicationProtocol>
|
|
309
|
+
let relay = NetworkConnection(to: endpoint) {
|
|
310
|
+
TLS()
|
|
524
311
|
}
|
|
525
312
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
After constructing a concrete connection for your app's protocol stack, install
|
|
531
|
-
state handlers and start it:
|
|
532
|
-
|
|
533
|
-
```swift
|
|
534
|
-
func startConnection<ApplicationProtocol>(
|
|
535
|
-
_ connection: NetworkConnection<ApplicationProtocol>
|
|
536
|
-
) where ApplicationProtocol: NetworkProtocolOptions {
|
|
537
|
-
connection.onStateUpdate { state in
|
|
313
|
+
func logStates(of connection: NetworkConnection<TLS>) {
|
|
314
|
+
connection.onStateUpdate { _, state in
|
|
538
315
|
switch state {
|
|
539
|
-
case .ready:
|
|
540
|
-
|
|
541
|
-
case .
|
|
542
|
-
|
|
543
|
-
case .cancelled:
|
|
544
|
-
print("Cancelled")
|
|
545
|
-
default:
|
|
546
|
-
break
|
|
316
|
+
case .ready: Logger.net.info("ready")
|
|
317
|
+
case .failed(let error): Logger.net.error("failed: \(error.localizedDescription)")
|
|
318
|
+
case .cancelled: Logger.net.info("cancelled")
|
|
319
|
+
default: break
|
|
547
320
|
}
|
|
548
321
|
}
|
|
549
|
-
|
|
550
|
-
connection.start()
|
|
551
322
|
}
|
|
323
|
+
|
|
324
|
+
logStates(of: relay)
|
|
325
|
+
try await relay.send(Data("status".utf8))
|
|
552
326
|
```
|
|
553
327
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
the
|
|
328
|
+
The stack can also be built separately and passed with
|
|
329
|
+
`NetworkConnection(to:using:)`. The connection establishes itself when it is
|
|
330
|
+
first used, so the first `send` or `receive` drives it to `ready`.
|
|
331
|
+
|
|
332
|
+
Keep HTTP on URLSession; `NetworkConnection` is for custom protocol stacks
|
|
333
|
+
and QUIC transport.
|
|
557
334
|
|
|
558
335
|
### QUIC Multiplexed Streams
|
|
559
336
|
|
|
560
|
-
`NetworkConnection<QUIC>`
|
|
561
|
-
Opening and accepting streams are
|
|
337
|
+
A `NetworkConnection<QUIC>` carries many independent QUIC streams over one
|
|
338
|
+
connection. Opening and accepting streams are both `async throws`. Here `quic` is such a
|
|
339
|
+
connection:
|
|
562
340
|
|
|
563
341
|
```swift
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
) async throws -> QUIC.Stream<QUICStream> {
|
|
567
|
-
try await connection.openStream(directionality: .bidirectional)
|
|
568
|
-
}
|
|
342
|
+
let outbound = try await quic.openStream(directionality: .bidirectional)
|
|
343
|
+
try await outbound.send(Data("subscribe prices".utf8))
|
|
569
344
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
)
|
|
573
|
-
try await connection.inboundStreams { stream in
|
|
574
|
-
// Process each incoming QUIC stream.
|
|
575
|
-
}
|
|
345
|
+
try await quic.inboundStreams { stream in
|
|
346
|
+
let chunk = try await stream.receive(exactly: 64).content
|
|
347
|
+
route(chunk)
|
|
576
348
|
}
|
|
577
349
|
```
|
|
578
350
|
|
|
351
|
+
`openStream` returns a `QUIC.Stream`; `inboundStreams` calls the closure for
|
|
352
|
+
every stream the peer opens. Treat both as fallible suspension points, never
|
|
353
|
+
as synchronous callbacks.
|
|
354
|
+
|
|
579
355
|
### Migration Guidance
|
|
580
356
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
-
|
|
585
|
-
|
|
357
|
+
- New low-level code that can require iOS 26: evaluate `NetworkConnection`
|
|
358
|
+
first for typed stacks, first-class QUIC streams and `Sendable`-aware
|
|
359
|
+
design.
|
|
360
|
+
- Apps that still support earlier releases keep `NWConnection`. It is not
|
|
361
|
+
deprecated, and the two can live in the same app.
|
|
586
362
|
|
|
587
|
-
|
|
588
|
-
Both APIs coexist and `NWConnection` is not deprecated.
|
|
363
|
+
The snippets log through `extension Logger { static let net = Logger(subsystem: "app.networking", category: "net") }`.
|