How to assess an inherited iOS codebase

A practical framework for evaluating build health, architecture, risk and delivery readiness in an inherited Swift or Objective-C app.

Start with whether the app can be built and released reliably, not whether its folders match a preferred architecture. A useful assessment connects technical findings to delivery, customer and business risk.

That framing is the difference between an assessment someone acts on and one that gets filed. Any competent engineer can list what they would have done differently. Far fewer can tell a founder which three things will cost them money in the next quarter, which five can wait a year, and which of the ugly-looking parts are actually fine.

This is the framework I use, in the order I use it.

Context before code

Read the product before reading the repository. An hour with whoever knows the app tells you what “good” would even mean here.

Find out what the product does and who pays for it. How many users, on what devices and OS versions, taken from analytics rather than assumption. How often releases go out, and whether that cadence is chosen or forced. What has gone wrong recently: incidents, bad releases, support spikes. What is on the roadmap for the next six months. And who is going to maintain this: an experienced iOS team, one generalist, or nobody yet.

The same codebase deserves opposite verdicts depending on those answers. A monolithic app with no tests that ships once a quarter to ten thousand users and has a stable roadmap is a very different risk from the same code shipping weekly to two million users while a team of four tries to double the feature set.

Reproducibility

This is the first technical test, and it is the one that most often produces a surprise.

Take a clean checkout on a machine that has never built the project. Can you build it? What did you have to know that was not written down? Are dependencies pinned to resolvable versions, or floating? Is configuration in the repository, with secrets clearly separated and injected? Is signing set up in a way that survives a new machine and a new engineer? Does CI build the same thing you built locally, and does anyone trust it?

A codebase that cannot be reproduced is not really under the company’s control, regardless of how elegant the code inside it is. If the build is broken outright, that becomes the first piece of work, and there is a safe order for troubleshooting it.

Map the codebase

Now read the code, but read it structurally before reading it line by line.

Identify the targets, packages and modules, and what actually depends on what. Trace two or three of the most important user journeys end to end: where the data comes from, who owns the state, how navigation is driven, where things are persisted. Note the third-party dependencies and what each one is load-bearing for. Look at how the app is configured for different environments.

You are building a mental model good enough to predict where a change would ripple. That prediction is what the rest of the assessment rests on.

Change-risk signals

These are the patterns that reliably indicate a codebase where changes are expensive or dangerous. None of them is fatal on its own; a cluster of them is meaningful.

  • Shared mutable state reachable from several places, particularly singletons that anything can write to.
  • Oversized types. A three-thousand-line view controller or an omniscient app-wide store is a change-cost problem before it is an aesthetic one.
  • Hidden coupling. Notification-driven flows, global state read at a distance, and implicit ordering requirements between unrelated components.
  • Force operations. try!, force unwraps and force casts on data that comes from a network, a database or a user.
  • Ignored errors. Empty catch blocks, discarded results, and error paths that silently do nothing.
  • Main-thread work. Parsing, disk access, image processing or database queries on the main queue.
  • Inconsistent abstractions. Three different networking approaches, two navigation patterns, and a persistence layer used two ways. Usually a sign of successive owners rather than carelessness, but the cost is real: every change needs someone who knows which convention applies where.

Language and concurrency

Look at what the compiler is being asked to enforce, and what it is being allowed to overlook.

Check the Swift language version, the build settings, and whether warnings are treated seriously or accumulate in the thousands. Turn on strict concurrency checking and see what the compiler says: the diagnostic count and their character tell you a great deal about Swift 6 readiness quickly. Look at how concurrency is actually done, and whether async/await, Combine, RxSwift, DispatchQueue and completion handlers are all in play at once. In mixed codebases, check the Objective-C interop surface and whether nullability is annotated.

Concurrency defects deserve particular attention because they are the ones that appear in production and not in testing. A data race that never fires on a fast device in a quiet room will fire on a slow device with a poor network.

Test signal

Ignore the coverage percentage. It is one of the least informative numbers available.

Ask instead: do the tests run, in CI, on every change? Do they pass consistently, or is there a known set of flaky tests everyone reruns? Do they cover the behaviour that would cost real money if it broke, such as purchases, authentication, data sync and anything involving money or health data? Would they actually catch a regression, or do they mostly assert that a mock was called?

A small suite of trustworthy tests around high-risk behaviour is worth far more than broad coverage nobody believes.

Production evidence

The app is already telling you where it hurts. Go and read what it is saying.

Crash reporting shows you the crash-free rate, the top crash clusters and how they correlate with releases, OS versions and device classes. Performance traces show launch time, hitches and memory behaviour on real hardware. App Store reviews and support tickets tell you what users experience, which is often not the same as what the crash dashboard shows. Analytics shows where users drop out, which sometimes has a technical cause hiding behind it.

If none of this instrumentation exists, that is itself a finding and usually a high-priority one.

The security and privacy boundary

A code review can surface obvious risks: secrets committed to the repository or embedded in the binary, sensitive data stored unencrypted, authentication tokens handled loosely, permissions requested beyond what the product needs, dependencies with known advisories.

It cannot tell you the app is secure. That requires penetration testing, which is a separate discipline with different tooling and a different kind of report. Be explicit about this distinction in whatever you write, particularly if the assessment is going to an investor or a board. An audit that implies more assurance than it can support is worse than no audit.

Rank the findings

Findings without a ranking are just opinions in a list. Use two axes.

Severity: critical, high, medium, observation.

Action: fix now, plan next, monitor, leave alone.

That last category matters more than people expect. Explicitly saying “this is ugly, and you should leave it alone” is one of the more valuable things an assessment can do. It stops a team spending a quarter tidying code that works and nobody needs to change.

Write the finding in a usable shape

Every finding should carry the same fields, so that someone who was not in the room can act on it:

Field What it captures
Observation What was found, stated plainly.
Evidence File, function, trace, crash cluster or measurement.
Impact What this costs in delivery, reliability, security or user experience.
Likelihood How probable the impact is, given how the app is actually used.
Recommendation The specific change proposed, not a principle.
Effort and sequence Rough size, and what it depends on.
Owner Who is best placed to do it.

Two paragraphs of evidence-backed finding in this shape is worth ten pages of general commentary.

Deliver a summary someone will read

Whatever else the assessment produces, it needs an executive summary that a non-iOS decision-maker can read in five minutes and act on: the overall state, the three things that matter most, what they cost, and what happens if they are ignored.

Then the detailed findings, and a roadmap that sequences them against the product roadmap rather than in a vacuum.

If this is the shape of assessment you need on a codebase you have taken on, that is exactly what my iOS Code & Architecture Audit service produces. For a sense of what that work costs in the UK and what moves the number, see how much an iOS code audit costs.

Need an independent read on a codebase you have inherited?

Tell me what you have taken on and what decision is waiting on it. An audit can usually start from a repository, a build and an hour of context.