Read the transcript
SPEAKER_00: Welcome to the
second episode of the App Force
One Worklock, where I share my
real world iOS development
journey as I work on my
day-to-day iOS projects.
Hey there, iOS developers,
welcome to the second worklock
episode of App Force One.
I'm Juden Leinhardt and I'm
excited to share with you what
I've been working on over the
past few weeks.
As I mentioned in the intro
episode, I'm now back to
building iOS apps full time at
Dawn Technologies.
Working on my day-to-day
projects, which include critical
image response apps for
companies.
Think of it like a digital first
aid kit that companies use when
something goes wrong.
This isn't just any app, it's
one of that could literally save
lives, which makes the work feel
incredibly meaningful.
But here's the thing this code
base is like a classic car
that's been sitting in a garage
for years.
It's over eight years old, which
in iOS development terms is
ancient.
It was built before Swiss UI
existed, so it's full on all UI
kit.
It's using patterns and
practices that were cutting edge
in 2016 but are now considered
outdated.
We're talking about manual view
controller management, old
networking patterns and
architectural decisions that
made sense at the time but are
now technical debt.
And I love it.
I absolutely love it because
this is exactly the kind of
challenge that gets me excited
about iOS development.
It's like being handed a classic
car that needs a complete
restoration.
Sure, it's going to be a lot of
work, but the end result is
going to be amazing.
So my weekend review.
So what I actually have been
working on since September 25th,
let me break it down into major
themes.
So those are the app delicate
cleanup and modern architecture,
working with the existing
feature flag system,
walkie-talkie and PTT
integration, chat system
refactoring, location tracking
modernization.
Okay, let's get started.
First one.
App Delicate Cleanup and Modern
Architecture.
One of the biggest refactoring
efforts I've been leading is
breaking down the monolithic app
delicate.
You know how it is.
Over the years, the app delicate
becomes this massive file that
handles everything from
authentication to notifications
to background tasks.
It's like that one friend who
always volunteers to organize
everything at a party.
They end up doing everything and
eventually they burn out.
I've been extracting concerns
into dedicated managers like
hiring a team of specialists
instead of relying on one
overworked generalist.
An app lifecycle manager handles
foreground background
transitions like a bouncer at a
club.
Background task manager manages
background task registration and
scheduling, it's more like a
project manager.
Authentication coordinator
handles all biometric and
authentication logic, it's the
security card.
Crash Reporting Manager manages
Sentry app and uses tracking
like detective collecting
evidence.
The key insight here is that
we've been moving from a single
massive class to a collection of
focused testable components.
Each manager has a single
responsibility, making the code
much cleaner to understand and
maintain.
It's like going from a Swiss
Army knife to a proper toolkit.
So the second thing I've been
dealing with is working with the
existing feature flag system.
The app already had a massive
feature flag system in place,
and I've been leveraging it
extensively for my refactoring
work.
Think of it like having a dimmer
switch for your code.
You can turn features up or down
without rewiring the entire
house.
This is crucial when you're
dealing with a critical app that
you can't afford to have any
downtime on.
It's like having an emergency
break on the train.
You hope you never need it, but
when you do, you'll be really
glad it's there.
The feature flags I've been
working on were the following
one: Refactor Alert Detail
Screen for the new alert detail
implementation, a refactor alert
statistic screen for the
statistics screen rewrite, and a
refactor flick buttons for the
flick management refactor.
And then there's the async
location tracking for the new
iOS 17 and 18 location tracking.
This allows me to test new
implementations alongside the
old ones, ensuring I can roll
back quickly if something goes
wrong.
It's like having a safety net
when you're walking a tightrope,
really.
And the third thing I've been
working on is the walkie-talkie
and push-to-talk integration.
One of the most complex features
I've been working on is the
push-to-talk integration for the
walkie-talkie functionality.
This involves real-time audio
streaming over WebSockets, which
is incredibly challenging to get
right.
It's like trying to have a
conversation through a
walkie-talkie while riding a
roller coaster, everything is
moving, everything is changing,
and you need to keep the
connection stable.
Let me tell you about the
debugging nightmare I had last
week.
I was getting reports that the
walkie-talkie was cutting out
mid-conversation, but only on
certain devices.
After hours of debugging, I
discovered that the audio engine
was setting up multiple times,
causing conflicts.
And it also caused the MP volume
fuel to literally move around on
the screen because of this.
That was the issue I talked
about earlier.
It was like having a volume
slider that had a mind of its
own.
And the key challenges I've been
solving is the audio streaming,
encoding and decoding audio in
real time, like trying to
translate a conversation while
it's happening, web socket
management, handling connection
states, reconnection logic, and
retry mechanisms, like having a
backup plan for every possible
scenario, state management,
tracking recording state,
connection state, and audio
playback, like keeping track of
who's talking, who's listening,
and what's happening.
And then of course there's
thread safety, ensuring that
audio processes happen on the
right threads, like making sure
that the right people are in the
right rooms at the right time.
I've consolidated the audio
engine setup to solve this MP
volume view movement issues I
had and implemented the retry
mechanism that attempts
reconnecting up to five times
instead of just two.
The audio buffering system now
runs in separate threads to
prevent clipping and ensure
smooth playback.
It's like having a backup sound
system that kicks in when the
main one fails.
Another thing I've been working
on was a chat system
refactoring.
I've been completely rewriting
the chat system to use a modern
service-based architecture.
The old system uses a singleton
MB chat manager that was tightly
coupled to the UI.
It's like having a chat system
that was permanently glued to
the screen.
The new chat service is much
more modular and testable, like
having a chat system that can be
plugged into any device.
And the key improvements that
I've been working on are an
async await support, modernizing
the matrix SDK integration and
better error handling so that
proper error types and recovery
mechanisms are in place, and
thread safety, again thread
safety, so to make sure that the
matrix SDK that we rely on is
used safely across threads and
making sure that everyone
follows the same traffic rules
there.
And I also wanted to introduce
some more separation of
concerns.
The service handles all the
matrix logics while the UI
focuses on presentations, like
having a chef who cooks and a
waiter who serves.
Everybody has their own
specialized task and we don't
walk in front of each other.
The location tracking system has
been completely overhauled to
use iOS 17 and 18's new async
location tracking APIs.
This is a perfect example of how
iOS evolves and how you need to
adapt your code.
It's like upgrading from a paper
map to GPS.
The old system worked, but the
new one is much better.
The new system includes
geofencing, processing, using
the new CL monitor API for
better performance, like having
a security card who never
sleeps, and background task
manager management, so
background task management,
proper background task handling
for location updates like having
a reliable assistant who always
remembers to check in.
Debouncing, preventing to
prevail prevent excessive server
calls when driving, because
otherwise we get like an update
every five meters, and
especially if you're at speed,
that's a bit much.
And distance filtering so that
we only send location updates
when the user has moved
significantly.
So that's just to make sure that
we don't uh saturate the server.
Alright, let's dive into the
details a little bit more
because this is like a
high-level overview.
Code Deep Dive, the App Delicate
Refactor.
Uh let me show you a specific
example of the refactoring work
that I've been doing.
This is the kind of real-world
problem that every iOS developer
faces when working with legacy
codes.
So the problem here.
The original app delicate was
over 500 lines long and handled
everything.
It was like a Swiss armor knife
that had grown into a full
toolbox.
Imagine if your kitchen had one
giant appliance that was
supposed to be your
refrigerator, your stuff, your
dishwasher, your microwave, and
your coffee make all rolled into
one.
That's what our app delicate had
become.
I remember the first time I
opened the file, I was like,
nope, what the hell is this?
It was handling authentication
and biometric login, push
notification registration,
background task management, deep
linking, security checks,
feature flag refreshing, and
then some other things.
This made it incredibly
difficult to test, understand,
and maintain.
It was like trying to fix a car
when all the parts were welded
together.
You couldn't work on one thing
without affecting everything
else.
Every time I made a change, I
was holding my breath, wondering
if I might have broken something
on the other side of the whole
machine.
So the solution that I came up
with was that I broke everything
down into focus managers.
The new app lifecycle manager is
much simpler, it just
coordinates between the other
services.
When the app comes to the
foreground, it refreshes feature
flags, syncs device info, and
updates the session manager if
needed.
When it becomes active, it posts
notifications and runs security
checks.
The key insight is that each
manager has a single clear
responsibility.
The app lifecycle manager
doesn't know how to handle
background tasks, it just knows
when to tell the other services
to do their job.
It's like having a conductor who
doesn't play any instruments but
knows when each section should
come in.
The benefits of this is that
first of all we have
testability.
Each manager can be tested in
isolation, like having separate
test tracks for each car
component.
Maintainability, changes to one
concern don't affect others,
like having separate rooms in a
house.
Readability, the code is
self-documenting, like having
clear labels on everything, and
there's some more reusability.
Managers can be used in
different contexts, like having
modular furniture that works in
any room.
So the challenges that ever was
facing was that the biggest
challenges was ensuring that the
refractor didn't break existing
functionality.
This is where the feature flag
system became crucial.
I could test the new
implementation alongside the old
one, ensuring a smooth
transition.
It's like having uh renovation
in your house while you're still
living in it.
You need to make sure that the
electricity and the water still
work while you're replacing the
plumbing.
Alright, so let's dive into
feature flags a little bit
because I've mentioned them a
full few times over.
Um the feature flag system
that's already in place in this
code base, it's like a tool that
becomes essential for modern iOS
development, especially when
dealing with legacy code.
Think of it like having a remote
control for your code.
You can turn features on and off
without touching the code
itself.
The code base already had a
well-designed feature flag
system in place.
It's built around an enum that
defines all the different
features with names like
refactor alert detail screen and
async location tracking.
Each feature has a unique
identifier and the default
value.
So, how did I use it?
In the UI code, I can
conditionally use new
implementations.
For example, when someone taps
on alert, I check if the new
alert detail screen feature is
enabled.
If it is, I create a new view
controller with the modern MVM
architecture.
If not, I fall back to the old
storyboard-based implementation.
This pattern is repeated
throughout the app.
Every time I want to use a
refacted component, I first
check the feature flag.
It's like having a switch that
determines which version of the
code gets executed.
So why does this matter?
Feature flags allows you to
deploy safely, test new code in
production without affecting all
users, like having a test
kitchen in a restaurant, and you
can roll back quickly.
If something goes wrong, I can
disable the feature instantly,
like having an emergency stop
button.
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 the
which of these the customers
prefer, and it allows for
gradual rollouts, so enabling
features for a subset of users
first, like having a soft
opening before the grant
opening.
This is especially important for
critical apps like the ones I'm
working on, where downtime could
literally cost lives.
It's like having a backup
generator for a hospital.
You hope you never need it, but
when you do, it's the difference
between life and death.
The existing system has been a
lifesaver for my refactoring
work.
Instead of having to deploy
everything at once and hope for
the best, I can gradually
migrate users to the new
implementation while keeping the
old code as a safety net.
So the lessons that I learned,
um, legacy code is a gift.
That's the first lesson.
Working with legacy code isn't a
burden, it's an opportunity to
learn.
Every piece of technical debt
tells a story about how IRS
development has evolved.
The old patterns made sense at
the time, and understanding why
they were chosen helps me make
better decisions today.
It's like reading a history
book, you can see how we got to
where we are now, and it helps
you understand where you're
going to need to be going.
I've actually started to
appreciate the craftsmanship
that went into this code, even
if it's outdated.
The developers who built this
eight years ago were working
with the tools and patterns they
had available.
They made the best decisions
they could with the information
they had back at the time.
That's something I try to
remember when I refactored their
work.
Refactoring also requires
patience, that's the second
lesson.
You can't refactor everything at
once.
The key is to identify the most
critical areas and tackle them
systematically.
The app delicate refactor took
well over a week, but each step
made the code base a little
better.
It's like renovating an old
house, you can't tear it all
down at once, you have to do it
room by room.
I had to resist the urge to just
rewrite everything from scratch.
That could have been faster in
the short term, but it would
have been significantly more
risky for a critical app like
this one.
Instead, I took the slow and
steady approach, making small
incremental improvements that I
could test and validate at each
step.
So the third lesson: testing is
everything.
When refactoring critical
functionality, testing becomes
your safety net.
The feature flex system allows
me to test new implementations
in production without risking
the entire app.
It's like having a safety
harness when you're rock
climbing.
You hope you never need it, but
when you do, it's the difference
between a minor slip and a major
fall.
I learned this the hard way
early on.
I made a change to the
authentication flow without
properly testing it, and it
broke login for a subset of
users.
That was a wake-up call.
Now I test everything
extensively.
And I use the feature flag
system to gradually roll out
changes to a small percentage of
users first.
Then the fourth lesson is modern
iOS apps are worth the effort.
Sorry, I need to say modern iOS
APIs are worth the effort.
The new location tracking API in
iOS 17 or 18 is significantly
better than the old ones.
Yes, it requires rewriting
existing code, but the
performance and reliability
improvements are worth it.
It's like upgrading from a flip
phone to a smartphone.
Sure, you have to learn new ways
of doing things, but the
capabilities are so much better
that it's worth the effort.
The old location tracking code
was a mass of callbacks and
delicate methods.
The new async await APIs are so
much cleaner and easier to
reason about.
It took me a while to get used
to the new patterns though, but
now I can't imagine going back
to the old way of doing things.
Okay, so let's look ahead a
little bit.
So what's coming up in the next
two weeks?
I'm not exactly sure, but I
expect it will be something to
do with the chat system.
I'm refactoring it and I'm now
80% done.
The remaining work involves
migrating the remaining UI
components to the new chat
service and in comprehensive
error handling, implementing
proper offline support.
It's like finishing the last few
rooms in the house renovation.
The foundation is solid, but
there's some finishing work to
do.
The chat system is one of the
most complex parts of the app,
so I'm taking my time to get it
right.
I also want to finish the alert
detail screen refactor.
The new alert detail screen is
much more complete.
It also uses a modern MVVM
architecture with proper
separation of concerns.
The remaining work is mostly UI
polish and testing.
It's like putting the final coat
of paint on a car.
The engine is running great, but
you want to make sure that it
definitely looks good.
This screen is critical because
it's where users get detailed
information about emergency
alerts.
The old version was clunky and
hard to navigate, especially in
high stress situations.
The new version is much more
intuitive and responsive.
I'm also going to continue the
location tracking modernization.
The new location tracking system
is working well, but I want to
add more sophisticated UFencing
logic and improve the background
task management.
It's like having a GPS that
works, but now I want to make
sure that it's smart enough to
avoid traffic jams.
So location tracking is crucial
for this app because it's used
to determine who's in the area
when an emergency happens.
The more accurate and reliable
it is, the better the emergency
response can be.
And then of course there's my
cleanup branch, because I've
been doing this on the side.
I've also been working on a
massive cleanup branch that's
been running in parallel.
That's the kind of work that
doesn't get much attention but
is absolutely crucial for
maintaining a healthy code base.
It's like doing uh spring
cleaning, not glamorous but
necessary.
I actually enjoy this kind of
work.
There's something satisfying
about removing that code and
fixing warnings.
It's like cleaning up your
workspace.
You feel more productive when
everything is organized and
tidy.
This cleanup work includes
removing deprecated APIs,
getting rid of the old API that
we don't longer want to support.
I've been just fixing warnings
because there were like hundreds
of warnings.
So cleaning up compiler warnings
and deprecation notices,
removing that code, eliminating
code that's no longer used
anywhere anywhere.
So there were large chunks of
code that were not being called
into.
And I wanted to modernize a few
of the patterns that were used.
So updating old patterns to use
modern Swift UI features and
Swift features.
You don't notice it when it's
working, but when you but you'll
definitely notice when it's not.
So there was also a bug fix I
needed to do, uh Wi-Fi settings
related.
I've been working on this bug
fix for the Wi-Fi settings not
being configurable.
Uh so it used to be that this
worked, but now with iOS 26 it
doesn't.
So this is a perfect example of
how legacy code can have subtle
issues that only serves when
you're doing other refacting
work.
The Wi-Fi management system was
using deprecated APIs and had
some threading issues that were
preventing users from properly
configuring their Wi-Fi networks
for presence tracking.
So here's the story.
I got a support ticket from a
user who couldn't configure
their Wi-Fi network.
They were on iOS 26, and the
Wi-Fi setting screen was
completely broken.
At first I thought it was a UI
issue, but after digging deeper,
I discovered something much more
interesting.
The issue was that the app was
still using the old Wi-Fi API
that was deprecated since iOS
14.
Specifically, it was using CN
copy current network info from
the system configuration
framework, which was deprecated
in iOS 14.
And here's the kicker.
It seems like this API stopped
working completely in iOS 26, so
users on the latest iOS versions
couldn't configure their Wi-Fi
networks at all, which is pretty
critical for an emerging
response app that relies on
presence tracking, right?
So the fix involved completely
modernizing the Wi-Fi validation
system, moving away from the
deprecated C and copy current
network info API to the modern
NEHotspot Network.fetchCurrent
API that's been available since
iOS 14.
This new API uses uh well uses
uh completion handling, but I
wrapped it in an async await
pattern, which is much more
reliable.
I also had to ensure that the UI
updates happen on the main
thread and fix some race
conditions in the validation
logic.
The key change was replacing the
old synchronous API call with a
new async version and wrapping
it in a proper continuation to
handle the completion-based API.
It's a perfect example of how
Apple's API evolution requires
us to constantly update our
codes 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's
a great reminder of why keeping
up with API deprecations is so
important, especially when
you're dealing with critical
functionality.
So, and that's a wrap on the
second App Force One worklock
episode.
I hope this gives you a real
sense of what it's like to work
on a complex legacy iOS app in
2025.
The key takeaways is that uh
refactoring legacy code isn't
just about making it modern,
it's about understanding the
business context, the technical
the technical constraints and
the user impact.
Every decision I make was has
real consequences for people who
depend on this app in emergency
situations.
It's like being a surgeon, you
can't just focus on the
technical aspects, you have to
remember that there's a real
person on the operating table.
In the next episode, I'll dive
deeper into the walkie-talkie
audio streaming most likely, and
share some of the debugging
challenges I am definitely going
to be facing in the next week.
I'll also talk perhaps about the
Matrix SDK integration and how
I'm handling real-time
management messaging.
Think of it like a
behind-the-scenes look on how
the magic happens.
Before I wrap up, I want to
mention that I'm organizing a
conference called Do iOS, which
is happening later this year.
It's going to be an amazing
event focused on iOS
development, which talks uh with
talks from some of the best
developers in the community.
If you're interested in learning
more about iOS development,
networking with other
developers, and getting inspired
by the latest trends and
techniques, definitely check out
do iOS.com for more information
on the tickets.
Make sure to check the link in
the show notes.
So, until next time, keep
building amazing apps and
remember every expert was once a
beginner.
The key is to keep learning,
keep building, and keep sharing
what you learn with the
community.
Thank you for listening, and
I'll see you next week for the
next worklock episode.
I'm really excited to share more
of this journey with you.