| name | ios-essentials |
| description | Cross-cutting Swift 6 / iOS 26 fundamentals no feature skill owns: Swift language (optionals, generics, protocols, Codable), Swift Concurrency (async/await, actors, Sendable, MainActor), GCD migration, thread-safety / data-race debugging, observability (os.Logger, MetricKit), URLSession, ObjC interop, deprecated-API registry; plus frameworks with no dedicated skill: APNs/silent push, notifications, AlarmKit, Vision, SpeechAnalyzer, MapKit, AVAudioEngine, SharePlay, CallKit, tvOS focus, Swift/C++ interop. Use for Swift/iOS work that is NOT SwiftUI layout, UIKit, architecture, testing, release, ASO, or App Intents — "why does this deadlock", "convert to async/await", "replace this deprecated API", "set up silent push". |
iOS Essentials — Swift 6 / iOS 26 Cross-Cutting Fundamentals
The language, concurrency, observability, networking, and migration knowledge
that underpins every iOS task. This skill is the catch-all: when a request
is iOS/Swift but doesn't belong to a specific feature skill (SwiftUI layout,
UIKit views, architecture patterns, testing, release, ASO, design), it belongs
here.
Target era: Swift 6.x (strict concurrency), iOS/iPadOS/macOS 26, Xcode 26.
Default to modern APIs and the @Observable / async-await / actor world.
Treat ObservableObject + @Published, DispatchQueue.main.async for UI,
NavigationView, and try! in production paths as legacy patterns to replace
(see references/deprecated-api-registry.md).
When this skill applies vs. when to defer
| Request | Use this skill? |
|---|
| "Explain / fix this Swift optional, generic, protocol, closure, enum" | ✅ Yes |
| "Convert this completion handler to async/await" / "migrate GCD to actors" | ✅ Yes |
| "Why does this deadlock / data-race / crash under Thread Sanitizer" | ✅ Yes |
| "My error disappears in production" / "add os.Logger / crash reporting" | ✅ Yes |
| "Validate this URLSession response" / "which persistence should I use" | ✅ Yes |
| "Bridge this Objective-C class to Swift" | ✅ Yes |
| "Replace this deprecated iOS 17 API with the iOS 26 one" | ✅ Yes |
| Build a SwiftUI screen / layout / animation | → defer to swiftui-ui |
| UIViewController / Auto Layout / diffable data source | → defer to uikit |
| Pick MVVM/VIPER/TCA, structure modules | → defer to ios-app-architecture |
| Reduce cognitive complexity / clean-code refactor | → defer to swift-code-cleanup |
| Build / boot / drive / screenshot in the Simulator | → defer to simulator-automation |
| On-device LLM / Foundation Models | → defer to the foundation-models skill |
| App Intents / Siri / Shortcuts / Spotlight / Control Center controls | → defer to the app-intents skill |
| App Store submission / TestFlight / metadata | → defer to the release / ASO skills |
| Push, local notifications, AlarmKit, Vision, Speech, MapKit, AVAudioEngine, SharePlay, tvOS focus, C++ interop | ✅ Yes — see §10 references |
If a task spans both (e.g. "make this SwiftUI view thread-safe and log its
network errors"), handle the language/concurrency/logging half here and let the
feature skill own the view code.
1. Swift language essentials
Full reference: references/swift-language.md —
optionals, error handling, Result, value-vs-reference semantics,
protocol-oriented design, some/any, generics (where), closures and capture
lists, enums, property wrappers, Codable, all in depth. Quick rules:
- Optionals: unwrap deliberately (
if let / guard let / ??); !/try!
only for genuine programmer errors that should crash in development.
- Errors: throw typed error enums; Swift 6 typed throws gives exhaustive
catch with no cast. Never empty catch, print-only catch, or try? on
network/auth/persistence/payment — those make failures invisible (§4).
Result for legacy callbacks / stored success-or-failure; a continuation
bridges it into async/await (§3). Prefer async throws for new code.
- Value vs reference: prefer
struct (models/DTOs) and enum (finite
state); final class/actor only for identity or shared mutable state.
let by default.
2. Swift Concurrency (async/await, actors, Sendable)
Full reference: references/concurrency.md —
mental model, async let / TaskGroup fan-out (incl. throttling), actors and
reentrancy, @MainActor, Sendable, Task lifetime + cooperative
cancellation. §3 covers GCD coexistence. Quick rules:
- Never
DispatchQueue.main.async in new code — @MainActor / MainActor.run.
- Shared mutable state lives in an
actor; a mutable class is not
Sendable (actor / immutable / documented @unchecked + lock).
- Reentrancy: an actor interleaves at every
await — re-check invariants.
- Cancellation is cooperative — check
Task.isCancelled /
checkCancellation(); in SwiftUI prefer .task {} (auto-cancels).
- A
Task {} that swallows a throw is a top production bug — wrap throwing
work in do/catch + logging, distinguishing CancellationError (§4).
- Never
DispatchSemaphore.wait() in async code. Build with
SWIFT_STRICT_CONCURRENCY = complete.
3. GCD & OperationQueue — coexistence and migration
Full reference: references/gcd-concurrency.md.
Apple has NOT deprecated GCD. It remains correct for parallel compute, system
I/O (DispatchSource, DispatchIO), and performance-critical paths. Use Swift
Concurrency for app-level async flow and shared-state protection; keep GCD where
it's genuinely better; bridge cleanly at the boundary.
Migrate: DispatchQueue.main.async{ui} → @MainActor; serial-queue-for-state
→ actor; DispatchGroup fan-out → TaskGroup/async let; completion handlers
→ withCheckedThrowingContinuation. Keep as GCD: concurrentPerform,
DispatchIO, DispatchSource timers, concurrent+barrier reader-writer.
Quick rules (full detail in the reference)
- Bridge callbacks with
withCheckedThrowingContinuation — resume exactly
once on every path (twice = crash, never = leak).
- Deadlock #1:
main.sync from main, or nested sync on the same serial
queue; assert with dispatchPrecondition(condition: .onQueue(.main)).
- Locks:
Mutex (iOS 18) / OSAllocatedUnfairLock (iOS 16+) / NSLock. Never
bare os_unfair_lock as a stored property (memory corruption); never
DispatchSemaphore as a mutex (no priority donation → inversion).
- Thread explosion: throttle blocking fan-out with
OperationQueue.maxConcurrentOperationCount; barriers only on CUSTOM
concurrent queues, never global(). TSan on every concurrency change.
See references/gcd-concurrency.md for the five
deadlock patterns, DispatchGroup/DispatchWorkItem/DispatchSource primitives,
the AsyncOperation KVO base class, and the full confidence checklist.
4. Production observability — make failures visible
Full reference: references/observability.md —
os.Logger setup, level + privacy-annotation tables, expected-vs-unexpected
triage, the silent-failure catalog and scan workflow, crash-SDK selection,
MetricKit, dSYM.
Most production errors don't crash — they vanish through try?, Task {},
.replaceError(), and print()-only catch blocks. AI-generated code is
systematically observability-blind because tutorial code is. Three non-negotiable
rules:
- No
print() in production code — os.Logger with privacy annotations
(dynamic strings are redacted by default — annotate every interpolation).
.info is not persisted to disk; use .notice for operational events.
- No catch block without observability — log every caught error;
unexpected errors (5xx, decoding, validation) also hit the crash SDK via
recordNonFatal; expected ones (401/408/429/offline/cancellation) get
breadcrumbs only — Crashlytics caps non-fatals at 8/session.
- No
try? where failure matters — use do/catch for network, persistence,
auth, payment, and user-facing operations.
Ship one fatal crash reporter (Sentry OR Crashlytics, never both), upload
dSYMs (DWARF with dSYM File on Release), and add MetricKit for OOM / watchdog
/ hangs (process pastDiagnosticPayloads at launch).
5. Networking with URLSession (async-first)
Use URLSession.shared.data(for:) with a generic Decodable fetch. The rules:
- Always validate
httpResponse.statusCode. URLSession…data(…) only throws
for transport failures — a 404/500 returns successfully with an error
body. Unchecked status is a classic silent failure (guard
200..<300 ~= http.statusCode).
URLSession.shared for simple cases; a configured session for auth headers,
caching policy, background transfers (.background(withIdentifier:)).
- Retry transient failures (408/429/503/offline) with backoff; breadcrumb each
attempt,
recordNonFatal only after all retries exhausted.
- Don't ship API keys in the client — proxy secrets through a backend.
For the copy-paste networking recipes — withRetry (Retry-After doesn't consume
backoff, no sleep after the last attempt, decorrelated jitter), the
URLSessionConfiguration knob table, NetworkSessionFactory (one session per
config type), a CRLF-correct MultipartFormData builder, and NWPathMonitor —
defer to ios-app-architecture's references/networking.md. For lower-level
sockets (NetworkConnection/Coder, iOS 26), AccessorySetupKit, CallKit/PushKit,
and the ATS-vs-Network.framework diagnostic, see
references/connectivity-frameworks.md.
6. Persistence — pick the right tool
| Need | Use | Notes |
|---|
| Small key-value, prefs | UserDefaults / @AppStorage | < ~1 MB, not for secrets |
| Secrets (tokens, passwords) | Keychain | never UserDefaults/plist for credentials |
| Files / blobs | FileManager (app sandbox) | use .cachesDirectory for re-derivable data |
| Structured app data, iOS 17+ | SwiftData (@Model) | modern default; CloudKit sync via ModelConfiguration |
| Large/complex object graph or legacy | Core Data | when you need fine-grained control or migration history |
| Lightweight Codable snapshot | Codable + JSONEncoder → file | simplest for app-state caches |
@AppStorage("key") is UserDefaults-backed; Keychain via a wrapper or the Security
framework. Persistence writes that matter must use do/catch (a
try? context.save() that silently fails loses user data — see the
NSDetailedErrorsKey unwrap in observability.md). For SwiftData/Core Data modeling
specifics defer to the architecture skill; this skill owns the selection decision
and the error-handling discipline.
7. Objective-C ↔ Swift interop
The bridging header exposes OC to Swift (annotate nullability in the .h or
everything imports as crash-prone String!); @objc/NSObject + the generated
<Module>-Swift.h exposes Swift to OC (generics, structs, associated-value
enums are invisible — provide shims); migrate file-by-file preserving runtime
behavior exactly; OC NSException is not catchable by Swift do/catch —
wrap risky OC calls in an OC shim converting to NSError.
For these concepts in full plus the concrete name-mangling traps that surface
only as compile errors (@property vs zero-arg method parens,
NS_SWIFT_NAME/is-prefix renames, typeWithX: label merging, @objc selector
pinning, failable-init optionals, KVO bridging), the file-by-file migration
mechanics (two import directions, the umbrella-adapter-header trick, pbxproj
surgery), and the behavior-changing lazy-getter side-effect-hoisting hazard, see
references/oc-to-swift-bridging-traps.md.
8. Xcode tooling & debugging quick reference
-
Instruments first, optimize second (Time Profiler, Allocations & Leaks,
Energy, os_signpost); Sanitizers (Thread / Address) + Main Thread Checker
in the scheme's Diagnostics tab; log streaming via
log stream --predicate 'subsystem == "com.you.app"'.
-
Build settings that matter here: SWIFT_STRICT_CONCURRENCY = complete,
DEBUG_INFORMATION_FORMAT = DWARF with dSYM File (all configs),
swiftLanguageMode(.v6).
-
Retain-cycle hunt: [weak self] in escaping/stored closures and repeating
timers; verify with the Memory Graph debugger.
-
Editing Swift outside Xcode (VS Code / Neovim / CI / agent box): sourcekit-lsp
is silent-wrong unless xcode-select points at Xcode.app, you invoke the
Xcode-bundled binary, pass COMPILER_INDEX_STORE_ENABLE=YES, and generate
buildServer.json with xcode-build-server. The CLT-only fallback
(swiftc -typecheck against the macOS SDK), Xcode 26's on-disk Apple-authored
LLM docs, and the Swift-spec/DocC-JSON fetch recipes:
references/headless-tooling.md.
-
Hand-editing .pbxproj (only for projects you can't regenerate — prefer
XcodeGen otherwise): run the PBXFileSystemSynchronized detection gate FIRST, then
see references/pbxproj-hand-edit.md.
For driving the simulator, capturing screenshots, and the full headless
build-run-debug loop, defer to simulator-automation.
9. iOS 26 / Swift 6 deprecated-API migration registry
The single most common review finding is stale-era code. Replace on sight:
ObservableObject+@Published → @Observable; @StateObject → @State;
NavigationView → NavigationStack; completion handlers → async/await;
try! / print(error) / try? → do/catch + Logger + crash report;
.animation(anim) → .animation(anim, value:). The full table — including
the SwiftUI-modifier, Foundation/formatting, SiriKit→App-Intents, and
@Entry/@Previewable rows — is in
references/deprecated-api-registry.md.
10. Platform & system-framework references (load on demand)
Cross-cutting framework knowledge that doesn't belong to a dedicated feature skill —
each is a separate reference so the catch-all stays lean. Reach for these when a
task touches the framework; most of the value is in the silent-failure / crash
gotchas (a missing Info.plist key, a wrong entitlement, a synchronous call on the
main thread).
| Topic | When | Reference |
|---|
| Silent / background push (APNs) | aps-environment errors, content-available:1, silent push → widget refresh | references/silent-push-apns.md |
| Local notifications | 64-pending cap, timezone reschedule, engagement-gated prompt, outbound-URL allowlist | references/local-notifications.md |
| AlarmKit (iOS 26) | system alarms/timers that override DnD; authorizationState, NSAlarmKitUsageDescription, Widget-Extension requirement | references/alarmkit.md |
| MapKit geocoding (iOS 26) | async MKGeocodingRequest, PlaceDescriptor (GeoToolbox) | references/mapkit-geocoding.md |
| AVAudioEngine recording | node-lifecycle / tap / format required condition is false crashes | references/media-frameworks.md |
| Vision (iOS 18/26) | async ImageRequestHandler, document/OCR/pose/segmentation, perform()-blocks-main + CVPixelBuffer-across-threads crashes | references/vision.md |
| Speech transcription (iOS 26) | SpeechAnalyzer/SpeechTranscriber, volatile-vs-final dedup, reserved-locale limit, simulator fallback | references/speech-transcription.md |
| On-device NaturalLanguage | NLEmbedding silent-corruption (sentence vs word OOV), language guard, canonical subtag | references/on-device-nl.md |
| SharePlay / GroupActivities | shared sessions, the session.join()+retention trap, prepareForActivation(), visionOS spatial | references/shareplay-groupactivities.md |
| Connectivity frameworks | Network.framework (NetworkConnection/Coder), AccessorySetupKit, CallKit/PushKit, ATS-vs-Network.framework diagnostic | references/connectivity-frameworks.md |
| tvOS focus engine | Apple TV spatial navigation, the White Bubble, .focusSection(), @FocusState timing | references/tvos-focus.md |
| Swift / C++ interop | SWIFT_SHARED_REFERENCE, std::span/std::string/cv::Mat bridging, lifetime annotations | references/swift-cxx-interop.md |
App Intents / Siri / Shortcuts / Spotlight / Visual Intelligence / Control
Center controls are a distinct shipping framework, not a cross-cutting
fundamental — defer to the dedicated app-intents skill.
References
Core references are linked from their sections above:
swift-language.md (§1),
concurrency.md (§2),
gcd-concurrency.md (§3),
observability.md (§4 — incl. PII compliance),
oc-to-swift-bridging-traps.md (§7),
headless-tooling.md and
pbxproj-hand-edit.md (§8 —
synchronized-folder gate, source-file wiring, plutil -lint repair),
deprecated-api-registry.md (§9).
Platform & system-framework references (push, local-notifications, AlarmKit,
MapKit, AVAudioEngine, Vision, Speech, NaturalLanguage, SharePlay, connectivity,
tvOS focus, C++ interop) are listed in the §10 table above.