001
work/cartola-fc
[ case study · shipped ]

cartola fc @ globo

how ai-assisted refactoring compressed months of objective-c into weeks of swift

role
senior ios engineer
team
4-6 ios engineers
stack
swift · swiftui · uikit · objective-c · async/await · live activities
period
2024 — present
status
● shipped
stack
swiftswiftuiuikitobjective-casync/awaitlive activities
screens migrated
0+
weeks to ship
~0
users impacted
0M+
lines rewritten
0k+
migration preview
← before
@interface LineupViewController : UIViewController
@property (nonatomic, strong) NSArray *players;
@property (nonatomic, strong) UITableView *tableView;

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupTableView];
    [self fetchPlayers];
}

- (void)fetchPlayers {
    [[APIManager shared] GET:@"/lineup"
        success:^(NSArray *data) {
            self.players = data;
            [self.tableView reloadData];
        }
        failure:^(NSError *error) {
            [self showError:error];
        }];
}

title: cartola fc @ globo subtitle: how ai-assisted refactoring compressed months of objective-c into weeks of swift role: senior ios engineer team: 4-6 ios engineers stack: [swift, swiftui, uikit, objective-c, async/await, live activities] period: 2024 — present year: 2026 status: shipped order: 1

context

cartola fc is one of the most-used apps in brazil. football, fantasy, millions of users, traffic that triples on match days. like most apps with a decade of history, parts of it were still in objective-c with uikit. the team had wanted to migrate for years — the calendar always pushed it back.

i owned a large area of that legacy code. the conventional path would take months. i tried something different and it took weeks.

the problem

the area being migrated covered lineup, market, and ranking features — core user flows that millions of people touch every week during the brazilian football championship. the objective-c codebase was tightly coupled, with uikit view controllers that mixed presentation, networking, and business logic.

a single view controller could be 800+ lines — viewDidLoad calling setupTableView, fetchPlayers, configureRefreshControl, with completion-handler-based networking inlined directly in the view layer. no separation of concerns. no testability.

the conventional approach kept losing to other priorities because the estimated timeline was always "months" — too long to justify when there were features to ship.

the code worked. that was the problem — "working" is the enemy of "better" when you're shipping weekly.

why a strangler pattern

a big-bang rewrite was not an option for an app where downtime is measured in lost users during live matches. the strangler pattern let new swift + swiftui code coexist with legacy objective-c, replacing it screen by screen while keeping the app shippable at every step.

the key was the bridging layer. every migrated screen had to:

→ expose the same navigation interface to the objc coordinator layer

→ consume the same data models (or bridge them via @objc annotations)

→ handle the same deep links and push notification payloads

→ pass the same integration tests that the legacy screens passed

where claude fit in

this is the core of the story. claude was the force multiplier.

context loading

i fed claude the legacy objective-c files — full view controllers, networking layers, model definitions. the long context window meant claude could hold an entire feature area at once, not just a single file.

a typical prompt included: the objc view controller (~800 lines), the associated model headers, the networking manager methods it called, and a brief architecture note about the target swiftui pattern.

what claude generated well

syntax translation — swift equivalents of objc method signatures, property declarations, enum definitions. mechanical but tedious work that claude did in seconds.

swiftui views — layout structure, data flow with @StateObject and @Published, basic state management. claude produced reasonable first drafts of 80% of views.

boilerplate — codable conformances, test scaffolding, mock objects. the kind of code that follows patterns perfectly.

consistency — when 15 screens followed the same pattern, claude was consistent in a way that manual work wouldn't be at 2am.

what claude got wrong

architecture — claude would default to patterns that looked clean in isolation but didn't fit the existing module structure or dependency graph. it didn't understand our coordinator pattern.

outdated apis — defaulting to NavigationView instead of NavigationStack, using onChange(of:perform:) instead of the two-parameter version. pre-ios 17 patterns when we could target newer.

objc runtime edge cases — subtle behaviors around nil messaging, dynamic dispatch, and kvo that the swift version needed to handle explicitly. claude would silently drop these.

the review loop

every generated file went through manual review. no exceptions.

what i always checked:

→ state management and data flow — is the @StateObject in the right place? are we over-observing?

→ threading boundaries — are async/await transitions correct? is @MainActor where it needs to be?

→ objc interop — are bridging headers correct? do @objc annotations cover all the coordinator calls?

→ accessibility — labels, traits, dynamic type support. claude often dropped these.

→ tests — existing xctest suite + new snapshot tests for every migrated view.

what shipped

the migration completed in under a month — work that the team had estimated at several months using conventional approaches. the migrated code is now in production, serving millions of users every match day.

the immediate payoff: the team could now add features to the migrated surfaces using swiftui, which cut feature development time significantly. live activities for match scores — a feature the team had been deferring because it required swift — shipped within weeks of the migration completing.

what i'd do differently

→ would establish a stricter prompt template on day one instead of iterating on it mid-migration. the first week was partially spent learning how to feed context to claude effectively.

→ would invest more upfront in automated diff validation between old objc and new swift output. caught at least two regressions late that could have been caught by a comparison script.

→ would document the bridging patterns as a playbook before starting. other teams at globo asked how to replicate this and i had to reconstruct the approach from memory.

what i learned

ai-assisted migration works when the engineer understands both the source and target deeply enough to review every line. claude compressed the mechanical work — the typing, the boilerplate, the syntax translation — but every architectural decision was still mine. the speed came from eliminating the tedious parts, not from skipping the hard parts.

by escaleira / 2026

project timeline
01 — analysis

mapped the legacy objective-c surface. identified 30+ screens for migration. evaluated complexity and dependency chains.

02 — strategy

chose the strangler pattern for incremental coexistence. set up bridging protocols for objc-swift interop.

03 — ai-assisted

used claude to generate swift + swiftui from objective-c source. review loop: generate → review → refine → test.

each file went through manual review — no blind merges.

04 — integration

gradual rollout through feature flags. snapshot tests for visual regression. live monitoring during match days.

05 — shipped

migration complete in under 4 weeks. all migrated code in production serving millions.