High Performance iOS Apps - Reading Note - Part I

Defining Performance

be related one or more of the considerations

  • performance metrics (what we want to measure and monitor)

  • measurement (actually collecting the data)

Performance Metrics

  • memory(Chapter 2)

  • power consumption(Chapter 3)

  1. an extremely important factor
  2. not only minimize power consumption but also ensure that the user experience is not degraded
  3. Your data structures and algorithms must be efficient in terms of execution time and CPU
    resources, but you also need to take into account various other factors.
  • initialization time(Chapter 5)
  1. Check if the app is being launched for the first time.
  2. Check if the user is logged in.
  3. If the user is logged in, load previous state, if applicable.
  4. Connect to the server for the latest changes.
  5. Check if the app was launched with a deep link. If so, load the UI and state for the deep link.
  6. Check if there are pending tasks from the last time the app was launched. Resume them if need be.
  7. Initialize object and thread pools that you want to use later.
  8. Initialize dependencies (e.g., object-relational mapping, crash reporting system, and cache).
  • execution speed(Chapter 4,6,7,11)
  • responsiveness(Chapter 4,5,6,10)
  • local storage
  • interoperability(Chapter 8)
  • network condition(Chapter 7)

must work in all of the following scenarios:

  1. High bandwidth and persistent network
  2. Low bandwidth but persistent network
  3. High bandwidth but sporadic network
  4. Low bandwidth and sporadic network
  5. No network
  • bandwidth(Chapter 7)
  • data refresh(Chapter 5,7)
  • multiuser support(Chapter 9) ignore
  • single sign-on(Chapter 9) ignore
  • security(Chapter 9) ignore
  • crashes(Chapter 12)

App Profiling

There are two ways to profile your app to measure the parameters that we have dis‐ cussed: sampling and instrumentation.

  • Sampling (Use sampling for initial performance explorations and to track CPU and memory utilization.)
  • Instrumentation (Because instrumentation involves injecting extra code, it does impact app performance—it can take a toll on memory or speed (or both).)

Measurement

  • Crash Reporting
  • Instrumenting Your App(recording user action in app)
  • Logging (CocoaLumberjack)

Memory Management

Memory consumption (i.e., how an app consumes memory)

The memory management model (i.e., how the iOS runtime manages memory)

Language constructs—we’ll take a look at Objective-C constructs and the available features you can use

Best practices for minimizing memory usage without degrading the user experience


Reference types

Strong references

A strong reference is the default reference created. Memory referred to by a strong reference cannot be relinquished. A strong reference increases the refer‐ ence count by 1, resulting in extension of the object’s lifetime.

Weak references

A weak reference is a special reference that does not increase the reference count (and hence does not extend the object’s lifetime). Weak references are a very important part of ARC-enabled Objective-C programming, as we explore later.

Other Types of References

Soft references

A soft reference is exactly like a weak reference, except that it is less eager to throw away the object to which it refers. An object that is only weakly reachable will be discarded at the next garbage collection cycle, but an object that is softly reachable will generally stick around for a while.

Phantom references

These are the weakest references in terms of strength, and are the first ones to be cleaned up. A phantomly referenced object is similar to a dealloced object, but without the memory actually being reclaimed.

These reference types do not have signifance in a reference count–based system. They are more suited for a garbage collector.


ARC also introduced four lifetime qualifiers for variables:

strong
This is the default qualifier and does not need explicit mention. An object is kept in memory as long as there is a strong pointer to it. Consider it ARC’s version of the retain call.
weak
This indicates that the reference does not keep the referenced object alive. A weak reference is set to nil when there are no strong references to the object. Consider it ARC’s version of an assignment operator, except with the added safety that the pointer is automatically set to nil when the object is dealloced.
unsafe_unretained
This is similar to
weak except that the reference is not set to nil when there are no strong references to the object. Consider it ARC’s version of an assignment operator.
__autoreleasing
Used for message arguments passed by reference using id *. It is expected that the method autorelease will have been called in the method where the argu‐ ment is passed.

Property Qualifiers

strong

Default, indicates a __strong relationship.

weak

Indicates a __weak relationship.

assign

This is not a new qualifier, but the meaning has now changed. Before ARC, assign was the default ownership qualifier. With ARC enabled, assign now implies __unsafe_unretained.

copy

Implies a __strong relationship. Additionally, it implies the usual behavior of copy semantics on the setter.

retain

Implies a __strong relationship.

unsafe_unretained

Implies an __unsafe_unretained relationship.

Because assign and unsafe_unre tained only copy over the value without any sanity check, they should only be used for value types (BOOL, NSInteger, NSUInteger, etc.). They must be avoided for refer‐ ence types, specifically pointers such as NSString and UIView .

However, when we call the method createUnsafeUnretainedPhoto again, we see not only that the object has been dealloced but also that the memory has been reclaimed and repurposed. As such, using the reference resulted in an illegal access, causing the app to crash with a signal of SIGABRT. This is possible if the memory is reclaimed at a later time (after the object deallocation but before the object access).
You will notice that the memory was reclaimed just before the title property was set, resulting in an unrecognized selector sent to instance error because the memory is gone and may be now used by some other object.

Zombies

Zombie objects are a debugging feature to help catch memory errors.
Normally when an object’s reference count drops to 0 it is freed immediately, but that makes debugging difficult. If zombie objects are enabled, instead of the object’s mem‐ ory being instantly freed, it’s just marked as a zombie. Any further attempts to use it will be logged, and you can track down where in the code the object was used past its lifetime.

Common Scenarios for Retain Cycles

  • Delegates
  • Blocks
  • Threads and timers

    clean up timer

1
2
3
 -(void)didMoveToParentViewController:(UIViewController *) parent { 
if(parent == nil) { [self cleanup]; } } -(void)cleanup {
[self.timer invalidate]; }
  • Observers

Similar to the key-value observer, the notification center does not keep a strong reference to the observer. This means that we are not responsi‐ ble for ensuring that the observer is not dealloced earlier or later than intended.

  • Returning Errors

NSError * __autoreleasing *error;

  • Finding Mystery Retains

disable ARC and add following code

1
#if !__has_feature(objc_arc)
-(id) retain {
DDLogInfo(@"%s %@", __PRETTY_FUNCTION__, [NSThread callStackSymbols]); return [super retain];
}
#endif

Memory Usage in Production

1
2
3
4
5
6
7
8
9
#import <mach/mach.h>

vm_size_t getUsedMemory() { task_basic_info_data_t info; mach_msg_type_number_t size = sizeof(info); kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t) &info, &size); if (kerr == KERN_SUCCESS) {
return info.resident_size; } else {
return 0; }
}
vm_size_t getFreeMemory() { mach_port_t host = mach_host_self(); mach_msg_type_number_t size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t pagesize; vm_statistics_data_t vmstat; host_page_size(host, &pagesize); host_statistics(host, HOST_VM_INFO, (host_info_t) &vmstat, &size); return vmstat.free_count * pagesize;
}

Energy

CPU

The quantum of compu‐ tation depends on various factors:

  • Processing to be done on the data (e.g., applying formatting to the text)
  • Size of data to be processed—larger displays allow your software to present more information in a single view, which in turn means more data to be handled
  • Algorithms and data structures used to process the data
  • Number of times an update is to be performed, particularly if the data update results in the app state or UI updating (push notifications received by the app may also result in a data update, and if the user has the app open, you may need to update the UI as well)

rules to reduce the execution on the device

  • Use the optimal algorithm for the scenario
  • If the app receives data from the server, minimize the need for processing on the client side
  • Optimize ahead-of-time (AOT) processing
  • Profile energy consumption

Network

Intelligent management of network access makes your app more responsive and helps conserve battery life.

  • If no network connection is available, you should defer further attempts to access the network until a connection has been restored
  • avoid bandwidth-heavy operations, such as video streaming, unless a WiFi connection is available

cellular radio systems (LTE, 4G, 3G, etc.) are known to consume more battery than WiFi radio. This is because LTE devices use multi-input, multi-output (MIMO) technology that uses multiple concurrent signals and can maintain two LTE links. Similarly, all cellular data connections periodically scan for stronger signals.

need to:

  • Check if an appropriate network connection is available before any network operation. can use Reachability Pod
  • Continually monitor network availability and respond appropriately when the connection status changes.

Location Manager and GPS

Optimal Initialization

  • distanceFilter

    The distance filter will cause the manager to notify the delegate about location Manager:didUpdateLocations: events only if the device has moved by the mini‐ mum distance. The distance is in SI units (meters).
    This does not help reduce GPS receiver usage but does impact the processing that your app will do, and hence indirectly reduces CPU usage.

  • desiredAccuracy

    The accuracy parameter has a direct impact on the number of antennas to be used, and hence the battery consumption. Choose the accuracy level based on the specific needs of the app.

Invoke startUpdatingLocation when you need to track and stopUpdatingLocation when you do not.

startMonitoringSignificantLocationChanges when the app is backgrounded and to startUpdatingLocation when foregrounded.

1
- (void)applicationDidEnterBackground:(UIApplication *)application {
    [self.locationManager stopUpdatingLocation];
    [self.locationManager startMonitoringSignificantLocationChanges];
}
- (void)willEnterForeground:(UIApplication *)application {
    [self.locationManager stopMonitoringSignificantLocationChanges];
    [self.locationManager startUpdatingLocation];
}

In iOS 8, you need explicit permission to continue to use the location manager when the app is backgrounded. The following steps must be completed in order to get per‐ mission from the user:

  1. Update the app’s Info.plist file, adding an NSLocationAlwaysUsageDescription entry of type String. The value is the message presented to the user when requesting permissions to use location tracking when the app is in the back‐ ground or in the foreground. The key NSLocationWhenInUseUsageDescription is used for permissions to use location tracking when the app is in the fore‐ ground only.
  2. Call the method requestAlwaysAuthorization for foreground and background permissions (the method requestWhenInUseAuthorization is for foreground use only).

NSTimers, NSThreads, and Location Services

Any timers or threads are suspended when an app is backgrounded. But if you have requested location updates when your app is backgrounded, the app will be woken for an infinitesimal duration each time the updates are sent to it. And for that dura‐ tion, the threads and timers will also come to life again.

Restart After App Kill

1
-(void)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(launchOptions[UIApplicationLaunchOptionsLocationKey]) { (1)
        [self.manager startMonitoringSignificantLocationChanges]; (2)
    }
}

(1) Detect if the app was restarted due to location changes after it was killed for lack of resources.

(2) If so, start monitoring for location changes. Otherwise, start monitoring at a later appropriate time.

Animation (Screen)

you can listen to UIApplicationWillResignActiveNotification or UIApplicationDidEnterBackgroundNotification notification events to pause or stop your animations and UIApplicationDidBecomeActiveNotification notification events to resume animations.

Other Hardware

When the app is backgrounded, release any locks obtained on the hardware:

  • Bluetooth
  • Camera
  • Speaker, unless the app is a music app
  • Microphone

Battery Level and State-Aware Code

1
2
3
4
5
6
7
8
-(BOOL)shouldProceedWithMinLevel:(NSUInteger)minLevel {
    UIDevice *device = [UIDevice currentDevice];
    device.batteryMonitoringEnabled = YES;
UIDeviceBatteryState state = device.batteryState;
if(state == UIDeviceBatteryStateCharging || state == UIDeviceBatteryStateFull) {
//Any operation can be performed during charging or when the device is fully charged. return YES;
}
//The batteryLevel returned by UIDevice is in the range 0.00–1.00. NSUInteger batteryLevel = (NSUInteger) (device.batteryLevel * 100); if(batteryLevel >= minLevel) { return YES;
} return NO;
}

CPU usage code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-(float)appCPUUsage { 
kern_return_t kr; task_info_data_t info; mach_msg_type_number_t infoCount = TASK_INFO_MAX;
kr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)info, &infoCount); if (kr != KERN_SUCCESS) {
return -1; }
thread_array_t thread_list;
mach_msg_type_number_t thread_count;
thread_info_data_t thinfo;
mach_msg_type_number_t thread_info_count;
thread_basic_info_t basic_info_th;
kr = task_threads(mach_task_self(), &thread_list, &thread_count); if (kr != KERN_SUCCESS) {
return -1; } float tot_cpu = 0; int j;
for (j = 0; j < thread_count; j++) { thread_info_count = THREAD_INFO_MAX; kr = thread_info(thread_list[j], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count);
if (kr != KERN_SUCCESS) { return -1;
}
basic_info_th = (thread_basic_info_t)thinfo;
if (!(basic_info_th->flags & TH_FLAGS_IDLE)) { tot_cpu += basic_info_th->cpu_usage / (float)TH_USAGE_SCALE * 100.0; }
}
vm_deallocate(mach_task_self(), (vm_offset_t)thread_list, thread_count * sizeof(thread_t)); return tot_cpu;
}

Alert the user if the battery level is low, and request the user’s per‐ mission to execute battery-intensive operations—only proceed if the user agrees.

Always use an indicator to show the progress of a long-running task, whether it is about computation being done on the device or merely downloading some content. Providing users with an esti‐ mate of how long the task will take to complete helps them decide whether it’s necessary to continue charging the device.

Best Practices

  • Minimize hardware use—in other words, start interaction with the hardware as late as possible and stop once the task is complete.
  • Check the battery level and charging status before starting intensive tasks.
  • If the battery level is low, prompt the user to determine whether the task should really be executed, and proceed only if the user agrees.
  • Alternatively, include a setting to let the user define a threshold battery level below which the app should prompt the user before executing intensive operations.