Everything the adaptive question bank can teach and test in iOS Development, from foundations through advanced practice. Work through it in order, or start practicing and let the questions find your level.
The framework owns startup, so there is no place to run code before the app exists. Anything that used to happen first now belongs in an initializer or a scene-phase reaction.
On a platform that allows a second window, each one gets its own copy, so state that should be shared has to live outside the view. This is invisible on a phone and immediate on iPad.
The intermediate phase is the last point at which work reliably completes, because what follows may be suspended before it finishes. Saving there rather than later is the whole reason to observe it.
Timers stop, network calls do not complete, and nothing resumes until the user comes back. Assuming background time is the root of most missing-data bugs.
Some system callbacks have no declarative equivalent, so the adaptor keeps a route to them. It is a bridge rather than a fallback, and it is the supported way to receive them.
Because the system expects the previous screen back after a termination it caused, UI state has to be serializable. That constraint shapes navigation design far more than it first appears.
B. The SwiftUI view: a description, not an object
Since instances are created and discarded constantly, holding a reference to one is meaningless. Anything that must persist belongs in framework-managed storage, not in the struct.
It may be evaluated repeatedly and at unpredictable times, so a side effect inside it runs an unpredictable number of times. Effects belong in a lifecycle modifier.
Because the protocol is isolated, anything reachable from an update is already main-thread work. Moving work off it requires leaving that isolation deliberately.
Each call produces a new view containing the previous one, so nothing is mutated and order becomes structural. That is why the chain reads outward-in rather than as a property list.
The wrapping order decides what each modifier sees, so two chains with the same modifiers can produce different results. When a modifier seems not to apply, check its position first.
Values propagate only into the subtree where they were set, so placing one too high or too low changes which views receive it. Siblings never see each other's values.
Both layer a second view relative to the first and take their size from it, which is the opposite of a stack. Using them to size something is the common mistake.
Choosing edges controls where space is added, which changes layout and touch targets rather than only appearance. The shorthand is a real decision, not a formatting nicety.
It exists to apply something to several views at once, without introducing arrangement. Reaching for it expecting a stack produces overlapping content.
C. State and data flow
Copying a value gives you two that can disagree, and nothing reconciles them. Passing a reference to the one owner is what keeps them from diverging.
The value lives outside the struct that declares it, which is why it survives the constant rebuilding. Understanding that explains why state persists while views do not.
A binding gives the child write access to the parent's value; a plain copy gives it something to modify that nobody reads. The edit appears to work and then vanishes.
The newer mechanism tracks reads automatically rather than requiring each property to be marked, which is both less code and different behavior. It is a replacement, not a wrapper.
Only the properties actually read during an update participate, so an unrelated change no longer triggers a rebuild. This is the main practical gain over the older approach.
The view that creates the model needs somewhere for it to live between rebuilds; one handed in already has an owner. Wrapping a passed-in model creates a second copy.
One creates the object once for the view's lifetime and the other re-evaluates its initializer on every parent update. The symptom is state that resets for no visible reason.
Because the lookup happens as the view is built, a missing injection is a crash rather than a compile error. Testing every path that presents the view is the only way to find it.
It produces writable bindings from a model the view did not create, which is what makes a passed-in model editable. It is the counterpart to owning the model.
Tying an effect to a specific value means it runs when that value changes, not on every rebuild. Putting the same work in the body would run it unpredictably often.
Treating keyboard focus as readable, writable state lets it be moved programmatically and restored. It is the mechanism behind advancing between fields.
D. View identity, lifetime and the async hooks
Without an explicit identifier, a view is identified by where it sits in the tree, so moving it is a different view arriving. Most surprising state loss traces back to this.
Changing the identifier is treated as one view leaving and another arriving, so its state is discarded. That makes it a deliberate reset tool as well as an accidental one.
The two arms are separate views structurally, so switching between them does not carry state across. Anything that must survive the switch has to be owned above it.
If identifiers repeat or shift with position, the framework matches the wrong rows, which corrupts both state and animation. Index-based identity breaks the moment the collection changes.
It can fire more than once and, in a lazy container, fires as content scrolls into view. Treating it as one-time setup produces duplicated work.
Work started this way is canceled automatically when the view goes away, which removes a whole class of leak. Starting the same work manually leaves you responsible for stopping it.
E. The layout system
Layout is a negotiation rather than an assignment, which is why a parent cannot simply dictate a size. Understanding the three steps explains nearly every surprising layout.
The modifier inserts a container that makes a proposal and then aligns whatever the child returns, so a child that ignores the proposal keeps its own size. The frame is not a resize.
Different proposals elicit different answers from the same view, which is how ideal sizes and fill behavior are discovered. The proposal is a question, not an instruction.
Fixed-size children are satisfied before the remainder is divided, so adding one rigid child changes what everything else receives. Sizing problems usually start with the rigid one.
It claims whatever remains along the axis, so a single one pushes everything to the far end. Two of them divide the remainder, which is how centering is achieved.
It takes the whole proposal rather than sizing to its content, and aligns to the top leading corner. Both behaviors surprise people expecting it to be transparent.
It asks the child for the size it would ideally take and refuses to propose less, which is the fix for truncated text inside a tight stack.
Content is inset automatically unless it opts out, and the keyboard appearing is itself a change to that inset. Layout that jumps when typing is usually this.
Choosing the container chooses the axis, and layering is a third option that arranges nothing. Picking the wrong one produces content in the right order and the wrong place.
The alignment parameter positions children across the stack rather than along it, which is the opposite of what the name suggests to most people on first reading.
Spacing separates children from each other while padding surrounds the group, so they solve different problems. Using one for the other produces uneven results.
Defining a shared guide lets views in different containers line up, which ordinary alignment cannot do because it only relates siblings.
F. Lists, scrolling and render cost
Only rows near the visible area are created, so a large collection stays affordable. A plain stack builds everything immediately, which is why it does not scale.
Deferring creation costs a little complexity and saves an unbounded amount of work. For small fixed content the eager version is simpler and correct.
One provides the standard edit behavior and the other is a general mechanism available on either edge. The general one is what you need for anything beyond deletion.
Because it offers unlimited space along its axis, a child that expects to be constrained will size wrongly. Children must be self-sizing to behave.
A row body is re-evaluated as content scrolls, so formatting or decoding inside it runs repeatedly. Moving that work outside the body is the standard scrolling fix.
Grouping changes the structure assistive technology reports as well as the appearance, so it carries meaning rather than styling. Visual grouping alone does not.
G. Navigation and presentation
Pushing a value and mapping its type to a destination decouples the link from the screen, which is what allows navigation to be driven from data. The link no longer names a view.
Once the path is a bound collection, navigation can be set programmatically, restored after termination and reached from a link. All three come from the same change.
Registration has to be visible to the stack, so placing it on the pushed screen silently does nothing. The failure is a link that does not navigate, with no error.
One can be dismissed by dragging and the other cannot, so the second must provide its own exit. Choosing it without adding one traps the user.
Reading the dismiss action means the presented view closes itself without the presenter passing anything down, which keeps the two from having to coordinate.
A URL has to be translated into navigation values, which is only possible if navigation is data. An imperative navigation design cannot accept a deep link.
Sections here are peers with no ordering, unlike a stack where each screen came from the one before it. Using a stack for peers, or tabs for a sequence, fights the model.
H. Animation and gestures
There is no command to animate a view; you change state and describe how the interpolation should happen. Looking for an imperative call is the wrong mental model.
What is scoped here is the MUTATION rather than any particular view, so a state write made in that closure animates wherever it is observed — including screens you were not thinking about.
Tying the animation to one value stops unrelated state changes from animating, which is the fix when things move that should not.
A transition describes how a view enters or leaves, but something must animate the change that causes it. Specifying only the transition produces no motion.
Pairing two views by namespace and identifier makes the framework treat them as one element moving, which produces a continuous transition between separate hierarchies.
When gestures overlap, the resolution has to be declared rather than inferred from nesting. Without it, the outer or inner gesture wins unpredictably.
I. UIKit: view controllers and the responder chain
It is the one-time setup point, so anything that must reflect the current state on each visit is in the wrong place there. The symptom is stale content on a second visit.
One runs before the transition and the other after it completes, which decides whether work is visible during the animation. Analytics belong in the later one, layout preparation in the earlier.
Frames are valid here and it can run many times, so it is the right place to read geometry and the wrong place to do expensive work or to modify state.
Connections are established when the hierarchy loads, so touching one earlier finds nothing. Initializer code that configures subviews always crashes for this reason.
The sequence exists so the child knows when it has been added, and omitting the final step leaves it without lifecycle callbacks. Adding only the view is the half-done version.
An unhandled event travels a defined path upward, which is what allows a distant object to respond. Knowing the path is how you work out where a handler can live.
Leaving the target unset sends the action along that chain to whoever implements it, which decouples a control from any particular object.
The superclass does real work in these methods, so skipping the call leaves the object partially configured. Failures appear later and far from the omission.
One object asks another for decisions and reports events to it, without owning it. It is the pattern behind most customization points in the framework.
Presenting interrupts to cover the screen; pushing continues a sequence the user can walk back through. The choice tells the user whether they are still on the same path.
A container owning an ordered stack, with the push and pop transitions and the back affordance that come with it. Order is the model.
A container owning peers, where nothing came before anything else. Placing sequential content here removes the user's sense of where they are.
Visual editing against reviewable text: one is faster to arrange and harder to merge and diff, the other the reverse. It is a team trade-off rather than a technical one.
J. Auto Layout
Every constraint is the same linear relation with a relation type and a priority, so the engine is solving a system rather than following instructions. Reading them that way makes conflicts explicable.
Too few constraints leaves a position undetermined; too many that disagree cannot all hold. The two produce different symptoms and different console messages.
When constraints conflict the engine drops the weakest and reports which, so the log names the constraint it abandoned. Reading that line is usually the whole diagnosis.
One resists growing beyond the content and the other resists shrinking below it, so they govern opposite directions. Which to raise follows from which way the view is being distorted.
A view that knows its own content size needs fewer constraints, which is why a label positions with two and a plain view needs four. Fighting it produces ambiguity.
Leaving the flag set generates constraints from the frame that conflict with the ones you wrote, producing unsatisfiable output immediately. It is the first thing to check.
Constraining to the guide keeps content clear of system interface elements, which vary by device. Using the superview edge works on one device and clips on another.
K. Table and collection views
A recycled cell arrives holding the previous row's values, so every field must be set on every configuration. Anything conditionally set is where stale content appears.
It is the point to clear state and cancel work belonging to the previous row. Without it, an in-flight request finishes against the wrong content.
A response arriving after the cell was recycled paints the wrong row, so the request must be canceled or the target re-checked before applying. It looks like a caching bug and is not.
Applying a snapshot of identifiers replaces manual index bookkeeping and produces the animation as a side effect. Whole classes of index-mismatch crashes disappear with it.
Automatic height requires an unbroken chain of vertical constraints through the content, so one missing constraint leaves the height undetermined and the row collapsed.
One supports arbitrary layouts and the other a single column of rows. Choosing the simpler one commits you to a shape that is expensive to leave later.
L. SwiftUI and UIKit interop
Creation happens once and updates happen whenever state changes, so anything done in the creation step is never repeated. Putting state-dependent configuration there means it never updates.
A value type cannot be a delegate, so the coordinator is the object that can. It is also where anything needing a stable identity across updates lives.
The bridge works in both directions, so a declarative view can sit inside an imperative hierarchy as an ordinary child. Adoption does not have to be all at once.
Changes made in the wrapped view reach the outer world only through a binding or a callback, because the wrapper struct is recreated constantly. Mutating it changes nothing.
Writing state during an update triggers another update, which can loop. The fix is to defer the write or to derive the value instead of storing it.
M. Concurrency and the main actor
Touching interface state off the main actor is undefined rather than merely risky, so a crash that appears occasionally is the same bug as one that never appears. Isolation is the fix.
Marking a type or function moves its work onto the main actor and lets the compiler check it, which replaces a dispatch call with a guarantee.
Other work proceeds while suspended, so anything read before the await may have changed by the time it resumes. Re-reading after the suspension is the habit.
Canceling sets a flag; work that never checks it continues to completion. A long loop must check explicitly or it cannot be canceled at all.
A task started from a view inherits main-actor isolation, so it does not move work off the main thread. Assuming otherwise is how the main thread is blocked while looking asynchronous.
Synchronous file, decoding or image work during an update misses the frame deadline, which the user perceives as a stutter. The profiler shows it as a hitch rather than a crash.
Serializing access to mutable state is what removes the race, at the cost of the access becoming asynchronous. The awaits are the price of the safety.
Concurrent read-modify-write corrupts memory rather than merely producing a stale value, so the consequences are unbounded and the reproduction is intermittent.
It owns the fetch, the placeholder and the cancellation, which removes the common failure of a response arriving after the view is gone.
N. Networking with URLSession
A server error is a successful transfer of an error response, so nothing throws. Code that only handles thrown errors treats every failure as a success.
The error identifies the key and the mismatch, so reading it is faster than inspecting the payload. Optionality and type mismatches are the two usual causes.
Percent-encoding is handled for you, which is what string concatenation gets wrong the moment a value contains a space or an ampersand. The corruption is silent.
A response may be served from the cache unless the policy says otherwise, so a changed server response can appear not to have changed. Testing against a stale cache misleads.
The system performs the transfer and can relaunch the app to deliver the result, which is what makes a large download survive the app being suspended.
Cleartext connections are refused unless an exception names the domain, so an endpoint that works in a browser fails in the app with no obvious cause.
It owns the transfer, the caching and the session configuration, and deliberately leaves interpretation of the response to you. Knowing the boundary prevents expecting it to validate.
Declaring a conversion strategy handles a naming convention across the whole payload, which removes the need to write out every key by hand.
O. Persistence: SwiftData, Core Data, defaults, Keychain and files
It is an unencrypted file inside the container, so anything stored there is readable by anyone with access to it. It is for preferences, never for credentials.
The chosen class decides whether an item is readable while locked and whether it moves to a new device, which is a security decision rather than a configuration detail.
Marking a class makes it both persistable and observable, so a fetched object can drive the interface directly without a separate presentation copy.
The container owns the store and the context accumulates pending changes, so nothing is durable until the context is saved. That separation is where unsaved-change bugs live.
The query re-runs when the store changes, which is why it belongs in a view. Placing it in a model gives you a snapshot that never updates.
An object belongs to the queue of its context, so passing it across threads is undefined. Identifiers cross safely; object references do not.
Adding is handled automatically, while renaming or restructuring needs an explicit mapping. Discovering the limit in production means users cannot open the app.
Each location has different guarantees about backup and survival, so choosing the wrong one either loses data or bloats the user's backup. The choice is a policy decision.
Conformance describes how a type maps to an external representation, and the mapping is where failure occurs. Most decoding bugs are mapping mismatches rather than transport problems.
The newer framework builds on the older one's engine with a different surface, so concepts transfer while the API does not. Knowing both explains constraints that otherwise look arbitrary.
A few values, a document, or a queryable graph with relationships are different problems. Size and the queries you need decide, not familiarity.
When two contexts save conflicting edits, something must decide the winner, and declaring it is what turns a crash into a defined outcome.
P. Images, memory and profiling
Memory is consumed by the pixel buffer, not the compressed file, so a highly compressed photograph can be enormous once decoded. File size is a misleading proxy.
Decoding directly to the size that will be displayed avoids materializing the full-resolution buffer at all, which is the difference between a scroll that stutters and one that does not.
An object holding a closure that strongly captures the object keeps it alive permanently. The tell is a deinitialiser that never runs.
The budget is the time between frames, so exceeding it drops one and the user sees it. Halving the refresh interval halves the budget, which is why newer displays are less forgiving.
A sampling profiler shows where time is actually spent, which is regularly not where it was assumed. Optimizing the suspected path first usually wastes the effort.
The system reclaims memory by terminating the app, so the outcome is a disappearance rather than a crash report. Responding to the warning is the only intervention available.
Q. Background execution, notifications and permissions
You request an opportunity and the system decides whether and when to grant it, based on usage patterns and device conditions. Designing as if it were a schedule guarantees disappointment.
Background work has a deadline and the handler is the last chance to save and stop cleanly. Ignoring it means the process is killed mid-operation.
One is scheduled on the device and needs no server; the other is delivered externally and needs a token and infrastructure. The choice is an architecture decision.
The prompt appears once per installation, so a denial can only be reversed in Settings. Asking at a moment the user does not understand spends the single opportunity.
A notification arriving while the app is open displays nothing unless the delegate opts in, which is why testing in the foreground shows nothing happening.
It is best-effort and rate-limited, so it cannot be relied on to wake the app at a particular moment. Treating it as a trigger for required work produces intermittent failure.
A missing usage description crashes the app the first time the protected API is used, so the failure surfaces only on the path that touches it. It is a build-configuration bug that presents as a runtime one.
One object handles permission, scheduling and responses, so the whole flow runs through it. Knowing that is what makes the API discoverable.
R. Accessibility
A control needs a name, its current value and an indication of what kind of control it is. Providing only the name leaves the user unable to tell what it does.
An image carrying no information should be hidden rather than described, because a label for it adds noise. Deciding which is which is an editorial judgment.
Merging a row means it is announced as one thing instead of several fragments, which is the difference between usable and exhausting. It also makes the row a single target.
Text scales with the user's setting, so a fixed height clips it at larger sizes. Testing only at the default size hides the problem entirely.
The minimum hit area is independent of how the control is drawn, so a small glyph still needs a large target. Expanding the tappable area costs nothing visually.
Meaning must survive being seen without color, so pairing it with text, shape or position is what makes it robust. This also helps in bright light and on poor screens.
The setting is a request from someone for whom motion causes discomfort, so substituting a fade is a commitment rather than a nicety.
Every accessibility API ultimately feeds it, so testing with it is the only way to know what the interface actually reports. Reading the code is not a substitute.
S. Testing and debugging
One runs inside the app and can reach any type; the other drives the app from outside and sees only what is on screen. What you can assert differs completely.
An asynchronous test can await the call directly, while an expectation exists for callback-based interfaces. Using an expectation for an awaitable call adds flakiness for nothing.
A test passing because the timeout is generous is already unreliable; it simply has not failed yet. The timeout hides the race rather than removing it.
Depending on an interface rather than a concrete type is what allows a test to substitute the network or the store. Without it the test needs the real thing.
Interface tests find elements by identifier, so identifiers are infrastructure that breaks tests when changed. Treating them as decoration makes the suite brittle.
Passing flags at launch lets a test establish starting conditions without reaching inside the app, which keeps the two processes properly separate.
T. Signing, capabilities and App Store distribution
One establishes who signed the build and the other what it may do and where it may run. Signing problems are almost always about which of the two is wrong.
Enabling a capability changes the identifier and therefore the profile, so it is never only a local setting. A build that suddenly fails to install is usually this.
One lists specific devices and the other does not, which is why a build that installs for you refuses elsewhere. The profile type is the first thing to check.
One is what users see and the other must increase with every upload of that version. Uploading a repeated build number is rejected before review begins.
External testing requires a review pass while internal testing does not, which changes how quickly a fix reaches testers. Planning around it avoids surprise delays.
Declarations must match what the app actually does, and a mismatch is grounds for rejection rather than a warning. The declaration is a commitment, not a form.
The most frequent rejection is a build that is not finished — placeholder content, dead links, or a crash on the reviewer's path. It is a preparation failure rather than a policy one.
Digital goods used within the app must be sold through the store's payment system, which is a business-model constraint rather than a technical one. Discovering it late is expensive.
Tracking across other companies' apps and sites requires the system prompt first, and the definition of tracking is broader than most teams assume.
Downloading executable code that changes reviewed behavior is not permitted, which constrains how remote configuration can work. Configuration is fine; new behavior is not.
U. Human Interface Guidelines expectations
Gestures and controls already carry meaning, so redefining one costs every user who learned it elsewhere. The cost is borne by them rather than by the platform.
A modal interrupts, so it should be short, single-purpose and obviously escapable. A deep hierarchy inside one is a sign it should not have been modal.
Destructive actions need either confirmation or an undo, and should never be the default choice. The default is what a distracted user activates.
Semantic colors follow appearance and contrast settings automatically; a fixed value does not, which is how a design breaks in dark mode without anyone changing it.
Adapting to size class, orientation and text size covers devices that do not exist yet. Checking a device name is a list that goes out of date immediately.
V. Framework awareness: which technology owns which job
A declarative stream API that predates structured concurrency and still underpins the older observation mechanism. Knowing it explains APIs you will still encounter.
Marking a property makes it notify subscribers on change, which is the mechanism the older observation model rides on. The rebuild is a subscription, not magic.
Two levels of access exist and the user decides which to grant, so the app must behave correctly with the narrower one. Requesting more does not guarantee more.
Maps, annotations and search available as a native view, so integrating a map does not require bridging. The declarative surface is the recent addition.
Capture, playback and editing for time-based media, which is a large framework because it covers the whole pipeline rather than one task.
Purchases, subscriptions and their transaction states, including the states that arrive later. Handling only the immediate success case is the common gap.
Views rendered outside the app on a timeline supplied in advance, which is why a widget cannot simply fetch on demand. The timeline is the model.
Describing actions to the system lets them be invoked by voice and automation without a separate interface. The app becomes something other software can call.
The layer tree is what is actually composited, so higher-level animation sits on top of it. Reaching down to it explains behavior that looks inexplicable above.
W. Xcode: targets, schemes and what the Simulator cannot tell you
A target defines a product and everything that goes into it, so questions about what is included are answered at this level.
A scheme decides which targets run for a given action and under which configuration, which is why two schemes can produce different behavior from one project.
Configuration decides optimization, assertions and what is stripped, so a bug that appears only in release is usually explained here.
There is no real hardware, so sensors, memory pressure, cellular conditions and performance are all unrepresentative. Passing there is not evidence about a device.
The store still delivers a slice appropriate to each device, while the intermediate representation submission has been retired. Knowing which is current avoids chasing a removed setting.
X. Structuring an app: layers, ownership and broadcast
It holds what a screen shows and what it can do, with no view types in it, which is what makes it testable without a running interface.
The data and the rules governing it, independent of any screen, so the same model serves several presentations. Screen-specific logic belongs elsewhere.
A shared instance is convenient and impossible to substitute in a test; a parameter is neither. The convenience is paid for later.
One-to-many messaging where the sender does not know the receivers, which is powerful and makes the flow of control hard to follow. Use it where the decoupling is genuinely wanted.
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
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.