Project 8 · Rubric and defend-your-codeswift-6.4/ios-26
Lesson 6 / 6
Unit 28 · Trackcast

Project 8 · Rubric and defend-your-code

Note

Unit 28 · Project 8 · Rubric. Score your own Trackcast honestly, then answer the defend-your-code prompts out loud or in writing. If you can't defend a choice, that is the choice to revisit.

The rubric is not a checklist to game; it is the review an enterprise iOS engineer would give your player. Media code earns its keep by surviving interruption, so most of the weight is on correctness under real conditions, not on features.

Rubric

Area Not yet Solid Excellent
Playback core play/pause works seek is exact, speed control, no observer leak transport disabled until item ready, clean deinit
Library & discover one hard-coded episode subscribed podcasts grid, show pages, .searchable discover reuses one model; no duplicated fetch paths
Chapters & queue none chapters derived from the play head (seek on tap); an up-next queue queue auto-advances at end-of-item
Sleep timer absent a timer that pauses playback when it fires cancels cleanly; survives re-arm
Isolation compiles PlayerModel is @MainActor @Observable every cross-domain hop justified, no @unchecked
Background plays foreground only plays backgrounded and on lock interruptions pause and resume correctly
Lock screen title shows artwork + self-advancing scrubber + remote commands commands stay in sync with app state
Downloads downloads to disk background session, file moved synchronously cancel with resume data, race-free progress
Live Activity (elective) absent Lock Screen + Dynamic Island render rate-limited updates, clamped progress, clean end
Craft builds light/dark correct, VoiceOver on transport no raw colors, no magic numbers, tokens only

Aim for every row at "Solid" before you ship, with the isolation and downloads rows at "Excellent" — those are the ones the AI checkpoint proved are easy to get subtly wrong.

Defend your code

Answer these as if in a code review. The goal is that your reasons are load-bearing, not decoration.

Why is PlayerModel marked @MainActor? What specifically would break if it were not, given that it both feeds SwiftUI and calls AVPlayer? Name the concrete race or crash.

Where does the periodic time observer's closure capture self, and why weakly? Trace the retain cycle that a strong capture would create, object by object, and say what removing the observer in deinit prevents.

Your download progress lives behind an actor. Why not a lock, or a @MainActor class? What does the actor buy you over each alternative, and what is the cost you accepted (the async access) in exchange?

Why is the temp file moved inside didFinishDownloadingTo rather than in a follow-up task? What is the lifetime of that file, and what happens if you defer the move?

On the lock screen, why set MPNowPlayingInfoPropertyPlaybackRate? What does the OS do with it, and what visible bug appears if you forget it on pause?

The AI-usage reflection

You are in Professional stage. Write two or three sentences on each:

  • What you delegated. Which components did you hand to Claude Code wholesale, and why were they good candidates (broad boilerplate, well-specified contract)?
  • What you rejected or rewrote. The download-concurrency checkpoint was one seeded bug — did you catch anything similar in your own AI-produced diffs? What did you send back?
  • What you would configure differently. If you set up a /swift-review pass or a hook to flag @unchecked Sendable in diffs, would this project have gone faster? Say how.
Tip

The strongest answer to "what did you reject" is specific: "Claude gave me a time observer that captured self strongly; I caught it because a leaked observer is a known trap, and I changed it to [weak self] and added the deinit removal." Specificity is the difference between using AI and being used by it.

Knowledge check

Q: Two rows are called out as "get to Excellent." Which, and why those two? Isolation and downloads. Both are places where AI-generated code compiles while hiding a concurrency bug (a wrong actor boundary, a raced progress dictionary), so they are where your review judgment, not the compiler, is the last line of defense.

Q: What makes a good defend-your-code answer versus a weak one? A good answer names the concrete failure a choice prevents — the specific race, retain cycle, or deleted file — rather than restating the choice. "It's @MainActor because UI code should be on the main thread" is weak; naming the exact cross-actor mutation it eliminates is strong.