Keentune
iOS development, oriented
10 chapters
·
about 16 min read
·
free
What makes iOS hard is not the size of the frameworks. It is five decisions underneath them that everything else obeys: values are copied unless you ask for sharing, absence is written into the type, memory is freed by counting references rather than by a collector, one thread alone may touch the interface, and the interface is a description you hand over rather than an object you keep. Learn those and the frameworks stop feeling arbitrary. This guide is about them, and the instinct each one corrects.
Each chapter opens with the short version. Tap one to read the detail.
A view is a value you describe, not an object you hold
~2 min
Two instincts to unlearn: that assigning a value shares it, and that a view is a thing you keep and mutate. Structs copy; SwiftUI builds your view, reads body, throws it away.
State is the only input: identity decides what survives, motion included
~2 min
The trap is thinking @State is a local variable belonging to the struct. The storage is the framework's, keyed to the view's *identity* — which is why state survives a rebuild but vanishes when identity changes, and why animation belongs to the change.
Layout is a negotiation, and a row is temporary
~2 min
The common assumption is that a parent sets its child's size and that .frame resizes a view. Neither is true: the parent proposes, the child chooses, the parent places. In a scroll view the proposal is unlimited and rows are reused.
Navigation is data
~2 min
You do not push a screen; you push a *value*, and navigationDestination(for:) maps that type to a view, so the route becomes state you can save and deep-link into. Two documented traps: the stack must see the destination, and dismiss acts where you declared it.
One thread owns the interface, and nothing collects your garbage
~3 min
Two beliefs cost the most here: that the interface can be updated from anywhere, and that reference counting means no leaks. The interface belongs to the main thread, and Swift encodes that as @MainActor; a reference cycle never reaches zero and simply stays.
Data in, and where it comes to rest
~2 min
A 404 is a successful transfer — only transport failures throw, so a try that did not throw proves nothing about the server. And an optional is not a nullable variable: absence is in the type, so ! is a crash you chose in advance.
The UIKit layer underneath, and the seam between them
~2 min
You will still meet UIKit — in a wrapped control, an older screen, a crash report. The recurring mistake is assuming its lifecycle methods run when you expect: loading once, layout repeatedly. Layout here is a constraint solver, not a proposal.
The settings you did not choose, and an interface that survives them
~2 min
Standard controls come labelled, so the assumption that you are done is easy to reach. What the framework cannot infer is grouping and meaning — and the grouping modifier hides children by default.
Proving it works, and getting it onto a device
~2 min
Two comfortable mistakes: treating accessibility identifiers as decoration, and trusting a suite that passes only because its timeouts are generous. Shipping is a separate system, where a provisioning profile authorises your code to run.
Your app is a guest in a process the system can end
~2 min
The instinct to lose first is that a backgrounded app keeps running. It is suspended within seconds, and a low-memory warning is the last notice before termination. Where the user was has to survive as data.
See the full iOS Development curriculum
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC