Episode Transcript
Available transcripts are automatically generated. Complete accuracy is not guaranteed.
SPEAKER_00 (00:02):
Welcome to the
second episode of the App Force
One Worklock, where I share myreal world iOS development
journey as I work on myday-to-day iOS projects.
Hey there, iOS developers,welcome to the second worklock
episode of App Force One.
I'm Juden Leinhardt and I'mexcited to share with you what
I've been working on over thepast few weeks.
As I mentioned in the introepisode, I'm now back to
(00:22):
building iOS apps full time atDawn Technologies.
Working on my day-to-dayprojects, which include critical
image response apps forcompanies.
Think of it like a digital firstaid kit that companies use when
something goes wrong.
This isn't just any app, it'sone of that could literally save
lives, which makes the work feelincredibly meaningful.
But here's the thing this codebase is like a classic car
(00:44):
that's been sitting in a garagefor years.
It's over eight years old, whichin iOS development terms is
ancient.
It was built before Swiss UIexisted, so it's full on all UI
kit.
It's using patterns andpractices that were cutting edge
in 2016 but are now consideredoutdated.
We're talking about manual viewcontroller management, old
(01:04):
networking patterns andarchitectural decisions that
made sense at the time but arenow technical debt.
And I love it.
I absolutely love it becausethis is exactly the kind of
challenge that gets me excitedabout iOS development.
It's like being handed a classiccar that needs a complete
restoration.
Sure, it's going to be a lot ofwork, but the end result is
going to be amazing.
(01:24):
So my weekend review.
So what I actually have beenworking on since September 25th,
let me break it down into majorthemes.
So those are the app delicatecleanup and modern architecture,
working with the existingfeature flag system,
walkie-talkie and PTTintegration, chat system
refactoring, location trackingmodernization.
(01:45):
Okay, let's get started.
First one.
App Delicate Cleanup and ModernArchitecture.
One of the biggest refactoringefforts I've been leading is
breaking down the monolithic appdelicate.
You know how it is.
Over the years, the app delicatebecomes this massive file that
handles everything fromauthentication to notifications
to background tasks.
It's like that one friend whoalways volunteers to organize
(02:06):
everything at a party.
They end up doing everything andeventually they burn out.
I've been extracting concernsinto dedicated managers like
hiring a team of specialistsinstead of relying on one
overworked generalist.
An app lifecycle manager handlesforeground background
transitions like a bouncer at aclub.
Background task manager managesbackground task registration and
scheduling, it's more like aproject manager.
(02:28):
Authentication coordinatorhandles all biometric and
authentication logic, it's thesecurity card.
Crash Reporting Manager managesSentry app and uses tracking
like detective collectingevidence.
The key insight here is thatwe've been moving from a single
massive class to a collection offocused testable components.
Each manager has a singleresponsibility, making the code
(02:49):
much cleaner to understand andmaintain.
It's like going from a SwissArmy knife to a proper toolkit.
So the second thing I've beendealing with is working with the
existing feature flag system.
The app already had a massivefeature flag system in place,
and I've been leveraging itextensively for my refactoring
work.
Think of it like having a dimmerswitch for your code.
You can turn features up or downwithout rewiring the entire
(03:11):
house.
This is crucial when you'redealing with a critical app that
you can't afford to have anydowntime on.
It's like having an emergencybreak on the train.
You hope you never need it, butwhen you do, you'll be really
glad it's there.
The feature flags I've beenworking on were the following
one (03:25):
Refactor Alert Detail
Screen for the new alert detail
implementation, a refactor alertstatistic screen for the
statistics screen rewrite, and arefactor flick buttons for the
flick management refactor.
And then there's the asynclocation tracking for the new
iOS 17 and 18 location tracking.
This allows me to test newimplementations alongside the
old ones, ensuring I can rollback quickly if something goes
(03:47):
wrong.
It's like having a safety netwhen you're walking a tightrope,
really.
And the third thing I've beenworking on is the walkie-talkie
and push-to-talk integration.
One of the most complex featuresI've been working on is the
push-to-talk integration for thewalkie-talkie functionality.
This involves real-time audiostreaming over WebSockets, which
is incredibly challenging to getright.
It's like trying to have aconversation through a
(04:08):
walkie-talkie while riding aroller coaster, everything is
moving, everything is changing,and you need to keep the
connection stable.
Let me tell you about thedebugging nightmare I had last
week.
I was getting reports that thewalkie-talkie was cutting out
mid-conversation, but only oncertain devices.
After hours of debugging, Idiscovered that the audio engine
was setting up multiple times,causing conflicts.
(04:30):
And it also caused the MP volumefuel to literally move around on
the screen because of this.
That was the issue I talkedabout earlier.
It was like having a volumeslider that had a mind of its
own.
And the key challenges I've beensolving is the audio streaming,
encoding and decoding audio inreal time, like trying to
translate a conversation whileit's happening, web socket
management, handling connectionstates, reconnection logic, and
(04:51):
retry mechanisms, like having abackup plan for every possible
scenario, state management,tracking recording state,
connection state, and audioplayback, like keeping track of
who's talking, who's listening,and what's happening.
And then of course there'sthread safety, ensuring that
audio processes happen on theright threads, like making sure
that the right people are in theright rooms at the right time.
I've consolidated the audioengine setup to solve this MP
(05:14):
volume view movement issues Ihad and implemented the retry
mechanism that attemptsreconnecting up to five times
instead of just two.
The audio buffering system nowruns in separate threads to
prevent clipping and ensuresmooth playback.
It's like having a backup soundsystem that kicks in when the
main one fails.
Another thing I've been workingon was a chat system
refactoring.
I've been completely rewritingthe chat system to use a modern
(05:38):
service-based architecture.
The old system uses a singletonMB chat manager that was tightly
coupled to the UI.
It's like having a chat systemthat was permanently glued to
the screen.
The new chat service is muchmore modular and testable, like
having a chat system that can beplugged into any device.
And the key improvements thatI've been working on are an
async await support, modernizingthe matrix SDK integration and
(06:01):
better error handling so thatproper error types and recovery
mechanisms are in place, andthread safety, again thread
safety, so to make sure that thematrix SDK that we rely on is
used safely across threads andmaking sure that everyone
follows the same traffic rulesthere.
And I also wanted to introducesome more separation of
concerns.
The service handles all thematrix logics while the UI
(06:22):
focuses on presentations, likehaving a chef who cooks and a
waiter who serves.
Everybody has their ownspecialized task and we don't
walk in front of each other.
The location tracking system hasbeen completely overhauled to
use iOS 17 and 18's new asynclocation tracking APIs.
(06:42):
This is a perfect example of howiOS evolves and how you need to
adapt your code.
It's like upgrading from a papermap to GPS.
The old system worked, but thenew one is much better.
The new system includesgeofencing, processing, using
the new CL monitor API forbetter performance, like having
a security card who neversleeps, and background task
manager management, sobackground task management,
(07:04):
proper background task handlingfor location updates like having
a reliable assistant who alwaysremembers to check in.
Debouncing, preventing toprevail prevent excessive server
calls when driving, becauseotherwise we get like an update
every five meters, andespecially if you're at speed,
that's a bit much.
And distance filtering so thatwe only send location updates
(07:25):
when the user has movedsignificantly.
So that's just to make sure thatwe don't uh saturate the server.
Alright, let's dive into thedetails a little bit more
because this is like ahigh-level overview.
Code Deep Dive, the App DelicateRefactor.
Uh let me show you a specificexample of the refactoring work
that I've been doing.
This is the kind of real-worldproblem that every iOS developer
(07:48):
faces when working with legacycodes.
So the problem here.
The original app delicate wasover 500 lines long and handled
everything.
It was like a Swiss armor knifethat had grown into a full
toolbox.
Imagine if your kitchen had onegiant appliance that was
supposed to be yourrefrigerator, your stuff, your
dishwasher, your microwave, andyour coffee make all rolled into
one.
That's what our app delicate hadbecome.
(08:09):
I remember the first time Iopened the file, I was like,
nope, what the hell is this?
It was handling authenticationand biometric login, push
notification registration,background task management, deep
linking, security checks,feature flag refreshing, and
then some other things.
This made it incrediblydifficult to test, understand,
and maintain.
It was like trying to fix a carwhen all the parts were welded
(08:29):
together.
You couldn't work on one thingwithout affecting everything
else.
Every time I made a change, Iwas holding my breath, wondering
if I might have broken somethingon the other side of the whole
machine.
So the solution that I came upwith was that I broke everything
down into focus managers.
The new app lifecycle manager ismuch simpler, it just
coordinates between the otherservices.
When the app comes to theforeground, it refreshes feature
(08:51):
flags, syncs device info, andupdates the session manager if
needed.
When it becomes active, it postsnotifications and runs security
checks.
The key insight is that eachmanager has a single clear
responsibility.
The app lifecycle managerdoesn't know how to handle
background tasks, it just knowswhen to tell the other services
to do their job.
It's like having a conductor whodoesn't play any instruments but
(09:12):
knows when each section shouldcome in.
The benefits of this is thatfirst of all we have
testability.
Each manager can be tested inisolation, like having separate
test tracks for each carcomponent.
Maintainability, changes to oneconcern don't affect others,
like having separate rooms in ahouse.
Readability, the code isself-documenting, like having
(09:34):
clear labels on everything, andthere's some more reusability.
Managers can be used indifferent contexts, like having
modular furniture that works inany room.
So the challenges that ever wasfacing was that the biggest
challenges was ensuring that therefractor didn't break existing
functionality.
This is where the feature flagsystem became crucial.
I could test the newimplementation alongside the old
one, ensuring a smoothtransition.
(09:56):
It's like having uh renovationin your house while you're still
living in it.
You need to make sure that theelectricity and the water still
work while you're replacing theplumbing.
Alright, so let's dive intofeature flags a little bit
because I've mentioned them afull few times over.
Um the feature flag systemthat's already in place in this
code base, it's like a tool thatbecomes essential for modern iOS
(10:17):
development, especially whendealing with legacy code.
Think of it like having a remotecontrol for your code.
You can turn features on and offwithout touching the code
itself.
The code base already had awell-designed feature flag
system in place.
It's built around an enum thatdefines all the different
features with names likerefactor alert detail screen and
async location tracking.
Each feature has a uniqueidentifier and the default
(10:40):
value.
So, how did I use it?
In the UI code, I canconditionally use new
implementations.
For example, when someone tapson alert, I check if the new
alert detail screen feature isenabled.
If it is, I create a new viewcontroller with the modern MVM
architecture.
If not, I fall back to the oldstoryboard-based implementation.
This pattern is repeatedthroughout the app.
(11:00):
Every time I want to use arefacted component, I first
check the feature flag.
It's like having a switch thatdetermines which version of the
code gets executed.
So why does this matter?
Feature flags allows you todeploy safely, test new code in
production without affecting allusers, like having a test
kitchen in a restaurant, and youcan roll back quickly.
If something goes wrong, I candisable the feature instantly,
(11:21):
like having an emergency stopbutton.
Also, it allows for A-B testing,so you can compare the old
versus the new implementations,like having two different
recipes and seeing which of thewhich of these the customers
prefer, and it allows forgradual rollouts, so enabling
features for a subset of usersfirst, like having a soft
opening before the grantopening.
This is especially important forcritical apps like the ones I'm
(11:43):
working on, where downtime couldliterally cost lives.
It's like having a backupgenerator for a hospital.
You hope you never need it, butwhen you do, it's the difference
between life and death.
The existing system has been alifesaver for my refactoring
work.
Instead of having to deployeverything at once and hope for
the best, I can graduallymigrate users to the new
implementation while keeping theold code as a safety net.
(12:04):
So the lessons that I learned,um, legacy code is a gift.
That's the first lesson.
Working with legacy code isn't aburden, it's an opportunity to
learn.
Every piece of technical debttells a story about how IRS
development has evolved.
The old patterns made sense atthe time, and understanding why
they were chosen helps me makebetter decisions today.
It's like reading a historybook, you can see how we got to
(12:27):
where we are now, and it helpsyou understand where you're
going to need to be going.
I've actually started toappreciate the craftsmanship
that went into this code, evenif it's outdated.
The developers who built thiseight years ago were working
with the tools and patterns theyhad available.
They made the best decisionsthey could with the information
they had back at the time.
That's something I try toremember when I refactored their
work.
(12:48):
Refactoring also requirespatience, that's the second
lesson.
You can't refactor everything atonce.
The key is to identify the mostcritical areas and tackle them
systematically.
The app delicate refactor tookwell over a week, but each step
made the code base a littlebetter.
It's like renovating an oldhouse, you can't tear it all
down at once, you have to do itroom by room.
I had to resist the urge to justrewrite everything from scratch.
(13:10):
That could have been faster inthe short term, but it would
have been significantly morerisky for a critical app like
this one.
Instead, I took the slow andsteady approach, making small
incremental improvements that Icould test and validate at each
step.
So the third lesson (13:25):
testing is
everything.
When refactoring criticalfunctionality, testing becomes
your safety net.
The feature flex system allowsme to test new implementations
in production without riskingthe entire app.
It's like having a safetyharness when you're rock
climbing.
You hope you never need it, butwhen you do, it's the difference
between a minor slip and a majorfall.
I learned this the hard wayearly on.
(13:45):
I made a change to theauthentication flow without
properly testing it, and itbroke login for a subset of
users.
That was a wake-up call.
Now I test everythingextensively.
And I use the feature flagsystem to gradually roll out
changes to a small percentage ofusers first.
Then the fourth lesson is moderniOS apps are worth the effort.
Sorry, I need to say modern iOSAPIs are worth the effort.
(14:09):
The new location tracking API iniOS 17 or 18 is significantly
better than the old ones.
Yes, it requires rewritingexisting code, but the
performance and reliabilityimprovements are worth it.
It's like upgrading from a flipphone to a smartphone.
Sure, you have to learn new waysof doing things, but the
capabilities are so much betterthat it's worth the effort.
(14:30):
The old location tracking codewas a mass of callbacks and
delicate methods.
The new async await APIs are somuch cleaner and easier to
reason about.
It took me a while to get usedto the new patterns though, but
now I can't imagine going backto the old way of doing things.
Okay, so let's look ahead alittle bit.
So what's coming up in the nexttwo weeks?
I'm not exactly sure, but Iexpect it will be something to
(14:53):
do with the chat system.
I'm refactoring it and I'm now80% done.
The remaining work involvesmigrating the remaining UI
components to the new chatservice and in comprehensive
error handling, implementingproper offline support.
It's like finishing the last fewrooms in the house renovation.
The foundation is solid, butthere's some finishing work to
(15:14):
do.
The chat system is one of themost complex parts of the app,
so I'm taking my time to get itright.
I also want to finish the alertdetail screen refactor.
The new alert detail screen ismuch more complete.
It also uses a modern MVVMarchitecture with proper
separation of concerns.
The remaining work is mostly UIpolish and testing.
It's like putting the final coatof paint on a car.
The engine is running great, butyou want to make sure that it
(15:38):
definitely looks good.
This screen is critical becauseit's where users get detailed
information about emergencyalerts.
The old version was clunky andhard to navigate, especially in
high stress situations.
The new version is much moreintuitive and responsive.
I'm also going to continue thelocation tracking modernization.
The new location tracking systemis working well, but I want to
add more sophisticated UFencinglogic and improve the background
(16:00):
task management.
It's like having a GPS thatworks, but now I want to make
sure that it's smart enough toavoid traffic jams.
So location tracking is crucialfor this app because it's used
to determine who's in the areawhen an emergency happens.
The more accurate and reliableit is, the better the emergency
response can be.
And then of course there's mycleanup branch, because I've
been doing this on the side.
(16:21):
I've also been working on amassive cleanup branch that's
been running in parallel.
That's the kind of work thatdoesn't get much attention but
is absolutely crucial formaintaining a healthy code base.
It's like doing uh springcleaning, not glamorous but
necessary.
I actually enjoy this kind ofwork.
There's something satisfyingabout removing that code and
fixing warnings.
It's like cleaning up yourworkspace.
You feel more productive wheneverything is organized and
(16:42):
tidy.
This cleanup work includesremoving deprecated APIs,
getting rid of the old API thatwe don't longer want to support.
I've been just fixing warningsbecause there were like hundreds
of warnings.
So cleaning up compiler warningsand deprecation notices,
removing that code, eliminatingcode that's no longer used
anywhere anywhere.
So there were large chunks ofcode that were not being called
(17:04):
into.
And I wanted to modernize a fewof the patterns that were used.
So updating old patterns to usemodern Swift UI features and
Swift features.
(17:32):
You don't notice it when it'sworking, but when you but you'll
definitely notice when it's not.
So there was also a bug fix Ineeded to do, uh Wi-Fi settings
related.
I've been working on this bugfix for the Wi-Fi settings not
being configurable.
Uh so it used to be that thisworked, but now with iOS 26 it
doesn't.
So this is a perfect example ofhow legacy code can have subtle
(17:53):
issues that only serves whenyou're doing other refacting
work.
The Wi-Fi management system wasusing deprecated APIs and had
some threading issues that werepreventing users from properly
configuring their Wi-Fi networksfor presence tracking.
So here's the story.
I got a support ticket from auser who couldn't configure
their Wi-Fi network.
They were on iOS 26, and theWi-Fi setting screen was
completely broken.
At first I thought it was a UIissue, but after digging deeper,
(18:18):
I discovered something much moreinteresting.
The issue was that the app wasstill using the old Wi-Fi API
that was deprecated since iOS14.
Specifically, it was using CNcopy current network info from
the system configurationframework, which was deprecated
in iOS 14.
And here's the kicker.
It seems like this API stoppedworking completely in iOS 26, so
(18:38):
users on the latest iOS versionscouldn't configure their Wi-Fi
networks at all, which is prettycritical for an emerging
response app that relies onpresence tracking, right?
So the fix involved completelymodernizing the Wi-Fi validation
system, moving away from thedeprecated C and copy current
network info API to the modernNEHotspot Network.fetchCurrent
API that's been available sinceiOS 14.
(19:01):
This new API uses uh well usesuh completion handling, but I
wrapped it in an async awaitpattern, which is much more
reliable.
I also had to ensure that the UIupdates happen on the main
thread and fix some raceconditions in the validation
logic.
The key change was replacing theold synchronous API call with a
new async version and wrappingit in a proper continuation to
handle the completion-based API.
(19:23):
It's a perfect example of howApple's API evolution requires
us to constantly update ourcodes to code to stay
compatible.
It's like fixing a squeaky door.
It's not the most exciting work,but it makes a huge difference
in the user experience, and it'sa great reminder of why keeping
up with API deprecations is soimportant, especially when
you're dealing with criticalfunctionality.
So, and that's a wrap on thesecond App Force One worklock
(19:46):
episode.
I hope this gives you a realsense of what it's like to work
on a complex legacy iOS app in2025.
The key takeaways is that uhrefactoring legacy code isn't
just about making it modern,it's about understanding the
business context, the technicalthe technical constraints and
the user impact.
Every decision I make was hasreal consequences for people who
depend on this app in emergencysituations.
(20:08):
It's like being a surgeon, youcan't just focus on the
technical aspects, you have toremember that there's a real
person on the operating table.
In the next episode, I'll divedeeper into the walkie-talkie
audio streaming most likely, andshare some of the debugging
challenges I am definitely goingto be facing in the next week.
I'll also talk perhaps about theMatrix SDK integration and how
(20:29):
I'm handling real-timemanagement messaging.
Think of it like abehind-the-scenes look on how
the magic happens.
Before I wrap up, I want tomention that I'm organizing a
conference called Do iOS, whichis happening later this year.
It's going to be an amazingevent focused on iOS
development, which talks uh withtalks from some of the best
developers in the community.
If you're interested in learningmore about iOS development,
(20:51):
networking with otherdevelopers, and getting inspired
by the latest trends andtechniques, definitely check out
do iOS.com for more informationon the tickets.
Make sure to check the link inthe show notes.
So, until next time, keepbuilding amazing apps and
remember every expert was once abeginner.
The key is to keep learning,keep building, and keep sharing
what you learn with thecommunity.
Thank you for listening, andI'll see you next week for the
(21:13):
next worklock episode.
I'm really excited to share moreof this journey with you.