Common programming patterns to avoid crashes in iOS applications.

Jatin Mishra
5 min readJan 31, 2024

--

Photo by Michael Jin on Unsplash

Crashes — the most dreaded and dangerous enemy of developers. Majority of the bugs can never match the destruction made by the crashes in user experiences and app reliability. Hence, it becomes a very high priority of a dev team to fix (optimally avoid) crashes as soon as possible.

Last year I got the privilege to be the `First line of defence` (fortunately or unfortunately not sure :) ) against these dreaded things. I got a fair share of experience (headaches could be a better word) in finding, debugging and fixing them.
These crashes can occur due to unsafe programming, ineffective usage of memory and resources causing the app to be terminated (same effect as a crash) by the operating system.
In this blog we will be focussing on the programming side of things.
Upon deeper analysis I found some of the common development patterns of these crashes which can be avoided/optimised to prevent these crashes from coming into existence.

Sharing these so that I can reduce some of the pain of my fellow developers.

Force unwrapping done anywhere is not safe, period.

Force unwrapping is the biggest contributor in crashes. It’s something like you are declaring a variable as optional (it can have a value or cannot) but when you accessing it you are saying “you have a value, you just don’t know it”. Force unwrapping itself is a red flag (an anti-pattern you can say), you are yourself contradicting your thought about an optional variable i.e. at any point of its lifetime it can or cannot have a value. So, how can you be 100% sure that it will have a value at a specific point of time.

Positively do not use force unwrapping, if you are still forced to do it consider the code structure or flow you are using. There should be a way without using force unwrapping.

Accessing index in an array, are you 110% sure this index exists??

Another common mistake we do is accessing an array with invalid index. In generic scenarios this is very less likely to happen but we should be well aware of all the contexts from which this access can occur, probably a multi threaded one?
Apart from plain subscript access, Swift has some array APIs which are risky to use.
Say you want to remove the last element in an array. Swift provide two methods to do this.
popLast() -> This method returns an optional so if the array is empty we get a nil
removeLast() -> This method has a non-optional return value so if the array is empty it will cause a crash!

Always be extra cautious while doing any manipulation in an array (or collection to be more Swifty)

Error handling done effectively and SAFELY!

Swift provide some options to handle unexpected conditions in the code flow by halting the execution of the app. These are like “Self-Destruct” actions which are required when there is absolutely no way to recover and continuing would do more harm than good.

We will quickly run through the three options Swift provides:
assertionFailure() -> This method will stop the app execution only in DEBUG environment.
preconditionFailure() -> This method will stop the app execution in both DEBUG and RELEASE environment.
fatalError() -> As the name implies is simply used to stop app execution regardless of the environment or conditions.

As developers, it is our duty to use these above methods ONLY if there is no way possibly to continue the execution and if the execution continue it would result in very dangerous outcomes.

Concurrency and Multi-threading is a double edged sword, use it with extra caution.

In present times since concurrent programming is made so easy via multiple level of abstractions, we use it many times unknowingly, at least until no weird issue surfaces in our executions flows.
Majority of the variables we use are not inherently atomic in nature i.e. they cannot be safely used by multiple threads at once. If these variables are accessed via invalid threads it can cause crashes.

A common example is lazy variables. Lazy variables are praised and used widely to improve performance by delaying the variable initialisation until the variable is accessed actually. But they have a big downside, they are not thread safe, accessing these from multiple threads can cause incomplete initialisations and cause crashes due to unexpected code executions.
Structs are also a common candidate of invalid thread access issue, so analyse its usage accordingly and if being used in a multi thread environment consider making them thread safe.

Object’s instance got deallocated but still dangling around, it should crash shouldn’t it?

Sometimes at the moment of deallocation of an object instance it is still being retained (referenced by other variables) this makes the pointer of the deallocated `dangling` and when accessed from the other retained places it causes a crash due to invalid memory pointer.
To avoid this random crash Swift enforces a rule that if the object gets deallocated with a retain count greater than 1 the runtime crashes (using a fatalError)

Dangling references can be caused if self is referenced inside the `deinit` block of the object in such a way that it outlives the `deinit` scope e.g. calling an async task for some cleanup operations with strong reference.
Another possible reason of dangling pointer could be again a threading access issue where a particular thread tries to deallocate an object instance and the other thread tries to retain/access it at the same time.
To avoid it obviously we have to make this variable in discussion thread safe and avoid retain cycles.

[unowned self] literally has no owner so be alert when it’s used.

To avoid retain cycles caused by capturing strong self references we have two options `unowned` and ‘weak’ references. Both of these do not increase the retain count but the key difference is that the `weak` reference is an optional and the `unowned` reference can not be nil. So if we try to access [unowned self] when the self instance is deallocated it results in a crash.
`unowned` references are supposed to be optimised as it does not have to deal with the overhead of being optional variable but it surely comes with a big risk of a crash if not used perfectly.
In my opinion the safety of [weak] references always surpass the overhead it has. On the other hand I have witnessed numerous crashes due to [unowned] with little to un-noticed gain in performance.

Conclusion

To conclude, if we try and notice the above patterns we can resolve majority of the crashes but the best approach is to have a very keen eye on every line of code added/modified in the codebase and deep analysis of all possible contexts/flows in which the said code executes.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Jatin Mishra
Jatin Mishra

Written by Jatin Mishra

Senior iOS Engineer with 6+ years of experience, specializing in high-performance app development and architecture optimization. Join me for special insights.

No responses yet

Write a response