7 Tips to Boost Performance of iOS App

Do you have your business purpose and oriented iOS based application? To have the presence is beneficial only if its speed, features and performance meets the users’ expectations. Features and functionalities including sometimes leads to make the app size heavy which leads to lots of loading time and occupying space in the devices – may users bounce back due to this. Below are smart quick tips to get rid of the performance issue in iOS application.

1. Decrease the number of Views & Transparent Views

The first easy thing needs to do, in order to improve the performance in your application, is (whenever possible) to:

  • Reduce the Number of Views
  • Avoid Transparency

If you have views that have no transparency defined (opaque view) — you should set their opaque property to YES. Now the question arise is WHY to do so? – Reason – By doing this, it will allow the system to draw your views in an optimal manner. This is a property that can be set in both Interface Builder and code.

As per Apple Documentation – by this property, drawing system gets the hint for treating the view. If it’s set to YES, the drawing system treats the view as fully OPAQUE, which allows the drawing system to optimize and improve performance by executing some drawing operations. If it’s set to NO, the drawing system composites the view normally with other content. The default value of this property is YES.

On static screens, setting the opaque property won’t be a big deal. If view is embedded in a scroll view, or is part of a complex animation, not setting this property will definitely impact the performance of your app!

  • Note: You don’t need to worry about this opaque, if you are adding UI controls directly from xib.
  • Keep in mind that set a value for the opaque property in subclasses of UIView that draw their own content using the drawRect: method.
  • This opaque property has no effect in system-provided classes such as UIButton, UILabel, UITableViewCell, and so on.

2. Do Not Block Main Thread

One most important thing to learn when developing for the iOS platform is to never block the main thread. If you are blocking the main thread with a long running operation, such as downloading images or other resources, your application will become unresponsive as long as the operation isn’t completed. Keep practicing and make it a habit to move long running tasks to a background thread.

With the introduction of Grand Central Dispatch, writing safe multithreaded code has become a lot easier. Take a look at the following code snippets –

extension DispatchQueue {
 
    static func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) {
        DispatchQueue.global(qos: .background).async {
            background?()
            if let completion = completion {
                DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
                    completion()
                })
            }
        }
    }
 
}

Usage

DispatchQueue.background(delay: 3.0, background: {
    // do something in background
}, completion: {
    // On completion of background job  , wait 3 seconds and do something in main thread
})
 
DispatchQueue.background(background: {
    // do something in background
}, completion:{
    // On completion of background job, do something in main thread
})
 
DispatchQueue.background(delay: 3.0, completion:{
    // do your stuff in main thread after 3 seconds
})

Note: Every UI update must be take place on main thread. Also, for API calling we recommend you to use third party library AFNetworking.

3. Minimise the load of work done in functions called continuously

Functions like cellForItemAt indexPath or scrollViewDidScroll are called continuously, so they must be very fast.

Always make sure to have the “dumbest” views/cells as possible, with configuration method(s) that are always very light. (E.g. no layout constraints involved, allocations of objects etc.)

4. Off-screen Rendering

When we have to deal to specific properties of our UI Elements, we may have some off-screen rendering issues, due the fact that we need to prepare those elements before presenting them. That means in other terms a heavy use of CPU and GPU.

Below is a very typical example regards the use of the cornerRadius property. If you have something as below in your app:

imageView.layer.cornerRadius = avatarImageHeight / 2.0
 
extension UIImage {
  class func getcircularImage(from image: UIImage, size: CGSize) -> UIImage? {
      let scale = UIScreen.main.scale
      let circleRect = CGRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale)
 
      UIGraphicsBeginImageContextWithOptions(circleRect.size, false, scale)
 
      let circlePath = UIBezierPath(roundedRect: circleRect, cornerRadius: circleRect.size.width/2.0)
      circlePath.addClip()
 
      image.draw(in: circleRect)
 
      if let roundImage = UIGraphicsGetImageFromCurrentImageContext() {
          return roundImage
      }
 
      return nil
  }
}

Always try to:

  • Avoid CornerRadius property;
  • KeepAvoiding to use ShouldRasterize unless you really know it helps;
  • Using .rounded() values whenever possible, it will be more easy to compute.
  • Keep a note that also Shadows can cause off-screen rendering.

5. Size Images to Image Views

If you’re retriving an image from the app’s bundle in a UIImageView, make sure that the image and the UIImageView are same size. Image scaling can be expensive, especially if your UIImageView is embedded in a UIScrollView.

If the image is downloaded from a remote service, sometimes you don’t have control over the size, or you might not be able to scale it on the server prior to downloading. In these type of cases, you can scale the image manually once you’ve finish downloading it — preferably on a background thread! — After use the resized image in your UIImageView.

6. Reuse Expensive Objects

Some objects are very slow to initialize — DateFormatter and Calendar are two examples. However, avoid uses of them will not be always possible such as when parsing dates from a JSON/XML response.

To avoid performance bottlenecks when working with these objects, try to reuse these objects if at all possible. This can be done by either adding a property to your class, or by creating a static variable.

Note that if you choose the second approach, the object will remain in memory while your app is running, much like a singleton.

Also remember that setting a DateFormatter’s date format is almost as slow as creating a new one! Because of that, if you frequently need to deal with multiple date formats in your app, your code may benefit from initially creating, and reusing, multiple DateFormatter objects.

7. Release all references belongs to viewcontroller

Always make sure that whenever your class is released or pop away from stack, all references to your viewcontroller should get deallocated to free up memory. You can check this by overriding deinit method in viewcontroller class. This method will be called whenever your class or viewcontroller get released. Also, make sure that you must have written something in your deinit method. Because if your deinit method is empty than also it will not be called even if all the reference to your viewcontroller are deallocated. e.g.,

deinit {
print(“deinit is called”)
}

Let’s create an example with a retain cycle: First we are creating a RootViewController and a SecondViewController. The SecondViewController gets presented, if a button on theRootViewController is pressed. You can do this easily by using a segue in the storyboard of the app.

Furthermore, there is a class called ModelObject, which has a delegate object of typeModelObjectDelegate. Once the SecondViewController’s view is loaded, the controller sets itself as the delegate of the ModelObject: import Foundation

protocol ModelObjectDelegate: class {
 
}
 
class ModelObject {
 
    var delegate: ModelObjectDelegate?
 
}
 
import UIKit
 
class SecondViewController: UIViewController, ModelObjectDelegate {
 
    var modelObject: ModelObject?
 
    override func viewDidLoad() {
        super.viewDidLoad()
        modelObject = ModelObject()
        modelObject!.delegate = self
    }
 
    @IBAction func closeButtonPressed(sender: UIButton) {
        dismissViewControllerAnimated(true, completion: nil)
    }
 
}

Ok, now examine the memory handling: If we are dismissing the SecondViewController, the amount of used memory is not decreasing. But why is this? We can expect that with the dismiss of the SecondViewController memory gets deallocated.

Let’s take a look at the objects. Once the SecondViewController is loaded, it looks like this:

SecondViewController is loaded

Now, if the SecondViewController is dismissed, it looks like this:

SecondViewController is dismissed

Here the RootViewController doesn’t have a strong reference to the SecondViewController anymore. Here, the SecondViewController and the ModelObject have strong references to each other. And because of this they are not reallocated.

The Trick
The trick to discover these kind of problems is the following: If an object gets deallocated, thedeinit method will be called. So just print a log message in the deinit methods of your objects:

class SecondViewController: UIViewController, ModelObjectDelegate {
 
    var modelObject: ModelObject?
 
    override func viewDidLoad() {
        super.viewDidLoad()
        modelObject = ModelObject()
        modelObject!.delegate = self
    }
 
    @IBAction func closeButtonPressed(sender: UIButton) {
        dismissViewControllerAnimated(true, completion: nil)
    }
 
    deinit {
        print("SecondViewController deinit")
    }
}
 
import Foundation
 
protocol ModelObjectDelegate: class {
 
}
 
class ModelObject {
 
    var delegate: ModelObjectDelegate?
 
    deinit {
        print("ModelObject deinit")
    }
 
}

Here if we dismiss the SecondViewController, there are no log messages in the debugger! It means they are not reallocated, so something is going wrong.

The Solution
As we know already that there is one strong reference too much. So let’s change  delegate to a weak property:

protocol ModelObjectDelegate: class {
 
}
 
class ModelObject {
 
    weak var delegate: ModelObjectDelegate?
 
    deinit {
        print("ModelObject deinit")
    }
 
}

The object graph looks now like this:

Weak Reference

Here because of just one strong reference between the SecondViewController and the ModelObject, we expect that there will be no problems anymore. And indeed, the following log messages appear in the debugger, if we dismiss theSecondViewController:

SecondViewController deinit
ModelObject deinit
Next article

Angular has come out with some amazing new features in version 6.0, especially in Angular-CLI. This is a major release focused more on the toolchain....

Comments

  • Leave a message...