Final Capstone — Building a Production Objective-C Application
Serve as the final synthesis lesson of the entire 80-module Objective-C course by walking end-to-end through a single required layered architecture — UI, Coordinator/Controller, ViewModel/Presentation, Service, Repository, and Persistence/Network — showing how essentially every feature and technique taught across the course (protocols, categories, class extensions, blocks, ARC, Foundation collections, JSON, networking, persistence, Keychain, authentication concepts, error handling, concurrency, testing, dependency injection, accessibility, performance profiling, logging, security, and Swift interoperability) fits together inside one coherent, production-oriented application.
This is the final lesson of the course: the capstone that ties together everything Modules 1 through 80 built up, from the very first C fundamentals through Objective-C's object model, memory management, Foundation, networking, persistence, concurrency, testing, interoperability, and the eight projects in Module 80. Rather than introducing new syntax, this lesson is pure synthesis — it takes a single required, layered architecture (UI down through Coordinator/Controller, ViewModel/Presentation, Service, Repository, to Persistence/Network) and walks it end-to-end, showing precisely which earlier lesson's techniques power each layer and why the layers are separated the way they are. If earlier lessons taught you individual tools, this lesson is about the discipline of assembling those tools into a codebase that a real engineering team could actually own, extend, test, and ship — which is, in the end, the entire point of learning Objective-C at production depth rather than as a collection of disconnected facts.
Layer 1 — UI: Views, Accessibility, and Nothing Else
The UI layer in this capstone's architecture — table/collection view cells, view controllers' view-lifecycle glue, and any custom UIView subclasses — is intentionally kept 'dumb': its only job is to render whatever the ViewModel/Presentation layer hands it and to forward user interaction (taps, text input) upward, without containing business logic, without knowing how data was fetched, and without knowing anything about networking, persistence, or auth. This is the practical antidote to the 'massive view controller' problem the course warned about repeatedly: when the UI layer's only responsibilities are display and forwarding, a view controller stays a few hundred lines no matter how complex the feature underneath it becomes.
Accessibility, covered in depth in the Accessibility lesson, lives here and only here: accessibilityLabel, accessibilityHint, accessibilityTraits, and dynamic-type-aware layout are all UI-layer concerns, because they describe how a screen is presented to an assistive technology, which has nothing to do with how data was fetched or stored. In this capstone, every UI element that presents ViewModel-provided data derives its accessibility label from the same formatted string the ViewModel already computed for display, so the two can never drift out of sync.
• UI reads formatted display state from the ViewModel and forwards user actions upward — no business logic, no networking, no persistence code. • Accessibility labels/hints/traits are set here, derived from ViewModel-provided formatted strings, exercising the Accessibility lesson. • Keeping the UI layer this thin is the direct fix for the 'massive view controller' anti-pattern flagged in the Mistakes section below.
Layer 2 — Coordinator / Controller: Navigation, Wiring, and Object Lifetimes
The Coordinator sits directly under the UI layer and owns exactly two things: navigation flow (which screen comes after which, and how they're presented — pushed, modally presented, dismissed) and the construction/wiring of each screen's dependencies (creating a ViewModel with its Service and Repository injected, then handing that ViewModel to a view controller). Nothing about business rules or data formatting belongs here — a Coordinator that starts accumulating if statements about what data means, rather than which screen to show, has quietly become a second view controller.
Coordinators in this capstone are Objective-C classes conforming to a small Coordinator protocol (- (void)start; and a weak parentCoordinator for child coordinators to report back to), which is the same protocol-based abstraction technique the Protocols lesson taught, applied to navigation rather than to a service. Blocks are used for the child-to-parent 'finished' callback (onFinished block property) rather than a delegate, which is a deliberate choice worth noticing: a delegate protocol would work too, but a single, focused, one-shot completion signal is exactly the case where the Blocks lesson recommended a block property over a full protocol — less ceremony for a narrower contract.
Retain-cycle discipline matters enormously here, because Coordinators, ViewModels, and view controllers all naturally want references to each other. The onFinished block captures self weakly (__weak typeof(self) weakSelf = self;) precisely because a strong capture would create a retain cycle between the parent coordinator and the child it owns strongly — the same weak-capture-in-blocks discipline from the ARC and Blocks lessons, now applied at the architecture level rather than in a single method.
• Owns navigation flow and dependency wiring only — never business logic or data formatting. • Coordinator protocol (start, parentCoordinator) is a direct application of the Protocols lesson to navigation. • Parent/child completion uses a block property (onFinished), not a delegate, per the Blocks lesson's guidance on narrow one-shot contracts. • Weak self capture in the onFinished block prevents a parent-coordinator/child-coordinator retain cycle, per the ARC and Blocks lessons.
Layer 3 — ViewModel / Presentation: Formatting, State, and Zero Networking Knowledge
The ViewModel is the layer that turns domain data into display-ready state — formatted strings, booleans for whether a spinner should show, arrays of cell-view-model structs — and it is deliberately forbidden from importing NSURLSession, Core Data, or the Keychain APIs directly. It depends only on a Service protocol (an abstraction, per the Protocols lesson), never on a concrete networking or persistence type. This is the single most important dependency-direction rule in the whole architecture, and it is precisely what the quiz below asks about: the ViewModel doesn't import NSURLSession because doing so would collapse the layering the whole architecture exists to provide, and it would make the ViewModel untestable without a real network connection.
Because the ViewModel depends on a ProfileServicing protocol rather than a concrete ProfileService class, a unit test can substitute an id<ProfileServicing> mock (per the Testing and Mocking and Dependency Injection lessons) that returns canned data synchronously, letting the whole presentation-logic surface be tested in milliseconds with no network, no disk, and no flakiness.
State exposed by the ViewModel is a small, explicit enum-like set of cases (loading / loaded / failed) modeled with a PXLoadState object plus associated values as properties — the same 'model state explicitly rather than with a pile of loose booleans' discipline covered when Foundation collections and model design were introduced earlier in the course.
• Depends on a Service *protocol*, never a concrete networking/persistence class — the architecture's core dependency-inversion rule. • Owns formatting, loading/error/empty state, and any presentation-only logic; owns zero networking, JSON, or persistence code. • Testable via protocol-conforming mocks (Testing / Mocking and DI lessons) with no real network or disk access. • Loading/loaded/failed state modeled explicitly as a small state object rather than several independent booleans.
Layer 4 — Service: Business Operations, Blocks, and NSURLSession
The Service layer is where an actual network call happens, using NSURLSession directly, and it is the layer that knows about HTTP, JSON decoding, and turning transport-level failures into domain-level errors. It is defined first as a protocol (ProfileServicing) and then implemented by a concrete class (ProfileService), so the ViewModel above it, and any test double below it, only ever see the protocol — the same protocol-first design used throughout the course whenever a class needed to be swappable or mockable.
JSON handling uses NSJSONSerialization, exactly as the JSON and Serialization lesson covered, and every completion block follows Cocoa's (result, error) convention with NSError populated on failure using a dedicated error domain and code, per the Error Handling lesson, rather than throwing generic errors or silently returning nil.
Concurrency inside the Service uses Grand Central Dispatch to guarantee its completion blocks are always invoked on the main queue (dispatch_async(dispatch_get_main_queue(), ...)), which is the same 'complete on main, work off main' convention the Concurrency/GCD lesson established, so callers (ViewModels) never have to think about which queue a callback lands on.
• Defined as a protocol first (ProfileServicing), implemented by a concrete NSURLSession-backed class — protocol-first design from the Protocols lesson. • JSON parsing via NSJSONSerialization, per the JSON and Serialization lesson. • Domain-specific NSError with its own error domain/code, per the Error Handling lesson, instead of leaking raw transport errors upward. • Completion blocks always dispatched back to the main queue, per the Concurrency/GCD lesson's convention.
Layer 5 — Repository: Composing Auth, Keychain, and Persistence Behind One Interface
The Repository sits below the Service (or, in flows that need stored credentials, is composed *by* the Service before it makes a request) and is responsible for combining multiple lower-level concerns — a Keychain-backed auth token store and a local persistence cache — behind a single, simple interface, so nothing above it needs to know that 'getting the current user' might mean checking a cache, then checking the Keychain for a token, then possibly triggering re-authentication.
The Keychain-backed piece uses SecItemCopyMatching/SecItemAdd/SecItemUpdate, wrapped in a small PXTokenStore class exactly as the Keychain lesson taught, exposing a simple currentToken/saveToken:/clearToken interface rather than leaking Security-framework C API calls into the rest of the app. Authentication concepts from the Authentication lesson — token expiry, the difference between an access token and a refresh token, and treating 'not authenticated' as a first-class state rather than an error — are modeled explicitly in the Repository's return type rather than being inferred from an HTTP status code deep in the Service.
The persistence half of the Repository is a lightweight on-disk cache (following the Persistence lesson's patterns), so a Repository method like currentUserProfile: can return a cached profile immediately while a network refresh happens in the background — the classic 'stale-while-revalidate' pattern that only works because the Repository, not the ViewModel or Service, is the one place that knows both the cache and the network exist.
• Composes a Keychain-backed PXTokenStore (Keychain lesson) with a local persistence cache (Persistence lesson) behind one interface. • Auth state (authenticated / token expired / unauthenticated) modeled explicitly, per the Authentication concepts lesson, not inferred ad hoc from HTTP status codes. • Enables stale-while-revalidate: return a cached value immediately while refreshing from the network in the background. • Is the only layer that knows both persistence and the auth token store exist — everything above it sees one simple interface.
Layer 6 — Persistence / Network: The Foundation Everything Else Rests On
At the bottom of the stack sit the two raw capabilities everything above composes: local persistence (in this capstone, a simple NSKeyedArchiver-based on-disk cache, per the Persistence lesson, though a real app might use Core Data here per the Core Data lessons and Module 80's Project 6) and networking (NSURLSession, per the URL and Networking lesson). These are deliberately the least 'clever' layers in the whole architecture — they do not know about users, profiles, or authentication; they know how to read and write bytes to disk and how to make HTTP requests and hand back raw data.
Foundation collections (NSArray, NSDictionary, NSData, with lightweight generics annotating their element types throughout, per the Lightweight Generics lesson) are the common currency every layer above passes data around in, which is what makes it possible for, say, the Repository's persistence cache and the Service's JSON decoding to hand off data to each other without any layer needing to know about the other's internal model types.
• Local persistence: NSKeyedArchiver-based cache (or Core Data, per the Core Data lessons), knows nothing about domain concepts like 'user' or 'auth'. • Networking: NSURLSession, per the URL and Networking lesson, hands back raw NSData/NSError to the Service layer above it. • Foundation collections with lightweight generics are the shared data currency passed between every layer.
The Six-Layer Dependency Rule
Depending on a protocol rather than a concrete class at each boundary (ViewModel → Service, Service → token provider) is what makes every layer replaceable with a test double. The rule is checked in code review the same way a lint rule would be: if a #import in ViewModel.m ever names NSURLSession or a Keychain header, that import itself is the defect, regardless of what the surrounding code does.
// Each arrow means "depends on / imports the type of", never the reverse:
//
// UI → ViewModel (concrete) [imports its header]
// Coordinator/Controller → ViewModel, Service, Repository [constructs + wires them]
// ViewModel/Presentation → id<ProfileServicing> [protocol only, NOT NSURLSession]
// Service → id<TokenProviding>, NSURLSession
// Repository → PXTokenStore (Keychain), PXProfileCache (persistence)
// Persistence/Network → Foundation only (NSURLSession, NSKeyedArchiver, Security.framework)
//
// A dependency arrow never points upward: Persistence/Network never imports
// Repository; Repository never imports Service; Service never imports ViewModel;
// ViewModel never imports UIKit view classes.Code Examples
ProfileCoordinator only ever constructs and wires objects (Service, ViewModel, view controller) and drives navigation — it contains no formatting or business logic. The child coordinator's onFinished block captures self and child weakly to avoid a retain cycle between a coordinator and the child it stores strongly in childCoordinators, the same weak-capture discipline the ARC and Blocks lessons taught applied at the architectural level.
__weak typeof(self) weakSelf = self; ... child.onFinished = ^{ typeof(self) strongSelf = weakSelf; ... };
Common mistake: Capturing self strongly inside child.onFinished (typeof(self) strongSelf = self; instead of going through weakSelf) would keep the parent coordinator alive as long as the child's block exists, and since the parent also holds the child strongly in childCoordinators, that pair of strong references never gets freed.
ProfileViewModel depends only on id<ProfileServicing> and never imports NSURLSession, a Keychain header, or a persistence class — every piece of state it exposes (state, displayName, accessibilitySummary) is derived purely from the profile the Service protocol handed it. Because the dependency is a protocol, a unit test constructs the ViewModel with a fake id<ProfileServicing> that calls its completion block synchronously with canned data, testing every branch of load without a network.
id<ProfileServicing> _service; // protocol only — never NSURLSession, never a Keychain type
ProfileService is the only layer permitted to import and use NSURLSession directly. It asks its injected id<ProfileRepositoring> for the current auth token (never touching the Keychain itself), builds the request, and turns both transport failures and malformed-JSON failures into a domain-specific NSError in the PXProfileServiceErrorDomain, always completing on the main queue.
NSString * _Nullable token = [_repository currentAuthToken];
ProfileRepository is the only object in the stack that knows both a Keychain-backed token store and a persistence cache exist; everything above it (the Service) sees a single simple protocol. PXTokenStore wraps the raw Security-framework C API (SecItemCopyMatching) exactly as the Keychain lesson demonstrated, so no other class in the app ever calls into Security.framework directly. Expected result: currentToken returns nil when no token is stored (never-authenticated or logged-out state) rather than throwing, letting ProfileService treat 'no token' as an ordinary, expected branch.
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
A category on NSDate (px_ prefixed to avoid collisions, per the Categories lesson's naming-convention rule) adds a small formatting helper the ViewModel layer uses without subclassing NSDate. A class extension on ProfileRepository exposes a readwrite property and a private helper method only inside the repository's own implementation file, keeping the public ProfileRepositoring protocol and ProfileRepository.h header free of implementation detail, exactly as the Class Extensions lesson taught.
• (NSString *)px_relativeDisplayString;
@protocol Coordinator <NSObject>
@property (nonatomic, weak, nullable) id<Coordinator> parentCoordinator;
@property (nonatomic, strong) NSMutableArray<id<Coordinator>> *childCoordinators;
- (void)start;
@end
NS_ASSUME_NONNULL_BEGIN
@interface ProfileCoordinator : NSObject <Coordinator>
@property (nonatomic, weak, nullable) id<Coordinator> parentCoordinator;
@property (nonatomic, strong) NSMutableArray<id<Coordinator>> *childCoordinators;
@property (nonatomic, strong) UINavigationController *navigationController;
@property (nonatomic, copy, nullable) void (^onFinished)(void);
- (instancetype)initWithNavigationController:(UINavigationController *)navigationController
repository:(id<ProfileRepositoring>)repository NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
@implementation ProfileCoordinator {
id<ProfileRepositoring> _repository;
}
- (instancetype)initWithNavigationController:(UINavigationController *)navigationController
repository:(id<ProfileRepositoring>)repository {
self = [super init];
if (self) {
_navigationController = navigationController;
_repository = reposit
…typedef NS_ENUM(NSInteger, PXLoadPhase) {
PXLoadPhaseIdle,
PXLoadPhaseLoading,
PXLoadPhaseLoaded,
PXLoadPhaseFailed
};
NS_ASSUME_NONNULL_BEGIN
@interface PXLoadState : NSObject
@property (nonatomic, readonly) PXLoadPhase phase;
@property (nonatomic, readonly, copy, nullable) NSString *errorMessage;
+ (instancetype)idle;
+ (instancetype)loading;
+ (instancetype)loaded;
+ (instancetype)failedWithMessage:(NSString *)message;
@end
@protocol ProfileViewModelDelegate <NSObject>
- (void)profileViewModelStateDidChange:(id)viewModel;
@end
@interface ProfileViewModel : NSObject
@property (nonatomic, weak, nullable) id<ProfileViewModelDelegate> delegate;
@property (nonatomic, readonly, strong) PXLoadState *state;
@property (nonatomic, readonly, copy, nullable) NSString *displayName;
@property (nonatomic, readonly, copy, nullable) NSString *accessibilitySummary;
@property (nonatomic, copy, nullable) void (^onRequestEditFlow)(void);
- (instancetype)initWithService:(id<ProfileServicing>)service NS_DESIGNATED_INITIALIZER;
- (void)load;
- (void)didTapEdit;
@end
NS_ASSUME_NONNULL_END
@implementation ProfileViewModel {
id<ProfileServicing> _service; // protocol only —
…NS_ASSUME_NONNULL_BEGIN
extern NSErrorDomain const PXProfileServiceErrorDomain;
typedef NS_ENUM(NSInteger, PXProfileServiceError) {
PXProfileServiceErrorNotAuthenticated = 1,
PXProfileServiceErrorInvalidResponse = 2,
};
@protocol ProfileServicing <NSObject>
- (void)fetchCurrentProfileWithCompletion:(void (^)(PXUserProfile * _Nullable profile,
NSError * _Nullable error))completion;
@end
@interface ProfileService : NSObject <ProfileServicing>
- (instancetype)initWithRepository:(id<ProfileRepositoring>)repository NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
NSErrorDomain const PXProfileServiceErrorDomain = @"PXProfileServiceErrorDomain";
@implementation ProfileService {
id<ProfileRepositoring> _repository;
NSURLSession *_session;
}
- (instancetype)initWithRepository:(id<ProfileRepositoring>)repository {
self = [super init];
if (self) {
_repository = repository;
_session = [NSURLSession sharedSession];
}
return self;
}
- (void)fetchCurrentProfileWithCompletion:(void (^)(PXUserProfile * _Nullable, NSError * _Nullable))completion {
NSString * _Nullable token = [_repo
…NS_ASSUME_NONNULL_BEGIN
@protocol ProfileRepositoring <NSObject>
- (nullable NSString *)currentAuthToken;
- (void)saveAuthToken:(NSString *)token;
- (void)clearAuthToken;
- (nullable PXUserProfile *)cachedProfile;
- (void)cacheProfile:(PXUserProfile *)profile;
@end
@interface ProfileRepository : NSObject <ProfileRepositoring>
- (instancetype)initWithTokenStore:(PXTokenStore *)tokenStore
profileCache:(PXProfileCache *)profileCache NS_DESIGNATED_INITIALIZER;
@end
NS_ASSUME_NONNULL_END
@implementation ProfileRepository {
PXTokenStore *_tokenStore; // Keychain-backed — see the Keychain lesson
PXProfileCache *_profileCache; // NSKeyedArchiver-backed — see the Persistence lesson
}
- (instancetype)initWithTokenStore:(PXTokenStore *)tokenStore
profileCache:(PXProfileCache *)profileCache {
self = [super init];
if (self) {
_tokenStore = tokenStore;
_profileCache = profileCache;
}
return self;
}
- (nullable NSString *)currentAuthToken {
return [_tokenStore currentToken];
}
- (void)saveAuthToken:(NSString *)token {
[_tokenStore saveToken:token];
}
- (void)clearAuthToken {
[_tokenStor
…// NSDate+PXFormatting.h — a category adding a formatting convenience used by the ViewModel layer
NS_ASSUME_NONNULL_BEGIN
@interface NSDate (PXFormatting)
- (NSString *)px_relativeDisplayString;
@end
NS_ASSUME_NONNULL_END
// NSDate+PXFormatting.m
@implementation NSDate (PXFormatting)
- (NSString *)px_relativeDisplayString {
static NSRelativeDateTimeFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSRelativeDateTimeFormatter alloc] init];
});
return [formatter localizedStringForDate:self relativeToDate:[NSDate date]];
}
@end
// ProfileRepository+Private.h — a class extension hiding a mutable property from ProfileRepository.h's public interface
@interface ProfileRepository ()
@property (nonatomic, strong, readwrite) PXUserProfile *lastKnownProfile;
- (void)invalidateCacheIfTokenChanged;
@endCapstone Required Architecture
Every dependency arrow points downward only. UI depends on ViewModel; Coordinator wires ViewModel/Service/Repository together; ViewModel depends on a Service protocol; Service depends on Repository; Repository composes Keychain and persistence; Persistence/Network depends on nothing above it.
┌────────────────────┐
│ UI │
└──────────┬───────────┘
↓
┌────────────────────┐
│ Coordinator / │
│ Controller │
└──────────┬───────────┘
↓
┌────────────────────┐
│ ViewModel / │
│ Presentation │
└──────────┬───────────┘
↓
┌────────────────────┐
│ Service │
└──────────┬───────────┘
↓
┌────────────────────┐
│ Repository │
└──────────┬───────────┘
↓
┌────────────────────┐
│ Persistence / │
│ Network │
└────────────────────┘Important Rules
Dependencies point one direction only, downward through the stack — a lower layer never imports or references a type from the layer above it.
Each layer has a single, clearly-scoped responsibility: UI renders and forwards input; Coordinator navigates and wires; ViewModel formats and holds presentation state; Service performs one business operation and talks to the network; Repository composes storage concerns (auth + cache) behind one interface; Persistence/Network provides raw, domain-ignorant capabilities.
A layer depends on the *protocol* of the layer directly below it, never on a concrete class, which is what makes every layer replaceable by a test double.
Testability is a design constraint, not an afterthought: if a class cannot be unit tested without a real network connection, Keychain, or disk, a dependency-injection seam is missing somewhere in its constructor.
Blocks used for cross-object callbacks (coordinator completion, service completion handlers) capture self weakly whenever the capturing object may be retained by the object holding the block.
Errors are represented as domain-specific NSError values with a defined domain and code, translated at the boundary where the failure occurred (Service translates transport failures; Repository translates 'no token' into an explicit state) rather than propagated as opaque failures.
This Architecture Is What 'Production Objective-C' Actually Means
Nothing in this capstone's architecture is unique to Objective-C — the same UI/Coordinator/ViewModel/Service/Repository/Persistence layering shows up, with different names, in Swift apps, Android apps, and plenty of backend services. That is deliberate: the point of this final lesson is not to teach one more Objective-C feature, but to show that everything this course taught — from @interface/@implementation syntax in the earliest modules, through ARC and blocks, through Foundation and networking, through Core Data and testing, through Swift interoperability — exists in service of being able to build exactly this kind of layered, testable, maintainable system. A team maintaining a real Objective-C codebase in 2026, whether it's a mature iOS app with a large legacy surface or a newer project that simply chose Objective-C for its C interoperability, is, on a good day, building something that looks close to this. That is the practical target the entire 80-module course has been aiming at.
Letting the UIViewController subscribe directly to NSNotificationCenter for network completion or read from NSUserDefaults/Keychain itself to decide what to display.
Route every piece of displayed state through the ViewModel; the UI layer only ever reads ViewModel properties and calls ViewModel methods in response to user actions.
The moment a view controller reaches around the ViewModel to talk to a lower layer directly, the layering stops being real, and the view controller starts absorbing the business logic and untestable dependencies the whole architecture exists to keep out of it — this is exactly how 'massive view controller' codebases form.
Having ProfileViewModel import <Foundation/NSURLSession.h> to make its own network call when the injected Service is 'inconvenient' for a one-off need.
Add the needed operation to the Service protocol (and its implementation) and call it from the ViewModel through that protocol, even for a single, seemingly simple request.
Any direct networking call from the ViewModel reintroduces a concrete, untestable dependency at the presentation layer and makes that one code path impossible to unit test without a live network connection, defeating the entire reason the Service boundary exists.
A Coordinator that stores a strong reference to its child coordinator and the child's onFinished block captures self (the parent) strongly.
Capture self weakly inside a block owned (directly or indirectly) by an object the capturing object also holds strongly, and promote to a strong local reference only for the duration of the block's body.
Parent holds child strongly (childCoordinators array); if the child's block also holds the parent strongly, neither object's retain count can ever reach zero, and both leak for the lifetime of the app — a classic retain cycle at the architecture level, not just inside one method.
Having the Service layer call SecItemCopyMatching/SecItemAdd directly to read or write the auth token instead of going through the Repository's token-store abstraction.
The Service asks the Repository (id<ProfileRepositoring>) for the current token; only the Repository's PXTokenStore touches the Keychain APIs.
Skipping the Repository layer to reach directly into Keychain APIs from the Service scatters Security-framework usage across the codebase, makes the Service untestable without real Keychain access, and duplicates token-refresh/expiry logic that should live in exactly one place.
Treating 'no auth token present' as an NSError thrown from deep inside the Service's NSURLSession completion handler, discovered only after a failed HTTP 401 response comes back.
Model 'not authenticated' as an explicit, checkable condition (Repository's currentAuthToken returning nil) that the Service checks before making the request at all, per the Authentication concepts lesson.
Waiting for a 401 to discover there was never a token wastes a network round trip, complicates the Service's error-translation logic, and conflates two different failure modes (never signed in vs. token expired mid-session) that a caller may need to handle differently.
Why does ProfileViewModel in this capstone never import or reference NSURLSession directly? A. NSURLSession is deprecated in favor of URLSession and cannot be used from Objective-C at all B. Importing it would collapse the layering the architecture depends on, coupling presentation logic to a concrete networking type and making the ViewModel impossible to unit test without a real network connection C. NSURLSession can only be called from a Coordinator, never from any other class D. ViewModels are compiled in a separate target that has no access to Foundation's networking APIs
B. Importing it would collapse the layering the architecture depends on, coupling presentation logic to a concrete networking type and making the ViewModel impossible to unit test without a real network connection — The ViewModel depends only on the id<ProfileServicing> protocol; depending on a concrete networking type directly would break the dependency-inversion rule the whole architecture is built on and remove the seam that makes the ViewModel testable with a mock service.
In the Coordinator example, why does the child coordinator's onFinished block capture self and the child weakly rather than strongly? A. Weak captures are required by ARC for every block in Objective-C, with no exceptions B. The parent coordinator already holds the child strongly in childCoordinators, so a strong capture back to the parent (and the child) inside the child's own block would create a retain cycle that never deallocates C. Strong captures are only valid inside completion handlers, not inside stored block properties D. Weak capture makes the block execute faster
B. The parent coordinator already holds the child strongly in childCoordinators, so a strong capture back to the parent (and the child) inside the child's own block would create a retain cycle that never deallocates — Because the parent already retains the child strongly, a block owned by the child that also retains the parent strongly forms a cycle; capturing weakly and promoting to a strong local reference only for the block's duration breaks that cycle.
Which layer in this capstone's architecture is the only one that directly calls Security-framework Keychain APIs like SecItemCopyMatching? A. The ViewModel, since it needs to know whether the user is authenticated B. The Service, since it needs the token to attach to its NSURLSession requests C. The Repository, via its composed PXTokenStore, which is the only place Keychain access is wrapped D. The UI layer, since login state affects what is displayed
C. The Repository, via its composed PXTokenStore, which is the only place Keychain access is wrapped — The Repository composes a Keychain-backed token store behind its ProfileRepositoring protocol; every other layer asks the Repository for the current token rather than touching Security-framework APIs itself.
What is the architectural problem with a Service class that, for convenience, calls straight into the persistence and Keychain layers itself instead of going through a Repository? A. There is no real problem; Repository is purely an optional convenience wrapper B. It scatters storage-related concerns across multiple layers, duplicates logic that should live in one place, and makes the Service harder to test in isolation from real storage C. NSURLSession requires a Repository object to be present in the same file to compile D. It would make the app crash immediately on launch
B. It scatters storage-related concerns across multiple layers, duplicates logic that should live in one place, and makes the Service harder to test in isolation from real storage — The Repository exists specifically to be the single place that composes storage concerns (auth token plus cache) behind one interface; letting the Service reach around it reintroduces the tight coupling and duplicated logic the layer was designed to prevent.
This capstone models 'not authenticated' as an explicit condition the Repository/Service check before making a request, rather than waiting for an HTTP 401. Why does this matter architecturally? A. HTTP 401 responses are not supported by NSURLSession B. Checking explicitly avoids an unnecessary network round trip and lets 'never signed in' be distinguished cleanly from 'token expired mid-session,' which may need different handling C. It has no real benefit; both approaches behave identically in every case D. NSError cannot represent HTTP status codes, only local Objective-C errors
B. Checking explicitly avoids an unnecessary network round trip and lets 'never signed in' be distinguished cleanly from 'token expired mid-session,' which may need different handling — Treating the absence of a token as a first-class, explicitly checkable state (per the Authentication concepts lesson) avoids a wasted network call and keeps two genuinely different situations — never authenticated versus a token that expired — distinguishable at the point where the distinction actually matters.
A teammate proposes moving all accessibilityLabel construction out of the UI layer and into the Persistence/Network layer, arguing it will be computed 'closer to the source data.' Why is this a bad fit for this capstone's architecture? A. Accessibility APIs are only available in UIKit, which the Persistence/Network layer must not depend on, and formatting for presentation belongs in the UI/ViewModel layers that already own display concerns B. It is actually the correct place, since accessibility strings should always be generated as early as possible C. The Persistence/Network layer cannot store NSStrings D. NSAttributedString requires Core Data to function
A. Accessibility APIs are only available in UIKit, which the Persistence/Network layer must not depend on, and formatting for presentation belongs in the UI/ViewModel layers that already own display concerns — Persistence/Network is deliberately domain- and UI-ignorant; pulling UIKit accessibility concerns down into it would violate the one-way dependency rule and couple the lowest, most reusable layer to presentation details that belong at the top of the stack, where ViewModel-provided formatted strings already feed the UI's accessibility labels.
Why is the Service layer's protocol, ProfileServicing, defined and consumed by the ViewModel even though only one concrete implementation (ProfileService, backed by NSURLSession) exists in the shipped app? A. Objective-C requires every class to have a corresponding protocol before it can be instantiated B. The protocol boundary is what allows a test double to be substituted for unit testing the ViewModel, independent of how many concrete implementations exist in production C. Protocols are faster to call than concrete class methods at runtime D. It has no real purpose beyond documentation
B. The protocol boundary is what allows a test double to be substituted for unit testing the ViewModel, independent of how many concrete implementations exist in production — Even with a single production implementation, defining the dependency as a protocol is what makes dependency injection and mock substitution possible for testing — the value is in the testability seam, not in supporting multiple production implementations.
Add a new feature slice — 'change display name' — through all six layers of this capstone's architecture: (1) a UI element (a text field plus a Save button) that calls a ViewModel method and shows its accessibility label from ViewModel-provided text; (2) no Coordinator change is needed unless the flow requires a new screen — decide whether it does and justify your answer; (3) a ViewModel method updateDisplayName: that validates the input (non-empty, under 50 characters) and calls a new Service method, translating a validation failure into the same PXLoadState failure state used for network errors; (4) a new updateDisplayName:completion: method on ProfileServicing and its NSURLSession-backed implementation in ProfileService, performing a PATCH-style request; (5) a Repository method that updates the cached profile locally so the UI reflects the new name immediately, even before the network call to persist it completes; (6) note, in one sentence, whether the Persistence/Network layer needs any change at all for this feature.
UI: a text field bound to a new displayNameDraft ViewModel property and a Save button calling [viewModel saveDisplayName]. Coordinator: unchanged — this is an in-place edit on the existing screen, not a new navigation destination. ViewModel: - (void)saveDisplayName { if (self.displayNameDraft.length == 0 || self.displayNameDraft.length > 50) { self.state = [PXLoadState failedWithMessage:@"Name must be 1-50 characters"]; return; } [_service updateDisplayName:self.displayNameDraft completion:^(...) { ... }]; }. Service: add - (void)updateDisplayName:(NSString *)name completion:(...)completion; to ProfileServicing, implemented with an NSMutableURLRequest whose HTTPMethod is @"PATCH" and a JSON body built with NSJSONSerialization dataWithJSONObject:options:error:. Repository: add - (void)updateCachedDisplayName:(NSString *)name; that mutates and re-saves the cached PXUserProfile via the existing PXProfileCache, called by the ViewModel optimistically before the network completion returns. Persistence/Network: no change needed — the existing NSURLSession and NSKeyedArchiver-based cache mechanisms already support an additional request and an additional cached-field update without new capability.
Validation belongs in the ViewModel (it's presentation-adjacent business logic, not network logic); the Repository's local-cache update before the network call finishes is the same stale-while-revalidate idea used for reads, just applied to a write.