Top 8 iOS Development Bugs and How to Avoid Them

Written by: Roger Oba
12 min read

So, you just landed in iOS Development Town, and you want to make sure that everything goes as smooth as possible. No bugs, glitches, crashes, lags or other issues, right? I thought so . In this article, I bring you the eight most common iOS development bugs and crashes that you'll probably face while being a junior developer, and also mid level or senior developer. After all, let's be realistic, we can't write perfect, flawless code all the time! I categorized the bugs by experience. These are just my opinion on them of course since development seniority level is something really arbitrary.

#1 Forgetting things. Everywhere! (Junior)

Storyboards are one of the most arguably loved and hated elements in Xcode. However, even those who choose not to use them, most will probably defend the idea that xibs are the way to go. Having said that, you're not safe from forgetting to link something to your classes if you choose to use the interface builder to quicken up your development. When working with IB components, there are a few things that must be connected, and some that you'd love to have connected to save some coding lines, keep your code cleaner and sometimes even less cluttered. To use custom classes with your components, you'll have to declare them in your interface builder's Identity Inspector.  

  That's for any custom class that you may be using with any interface element. If you forget to add your custom class to your Identity Inspector, the component will have no idea that you have a beautifully written class waiting for it with your name on top. That means no delegate or data source methods will be called, and you also won't be able to link IBOutlets and IBActions to from your interface to your custom class. These behaviors won't make your app crash at first, but if you, for instance, have a situation where you have a custom class call a custom-class-specific method that you created, that will give you a runtime error.


Following the same concept, if you forget to link your component to the  IBOutlet or  IBAction that you had previously created, you might find yourself using a variable that is not what you think it is: because it's not linked to your component. Likewise, you'll experience a runtime error if you connect your interface element to an  IBOutlet /IBAction and then delete or rename that outlet/action from your code because it'll still think it's connected and try to execute it. That's something Apple could improve, definitely, but until then, we have to pay attention to these details, even though it's hard to let this error pass unnoticed.

Delegate/Data source

Lastly, something that happens fairly often if you’re still a beginner (and maybe will still happen no and then even being a mid/senior dev) is forgetting to connect your component to a delegate or data source class. The result of this lack of attention is not having your delegate/data source methods being called as you’d expect. That’s a silent error and won’t crash your app (unless you make explicit calls to the component’s delegates), so you must notice that the methods aren’t being called at all, and connect it to the respective delegate/data source classes. This is a step that some people prefer to do programmatically since it gives a notion of clarity as to what’s going on with that component without having to open the interface builder. Alternatively, it can also be done via interface builder, just dragging a connector between the component and the correspondent class (e.g. its view controller).  

Press and hold Control, and drag your component to your view controller

#2 NullPointerException (Junior)

Dealing with an yet unallocated object doesn’t throw an exception like in Java and other languages, but instead, it doesn’t apply any changes to the object since it virtually doesn’t exist. In case you forget to allocate memory for it, it might take a while until you figure out what’s going on with your implementation, so keep an eye for that. A common use case that probably had happened to everyone when they began developing for iOS is creating a property in a view controller like, let’s say, an NSMutableArray, adding objects to it, and it still seems to be nil. You won’t get a runtime error for doing that, so you should always remember to allocate and init your objects (calling the class method new, for example) before using them.

if (self.mutableArray == nil) {
    self.mutableArray = [NSMutableArray new]; //or [NSMutableArray array];
[self.mutableArray addObject:[MyObject new]];

#3 Navigation Glitches (Junior)

Using the navigation controller is awesome, right? It automatically manages views hierarchy, has inbuilt swipe-to-go-back gesture, automatically manages the navigation bar titles and back buttons for you… All good. The problem is that Apple lacks some documentation on how to properly integrate UINavigationControllers with a UITabBarController. If you use navigation controllers inappropriately with tab bar controllers, you might end up with unexpected navigation behaviors and mainly animations.   These are two ways that you probably should not implement it:  

UITabBarController and UINavigationControllers - Wrong Layout #1

  You should use this approach only if your application has more than 2 tab bar controllers, which I think the chances are way too low (I personally have never seen an app doing that, and I totally discourage such design, UX-wise).   Another implementation that should be avoided is the following:

UITabBarController and UINavigationControllers - Wrong Layout #2

  The user could lead the user to experience more unexpected behaviors and animations with this structure than with the previous one. The correct way to implement a navigation flow using tab bars is as follows:

UITabBarController and UINavigationControllers - Correct Layout

  Note how Xcode even place things in a better layout: now the Tab Bar Controller has its title inside its screen and displays each tab accordingly in its own tab bar, unlikely the previous layouts. To understand this, put it like this: the tab view controller defines up to 5 (or more, actually) different navigation flows, so each flow should have its own navigation controller. If one or more view controllers don't have a navigation (it's just a standalone view controller) you obviously don't have to use a navigation controller for that specific view controller, even though there's no harm in using it (and you also get the handy navigation bar ready to use). If you’ve experienced weird navigation animations and glitches like a black navigation bar or screen flickering, probably you’ve done one of the 2 first examples of implementation. If you haven’t experienced weird glitches but have implemented it, probably it’s a matter of time until you (or your users!) notice it.  

#4 Dictionaries and Nil (Junior/Mid)

While handling dictionaries, it’s important to make sure that the object you’re adding as the value for a key is actually a valid object. That is because dictionaries cannot have NULL or nil as a value for a key, so if you try to insert one of those values (or lack of values) inside one, you’ll face a crash. A very typical case where this happens is when you’re sending parameters to a web request, or parsing analytics attributes.

if ( == nil) {
   //log analytics that the user cleared his personal info
   NSDictionary *attributes = @{@“bioInfo”}; //crash on this line
   [AnalyticsManager logEvent:kEvent withAttributes:attributes];

If you’d like to add a null value to a dictionary, you’ll have to add it as an NSNull object: attributes = @{@"nullInfo":[NSNull null]};   

#5 Cells With Resizable Height (Mid)

Here's something that probably you've already stumbled into or certainly will throughout your career: cells with resizable (or dynamic) height. Upon iOS 8, this wouldn't work as you expected, it had to be done manually, calculating the height of each element inside of the cell, and then use table view's data source methods to set the height... Blah, too much effort. Since we're already running iOS 10.3 as of today, I'll assume that supporting only iOS 8+ won't an issue for you. Following that, what you have to do is simply set these table view's properties:

//put this in your viewDidLoad (or similar)
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 44.0;

Note that you still need to build your cell using Auto Layout in your favorite interface builder, but now it's way easier. The estimated row height property will be used to calculate the approximate size of your scroll view, to display an estimated scroll bar size. It doesn't have to be precisely correct, it's just an estimate, but if your cells may differ by an order of magnitude, you should consider implementing table view's tableView:estimatedHeightForRowAtIndexPath:  data source method for higher complexity logic.  

#6 Observers (Mid)

Whether using KVO (Key-Value Observing) or NSNotificationCenter, you'll need to register your class (mostly view controller) to listen to those changes and notifications. What needs extra attention, though, is that you also need to unregister that same class upon its deallocation. Otherwise, your app will crash immediately when the referred class is deallocated. That happens mostly because your observer would now be pointing at a null pointer, causing your app to crash. This is a tricky development bug because sometimes it won't log any error in your console, nor display any signs that something is going wrong - it just kills your app. Luckily this has an easy and quick fix, which's removing all the observers upon class deallocation, which can be safely done inside the object's lifecycle delegate method dealloc.  

#7 Retain Cycles (Mid/Senior)

Now more from a mid-senior perspective, I’ll introduce one of the most common retain cycles that are caused by automatic reference counting (ARC) and, of course, how to avoid it.   Retain cycles happens when an Object A retains Object B (using a strong reference) and Object B also retains Object A. So, what happens is that whenever ARC try to deallocate either object since Object B holds a reference to Object A, this one won't be deallocated. Likewise, neither will Object B, because Object A also retains a reference to it. In the long term, this can cause severe performance issues with your app since both objects will just hang around in memory during the whole life of the program, even though they should be deallocated. A typical occasion where this happens is when dealing with blocks that hold strong references to self. E.g.:

[self fetchObjects:^(NSError *error, NSArray *objects, ) {
   if (!error) {
       [self updateObjectsWithData:objects];
   } else {
       //treat error accordingly…

What happens here is that self has an explicit strong reference to the block and the block has an implicit strong  reference to self, which characterizes a cycle, meaning neither of them will be deallocated properly.   The easiest way to fix this is to use an explicit weak reference to self so the block can use it safely:

__weak typeof(self) weakSelf = self;
[self fetchObjects:^(NSError *error, NSArray *objects, ) {
   if (!error) {
       [weakSelf updateObjectsWithData:objects];
   } else {
       //treat error accordingly…

But be aware that the implementation above shouldn’t be used without discernment. It should only be used to break what would otherwise be a retain cycle between self and the block. See the code sample below:

__weak typeof(self) weakSelf = self;
[[APIManager alloc] initWithCompletion:^(NSError *error, NSArray *objects, ) {
   if (!error) {
       // here "weakSelf" might be `nil` because it's not being retained
       [weakSelf updateObjectsWithData:objects];
   } else {
       //treat error accordingly…

  The example above is not advised and could cause unexpected behaviors like not having the code executed at all because weakSelf was deallocated by the time the block was being executed. This example is just one particular case of the many ways to have a retain cycle. Other not-so-obvious ways include large chains of objects that end up retaining themselves and having relational object models that reference to themselves. Note that retain cycles are not ARC-specific, but ARC made it very comfortable for us to code without worrying about releasing references manually, and that's why this may become a huge problem in your projects as they get more complex.  

#8 System Signal Flag Exceptions (Senior)

The system signals are used to tell the developer what caused that particular crash. Facing these signals is not something that only senior developers will see, but understanding deeply what each of them means is something that senior developers must know.   SIGBUS & SIGSEGV These signals indicate that an invalid address is trying to be accessed. While SIGBUS indicates a bus error, it occurs when the physical address is invalid, as it doesn't exist or more usually has an invalid alignment. SIGSEGV means a segmentation violation occurred, and it's the most frequent crash on iOS, composing almost 50% of all crashes - mostly due to its generic type. It's very similar to SIGBUS, but it happens when the logical address is invalid, or, in other words, when your app tries to access memory that has not been allocated yet or has been freed recently.   SIGABRT If you're debugging your app, you'll see this abort signal when there's an unhandled exception. Though, if your app has already been deployed, you'll see this signal only when an assertion failure happens, or an abort method is called. When looking at the stack trace after a SIGABRT crash, if it looks obfuscated, chances are that Apple's code called the abort method. Unfortunately, this doesn't mean you found a bug in the OS code - the crash is still likely to be your fault :( - but that your app just reached an unexpected state and had to be aborted. There are many other signals, but the ones listed above are the most common ones. For a complete list, check Apple's open sourced Signal.h header file.


From the most common development bugs and crashes you saw in this article, I'd definitely recommend keeping an eye on inattention if you're still forgetting components unlinked when you're developing your UI elements. If you're more of an experienced one, take the UX of your app to the next level keeping an eye on UI glitches and annoying behaviors and animations that will definitely catch the user's attention. For the most senior guys, I'd definitely start looking at the app's performance, so start profiling your app and seeing if there are any zombie objects, memory leaks, retain cycles, and anything that could impact the usage of the app in the long term. The road to becoming a senior app developer is tough but extremely rewardful. You'll learn a lot in your way to the top, and there are two advantages there: the more you learn, the easier it gets, and also the more you can teach to less experienced fellows. So spread the word and share the knowledge you acquired on this article!   Did I miss anything? Let me know in the comments what bugs you think I should have included.

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.