High Performance iOS App Development is a high quality iOS book, and I learned a lot from it systematically. This blog post is a reading note for part 3 of the book High Performance iOS APP Development, “iOS Performance.” Since I’m interested in APP performance, I’ll start with part 3, “iOS Performance.”

1. Start the application

When an iOS app starts up, it calls the UIApplicationMain method and passes a reference to the UIApplicationDelegate class. Receiving application scope authorized by the event, and have a clear life cycle, application: didFinishLaunchingWithOptions: methods show that application has been launched.

The application window has a rootViewController, and the corresponding UIViewController object also has a clear life cycle. UIViewController method viewDidAppear: when executed, startup is complete.

During APP startup, unnecessary operations should be minimized to shorten APP startup time and achieve better user experience. There are four startup types for applications.

1.1 Initial Startup

The first boot after the application is installed. There is no previous state and no local cache. This means that one of two things will happen: there will be no content to load (and therefore load time will be shorter), or initial data will need to be downloaded from the server (which can take a long load time). When the app is first launched, you can choose to provide a guide diagram summarizing the functionality and usage of the app.

When first started, applications typically perform multiple tasks:

  • Load application defaults (NSUserDefaults, bundled configurations, etc.)
  • Check the private/test version
  • Initializes application identifiers, including but not limited to the Identifier for Vendor (IDFV) and Identifier for Advertiser (IDFA) for anonymous users
  • Initialize the crash reporting system
  • Set up A/B tests
  • Establish analytical methods
  • Network using operations or GCD
  • Setting up the UI infrastructure (navigation, themes, initial UI)
  • Displays login prompts or loads the latest content and other updates from the server
  • Create an in-memory cache (such as an image cache)

The above list is only the tasks that the application may perform when it is first started. Some of these will also be executed on subsequent launches. The problem is that a rapid increase in the number of tasks inevitably leads to slower startup times.

So how do you avoid this problem? You can follow these specific steps to unpack the task list for higher performance.

  • Determine the tasks that must be performed before the UI can be displayed. If the application is being launched for the first time, there is no need to load any user preferences such as themes, refresh intervals, cache sizes, and so on. There are no custom values at this point. It’s okay for the initial cache to grow arbitrarily, because it doesn’t grow beyond the final limit. The crash reporting system should be initialized first.
  • Perform tasks sequentially. Sorting is important because tasks can be interdependent, and it also saves valuable user time.
  • Divide tasks into two categories: those that must be executed in the main thread and those that can be executed in other threads, and execute them separately. You can further divide tasks that are executed on non-main threads into those that can and cannot be executed concurrently.
  • Other tasks can be performed after the UI is loaded or asynchronously. Delay initialization of other subsystems, such as recorders and analysis methods. Some operations (for example, write log messages or trace events) are queued in later phases of the application until the subsystem is fully initialized.

1.2 cold start

Subsequent startup of the application. During startup, you might want to restore the original state, for example, the highest level reached in the game, chat history in the messaging app, the last synchronized article in the news app, the credentials of the logged in user, or just the bootstrap graph markers that the user has already used.

One of the more important tasks in cold start is to load the previous state. In the application, the first screen shown to the user (after logging in) is the feed stream. If the user logged in on a previous startup and the data has been synchronized, we will consider loading the previously cached feed stream.

To accomplish the task of presenting the feed stream to the user, you must request the most recent update from the server while loading the data from the local cache. These behaviors are known without thinking. However, the following points should not be ignored.

  • The minimum amount of information (min) required to present a useful and meaningful UI.
  • Record the time it took to load M pieces of information from the local cache (called TL).
  • Record the time it took to get the latest M pieces of information from the server (called TR).
  • For faster speed, the maximum amount of information stored in memory at any one time (Max), especially when sliding and scrolling quickly.

1.3 Hot (heavy) startup

This is the startup triggered when the user switches to the application while it is in the background but not suspended or closed. In this case, when a user by clicking on the icon or deep link to return to application, will not trigger at the start of the callback, but directly in applicationDidBecomeActive: (or application: openURL: source: annotation:) callback.

In general, this situation is no different from continuing execution, except that the view controller may need to handle some additional events.

Hot start means switching to an already running application. Two things can deactivate an app: the user pulls down the status bar, or the user clicks the home button or switches to another app.

There are two scenarios for hot start:

  • The user clicks on the icon
  • The application receives a deep link

1.3.1 Restarting an Application

When the user clicks the app icon, no special actions are required.

Background and foreground notifications can be monitored when the application is in a safe state, or when many animations are running. In the first case, the login screen is displayed every time the application enters the foreground state. In the latter case, the animation or game state is suspended and needs to be restored.

1.3.2 Deep links

When the application receives the application: openURL: sourceApplication: annotation: correction, is expected to jump to the application of specific page, the user wants to do. But the target application may have changed and be in a particular state.

If the deep link needs to fetch data from the server, you can display the original page related to the deep link or display a progress bar and wait for the latest data to be retrieved from the server before refreshing.

1.4 Startup after the Upgrade

Startup after application upgrade. Generally speaking, upgraded startup is no different from cold startup. However, different startup names indicate different times when local storage changes, including schema, content, synchronization operations pending prior releases, and internal API/ default dependencies.

The first startup after an application upgrade follows one of the following scenarios:

  • No local caching or application abandoning caching altogether;
  • Local cache is available and can be used directly or switched to an upgraded version.

No special processing is required if there is no local cache or if the application decides to forgo caching (for example, data is unavailable or faster to get synchronously from the server). Notifies users when local data changes. The following best practices can make the user experience better.

  • Notify the user if the local cache is available. If there is no need to migrate to the local cache, there is no need to notify the user because the use of the local cache is implicit.
  • If you must take a few minutes to migrate the data, present the user with an option to postpone the operation.
  • If it is faster and easier to retrieve data from the server, and therefore the use of local caching must be abandoned, the user needs to be notified in this case.

2. User interface

Most users only notice performance issues when interacting with the UI. If an application takes a long time to synchronize and refresh data, or if user interaction is not consistent, then the application is considered sluggish.

Power consumption, network usage, local storage and other factors are invisible to users. Therefore, while these factors are essential to solving performance problems, the UI is the face of the application, and if the UI is slow to respond, it will directly affect user feedback.

There are also external factors beyond your control, as follows.

  • network
    • Weak network environments increase the time required for synchronization.
  • hardware
    • The better the hardware, the better the performance it provides. The new iPhone with the new operating system performs faster than older models.
  • storage
    • Applications can run on devices with different storage capacities ranging from 16GB to 128GB, which limits the size of data cached locally and offline by applications.

2.1 View Controller

In the early stages of application development, view controllers are lean and in good shape. Over time, these view controllers became a dumping ground for all business logic, growing to thousands of lines of code. While logical “lumps” are inevitable, it’s a good idea to reorganize code into short, reusable methods. Not only can you decouple, but you can also find useless, duplicate code.

Here are some of the more basic best practices to follow when creating a view controller.

  • Keep your view controller light. In MVC architecture applications, the controller is just the tie, not the place where all the business logic is stored. It’s not even part of the model. Business logic should belong to the service layer or business logic component. Put it there.
  • Do not write animation logic in view controllers. Animation can be implemented in a separate animation class that accepts views as arguments, which are the views to run the animation. The view controller then adds the animation to the view or transition effect.
  • Use data source and delegate protocols to separate code according to data retrieval, data update, and other business logic. View controllers can only be used to select the correct views and connect them to the provider.
  • The view controller responds to events from the view, such as button click events or list cell selection events, and then connects them to the data sink.
  • The view controller responds to UI-related events from the operating system, such as direction changes or low memory warnings. This may trigger a view relayout.
  • Do not manually lay out the UI with code in the view controller, and do not implement all of the UI, view creation, and view layout logic in the view controller.
  • A better way to do this is to create a base class view controller that implements the common Settings, from which other view controllers inherit.
  • Use categories to create reusable code across view controllers. If the parent view controller is not sufficient for use (for example, if you need a different kind of view controller in your application), create a category and add custom methods or properties to the category.

2.1.1 View Loading

There are two methods involved in view initialization — loadView and viewDidLoad.

If you create a custom UI by overwriting the loadView method, here are a few things to keep in mind.

  • Set the View property to the root of the view hierarchy.
  • Make sure the view is being shared by other view controllers.
  • Do not call [super loadView].

During execution, you should minimize the time spent on the viewDidLoad method. Specifically, the data to be rendered should be either already available or loaded in another thread. Any delay that occurs in the completion of viewDidLoad will cause a delay in the UI presentation associated with the view controller. The user gets stuck in the previous view controller or app startup.

2.1.2 View Hierarchy

The DISPLAYED UI is made up of hierarchical views nested in a tree structure, their position constrained by automatic layout or other choreography. View structure and rendering include the following steps.

  • (1) Construct a subview.
  • (2) Calculate and provide constraints.
  • (3) Recursively perform Steps 1 and 2 for the subview.
  • (4) Recursive rendering.

The more complex the view hierarchy, the longer it takes to build and render the view, so minimize the view hierarchy.

2.1.3 View visibility

The view controller provides four lifecycle methods to receive notifications about view visibility.

  • ViewWillAppear: This method is called when the view hierarchy is ready and the view is about to be placed in the view window. This happens when a view controller is about to be displayed or when a view controller that was previously pushed (modal or other) pops up. At this point, the transition animation has not started and the view is not visible to the end user. Do not start any view animations, because nothing works.
  • ViewDidAppear: This method is called when the view is displayed in the view window and the transition animation is complete. Since the animation takes about 300 milliseconds, the time difference between viewWillAppear: and viewDidLoad:, viewDidAppear: and viewWillAppear: might be quite large. Start or restore any view animations that you want to present to the user.
  • ViewWillDisappear: This method indicates that the view will be hidden from the screen. This could be because another view controller wants to take over the screen, or the view controller is going off the stack.
  • ViewDidDisappear: This method is called when the transition animation of the previous/next view controller is complete. Just as viewDidAppear:, viewWillDisappear: events also differ by about 300 milliseconds.

Here are some best practices for using life cycle events efficiently.

  • Needless to say, do not overwrite loadView.
  • Use viewDidLoad as a final checkpoint to see if the data from the data source is available. If available, update the UI elements.
  • If you need to show the latest information every time, use viewWillAppear: Update the UI element.
  • Start the animation in viewDidAppear:. If you have streaming content like video, you can start playing. Subscribe to application events to detect whether animation/video or other continuously updated video processing should continue or stop. Updating the UI with the latest data in this method is not recommended. If you do this, the net effect is that after the transition animation is complete, the user will transition to the old UI, and then the update will occur. The experience was not very friendly.
  • Use viewWillDisappear: to pause or stop the animation. Again, don’t do anything extra.
  • Use viewDidDisappear: Destroy complex data structures in memory. This is also where you can unregister data source notifications bound to view controllers and application event notification centers that are involved in animations, data sources, and UI updates.

2.2 the view

The most challenging part of tuning views is that there are very few techniques that work for all views. Each view has its own unique purpose, and most of the tuning techniques are related to the particular view and exposed API.

  • Basic guidelines:
    • Minimize the amount of work done in the main thread. Any extra code execution means a higher rate of frame loss. Too many lost frames will result in poor flow.
    • Avoid large NiBS or storyboards. Storyboards are powerful, but the entire XML must be loaded (I/O) and parsed (XML processing) before it can actually be used. The number of units in the storyboard should be minimized.
    • Avoid multiple layers of nesting in view hierarchies. Try to keep it flat.
    • View loading and reuse as lazily as possible. More views not only result in longer loading times, but also longer rendering times, which can affect memory and CPU usage.
    • For complex UIs, it is best to use custom drawing. This triggers only one view to draw, rather than multiple subviews, and avoids calling the expensive layoutSubviews and drawRect: methods. In addition, to avoid the expense of using general-purpose and feature-rich components, you can use views that directly implement drawing methods instead.

2.2.1 UILabel

This is probably the most commonly used view on iOS. It looks simple, but the rendering cost is not trivial. Here are some of the complex steps involved.

  • Calculate the number of pixels needed to use the font, font type, and text to be rendered. This is a costly process and should be done as little as possible.
  • Check the width to be rendered.
  • Check numberOfLines to count the number of rows to be displayed.
  • Is sizeToFit called? If so, calculate the height.
  • If sizeToFit is not called, check whether the current content can be displayed at the given height.
  • If the frame is insufficient, use lineBreakMode to determine where to hide or truncate.
  • Finally, use fonts, types, and colors to render the final displayed text.

Specifying each UILabel is a lot of work. With fewer tags, it’s easier to manage, and with more tags, you need to pay more attention to how those tags are created, configured, and reused.

2.2.2 UIButton

There are four ways to render a button:

  • Use the default rendering of custom text
  • Full size resource button
  • Variable size resources
  • Custom drawing using CALayer and Bessel paths

2.2.3 UIImageView

Of all the UI elements that are expensive to render, images are at the top of the list. When using UIImage and UIImageView, follow the following best practices to improve performance.

  • For known images, the image is loaded using the imageNamed: method. It ensures that content is loaded into memory only once, and that it can be reused across multiple UIImage objects.
  • Use the resource bundle when loading the package image using the imageNamed: method. This is especially useful if your app has a bunch of ICONS and each icon is small. You can freely create multiple directories of related images (that is, images that are usually used together).
  • For other images, use a high-performance image cache library. AFNetworking and SDWebImage are both optional and powerful libraries. When using images in memory, ensure that memory usage parameters are correctly configured. Don’t use hard coding. Make it adaptive — it’s better configured with a reasonable percentage of RAM.
  • The loaded image is the same size as the UIImageView that will be rendered. If the image being parsed is the same size as UIImageView, you’ll get extremely high performance because resizing an image is an expensive operation, even more so if the image is included in UIScrollView. If images are downloaded from the Internet, try to download images that match the view size. If that doesn’t work, preprocess the image properly and resize it.
  • If you need to use something like blur or tone effects, you can create a copy of the image content, apply the effects on the copy, and then use the final bitmap to create the desired UIImage. This way, these additional effects are used only once, and the original image can be used for other displays if necessary.
  • Regardless of the technique used to load the image, do it in a non-main thread, preferably in a dedicated queue. In particular, unzip JPG/PNG images in a non-main thread.
  • Last but not least, determine if you really need an image. If you want to display a score bar, it is better to use a custom view drawn directly rather than multiple images, by adjusting transparency or overlaying.

2.2.4 UITableView

UITableView is the most commonly used view to display data, whether in a news app, mail app, photo stream, or any other application. UITableView provides an excellent choice for displaying bars of information that can be of either the same or different categories.

UITableView is bound to two protocols.

  • UITableViewDataSource

    • The dataSource property must be set to the dataSource. As the name implies, a data source is a data source to be populated into a list cell.
  • UITableViewDelegate

    • The delegate property must be set to the delegate, which must be able to receive callbacks when the user interacts with the list or cell.

Here are some best practices to keep in mind when working with UITableView.

  • In the data source of the tableView: cellForRowAtIndexPath: method, use the tableView: dequeueReusa bleCellWithIdentifier: Or tableView: dequeueReusableCellWithIdentifier: forIndexPa th: cell reuse, rather than create a new cell every time.
  • Avoid cells of dynamic height whenever possible. Admittedly, the established height represents a small amount of computation. If the content is configured dynamically, not only does the height need to be calculated, but the contents of the cells need to be refreshed and rearranged every time the view is rendered. That’s a big performance penalty.
  • If you really need a dynamically high cell, define a rule to mark the cell as dirty. If a cell is dirty, calculate its height and cache it. In the tableView delegate: heightForRowAtIndexPath: continue to return to the height of the cache in the callback, until the cell no longer marked as dirty. If the model to be rendered is immutable, a simple rule to use is to check whether the currently rendered model has the same value as the corresponding indexPath. If so, the same values are rendered without further processing. If not, the value is recalculated and a new object (model) is appended to the cell.
  • When reusing cells with custom views, avoid laying them out each time by calling layoutIfNeeded. Even if the height of a cell is fixed, it is possible for individual elements within the cell to be set to different heights. For example, UILabel supports multiple lines of content, and UIImageView can load images of different sizes.
  • Avoid transparent cell view. When creating a UITableViewCell, introduce as many opaque elements as possible. Translucent or transparent elements (views with an alpha below 1.0) are nice to look at, but suffer a performance penalty.
  • Consider using an interface housing for fast scrolling (see this Skeleton). When a user scrolls through a list view quickly, despite all the optimizations, the view still takes more than 16 milliseconds to reuse and render, and there is also the possibility of occasional lost frames, leading to an uneven experience.
  • Avoid gradients, zooming, and any off-screen painting. These effects are a drain on the CPU as well as the graphics processing unit (GPU).

2.2.5 UIWebView

UIWebView is the most common view for rendering unknown or dynamic content.

While some applications may be completely native, there are still scenarios where you need to use UIWebView. Here are some common scenarios.

  • User login in any application. Apps like Spotify, Mint, and LinkedIn use native UI to render login forms. But there are limits.
  • Display privacy policy or terms of use in any application. Because these change over time and require a lot of formatting (text styles, numbered lists, cross-referencing of other content), using native views is not a good choice.
  • News or article readers, since most of the articles are created for the Web, are almost always HTML.
  • Mail application. For example, the initial email is HTML, when rendering a message or comment, and when writing a reply.

Keep the following best practices in mind when using UIWebView. (Note that there is very little you can do about UIWebView, not all of it performance-focused; Instead, the focus here is on presenting HTML content in the most appropriate way.)

  • Uiwebviews can be clunky and obtuse, so reuse web Views whenever possible. UIWebView is also known for memory leaks. Therefore, each application instance should be good enough.
  • Attach a custom UIWebViewDelegate. Implement the webView: shouldStartLoadWithRequest: navigationType: method. Keep an eye on URL schemes. If it’s something other than HTTP or HTTPS, a note of caution: the application should know how to handle this situation or warn the user that the site is trying to leave the application.
  • Through stringByEvaluatingJavaScriptFromString: ways you can create a bridge to connect application and JavaScript, so as to have loaded in the current web page to execute JavaScript. If you want to call native application methods, you can refer to the previous handling method and use a custom URL scheme.
  • Implement entrust webView: didFailLoadWithError: method, in order to keep close track of all possible error.
  • Implement the webView: didFailLoadWithError: method to deal with specific error.

2.3 Automatic Layout

Auto Layout allows you to describe how far one element is from another (horizontal or vertical), its size (width or height), or how it aligns with another element (horizontal or vertical).

As for the performance of Auto Layout, the book only introduces that the consumption of Auto Layout is much larger than that of Frame Layout when there are a large number of views. For details, please refer to this blog: Discuss the performance from the Layout algorithm of Auto Layout.

3. The network

The use of networks in your applications is essential, but there are limited ways to reduce network latency, so you should start to optimize network conditions to the maximum and plan for different scenarios in advance.

3.1 Indicators and measurements

Most of the work done on the network is out of your control, so it is important to determine the yardstick for measuring it. Some of the more important performance optimization-related measurements are listed below.

3.1.1 DNS Search time

The first step in initiating a connection is a DNS lookup. If your application relies heavily on network operations, DNS lookup times can slow your application down.

To minimize the delay caused by DNS query times, you should follow the following best practices.

  • Minimize the number of domain names used by the application. The way routing works in general, multiple domain names are inevitable. It is best to do the following:
    • Identity Management (Login, logout, profile)
    • Data Service (API endpoint)
    • CDN(Pictures and Other Static Artificial Products)
  • You don’t need to connect to all the domains when the application starts up, you may only need the data for identity management and the initial screen. For subsequent subdomains, try DNS resolution earlier, also known as DNS pre-download. To do this, you can refer to the following two points.
    • If the subdomain name and host are within the control range, you can configure a preset URL that does not return any data but only the HTTP 204 status code, and initiate a connection to this URL in advance.
    • The second method is to perform an explicit DNS lookup using gethostbyName. However, hosts may resolve to different IP addresses for different protocols, for example, HTTP requests may resolve to one address and HTTPS to another. Although not very common, layer 7 routing can resolve IP addresses based on actual requests, for example, images are one address and video is another. Given these factors, it is often useless to resolve DNS before connecting, and it is more efficient to pseudo-connect the host.

3.1.2 SSL Handshake time

For security purposes, you can assume that all connections in your application are over TLS/SSL (using HTTPS). HTTPS starts the connection with an SSL handshake, which validates the server certificate and shares random keys for communication. This may sound simple, but it’s a lot of steps and takes a lot of time.

You should follow these best practices.

  • Minimize the number of connections initiated by your application. Therefore, you also need to reduce the number of unique domain names that your application connects to.
  • Do not close the HTTP/S connection after the request ends. Add header Connection: keep-alive for all HTTPS requests. This ensures that the same connection can be reused on the next request.
  • Use domain sharding. That way, you can use the same socket even though it’s connected to a different host name, as long as they resolve to the same IP, and you can use the same certificate (for example, in the wildcard field).

3.1.3 Network Type

Typically, iphones and ipads can connect to the Internet using WiFi, 4G, 3G and other networks.

Follow these best practices.

  • Design with different network availability in mind. The only constant on mobile networks is that network availability is variable. For streaming, it is best to choose HTTP live streaming or any of the available adaptive bitrate streaming technologies that can dynamically switch at a given moment against the available bandwidth to the best streaming quality of the current bandwidth to provide smooth video playback. For non-streaming content, you need to implement policies that determine how much data should be downloaded in a single pull, and the amount of data must be adaptive. For example, you might not want to pull all 200 new emails at once on your latest update. You can download the first 50 emails and gradually download more. Also, on slow networks, do not turn on auto-play, which can cost users a lot of money.
  • When a failure occurs, retry after a random, exponentially increasing delay. For example, after the first failure, the application may retry 1 second later. On the second failure, the application is retried 2 seconds later, followed by a 4 second delay. Don’t forget to set the maximum number of automatic retries per session.
  • Set the minimum time between forced flusher. When a user explicitly requests a refresh, do not immediately request it. Instead, check whether a request already exists or whether the interval between the current request and the last request is less than the threshold. If the above conditions are met, do not send the request.
  • Use the reachability library to detect changes in network state.
  • Do not cache network state. Always use the latest value of a network-sensitive task, whether you get the status by triggering a callback when the request is triggered or explicitly checking the status before sending the request.
  • Download content based on the network type. If you want to display an image, you don’t always have to download the original, high-quality image. You should always download images that fit your device — the image size required for the iPhone 4S is very different from that required for the third-generation iPad.
  • Be optimistic about pre-downloading. Pre-download the content that the user needs at a later time on the WiFi network. You can then use the cached content. It’s best to download content in batches and turn off your Internet connection after use to help save battery life.
  • If applicable, synchronous offline storage is supported when the network is available. Usually, a network cache is sufficient. But if you need more structured Data, using local files or Core Data is a better choice. For games, cache the details of the most recent level. For mail applications, it’s a good idea to store some up-to-date emails with attachments.

3.1.4 delay

Latency is the extra time spent on network transport when requesting resources from the server. It is important to set up a system for measuring network latency.

Network latency can be measured by taking the total time spent in the request process minus the time spent on the server (computation and service response) :

Round-Trip Time = (Timestamp of Response - Timestamp of Request)
Network Latency = Round-Trip Time - Time Spent on Server
Copy the code

The time spent on the server can be calculated by the server. The round-trip time is accurate and available to the client. The server can put the time spent in the custom header of the response, which the client can then use to calculate the delay.

If you have data to analyze delays in any mode, you should also track the following data.

  • Connection timeout

    • It is important to track the number of connection timeouts. This metric provides a detailed geographical breakdown based on network quality (weak infrastructure or low capacity), which in turn helps plan synchronous time transfers. For example, synchronization is transmitted at short intervals, such as minutes, rather than synchronizing across time zones at a specific time.
  • Response timeout

    • Capture the number of successful connections but response timeouts. This helps plan data center capacity based on geographic location and time of year and date.
  • Load size

    • The size of requests and responses can be measured on the server side. Use this data to identify any potential drop low peak network operating speed, and to determine some of the available options: by choosing appropriate serialization format (JSON, CSV, Protobuf, etc.) to reduce data placeholder, or split the data and use the incremental synchronization (for example, by using small batch size or send some data in multiple blocks).

3.2 Application Deployment

With these metrics in mind, you can better plan your application deployment. This includes not only the server, the location and capacity of the server, but also the client, and how to get the best for a given scenario.

3.2.1 server

When looking at the geographic distribution of network latency, we can use this information to select the appropriate location for the data center. If you are using a hosted data center provider, choose one with multiple geographic locations, such as Amazon AWS or Rackspace Cloud. If you have your own data centers, make sure they are geographically dispersed.

A no-brainer, servers should be installed in multiple locations so you can better serve local content.

Here are some best practices to follow.

  • Using multiple data centers allows servers to be geographically dispersed and closer to users.
  • Use CDN to provide static content such as images, JavaScript, CSS, fonts, and so on.
  • Use close edge servers to serve dynamic content.
  • Avoid using multiple domain names (DNS queries can take a long time, which can degrade the user experience).

3.2.2 request

To set up the network properly, it is important to configure HTTP/S requests correctly. You should follow these best practices.

  • Do not request once for each unit of action, use batch requests. Even if you have to implement multiple back terminal systems to do this, the performance gains from merging batch requests are significant and therefore worth it.
  • Use a persistent HTTP connection, also known as an HTTP long connection. They help minimize the consumption of TCP and SSL handshakes while also reducing network congestion.
  • Use HTTP/2 whenever possible. HTTP/2 supports true reuse of HTTP requests over a single connection; If a request is resolved to an IP address, HTTP/2 aggregates requests across multiple subdomains. HTTP/2 also supports header compression, etc. The benefits of using HTTP/2 are huge. Best of all, the protocol remains the same in terms of message structure, still including headers and bodies.
  • Use HTTP cache headers to set the correct cache level. For standard images that you want to download, such as theme backgrounds or emojis, the expiration of the content can be set to a longer period of time. This not only ensures that the network library caches them locally, but also that other devices can benefit from a mediation server (ISP server or proxy) that is cached locally. The response headers that affect HTTP caching are Last-Modified, Expires, ETag, and cache-Control.

3.2.3 Data format

Choosing the right data format is just as important as choosing network parameters. Some choices can make a big difference in your application’s performance, such as PNG or WEBP for lossless image compression.

If your application is data-oriented, choosing the right format for its delivery is critical. Features supported by other protocols can also help.

When choosing a data format, you should follow the following best practices.

  • Use data compression. This is especially important when passing textual content such as JSON or XML. NSURLRequest automatically adds Accept-Encoding:gzip, deflate to the header so you don’t have to do it yourself. But this also means that the server should recognize the header and send the data using the appropriate transport encoding.
  • Choose the correct data format. Needless to say, lengthy, human-readable formats like JSON and XML are resource-intensive — serializing, transporting, and deserializing can take more time than using custom crafted, binary, machine-friendly formats. Instead of talking about media compression (that is, image compression and video codecs), I’m looking at text data formats.

3.3 tools

Charles is a very powerful network debugging agent. Use relatively simple, here do not do too much introduction, if you need to refer to the online blog.

Recommend some of the performance optimization blogs collected earlier:

  • IOS network optimization in depth
    • IOS Web Cache Literacy – 80% of your caching needs can be done with just two lines of code
    • Ctrip App network performance optimization practice
    • Overview of mobile APP Network optimization
    • Deeply optimized the iOS network module
    • IOS network request optimization
    • Mobile terminal network common problems and optimization countermeasures
    • Wireless performance optimization: domain name convergence
    • App network test performance optimization scheme
    • Optimization of the iOS network module (retransmission of failures and network transmission of cache requests)
    • Evolution of 58.com iOS client network framework
    • Ali Wireless 11.11: Mobile Phone Taobao 521 performance optimization project revealed

4. Data sharing

Sometimes you’ll need to share data with other apps, or access shared data from other apps on your device. The scenarios for sharing data include the following.

  • Integrate with other apps (for example, let users log in to your app using wechat login information).
  • Release a set of complementary applications.
  • Move user data from a unified application to one that serves multiple specific purposes, detect its presence, and pass control as needed.
  • Open the document in the best viewer available.

4.1 Deep Links

In the context of a mobile application, deep linking involves using uniform Resource Identifiers (URIs) that link to specific locations within a mobile application rather than simply launching the application.

Deep linking provides a decoupling scheme for shared data between applications. Similar to HTTP urls when visiting websites, deep links in iOS are provided through so-called custom URL schemes. You can configure your application to respond to a unique Scheme, and the operating system ensures that whenever you use scheme, your application handles it. Applications can respond to any number of schemes.

Deep linking is probably the most common option for accessing shared data, as well as for sharing data externally, and it is also important to optimize creation and parsing time. The following list covers some of the best practices you can follow to maximize your application’s performance.

  • It is best to use shorter urls because they are faster to build and parse.
  • Avoid patterns based on regular expressions.
  • Preferentially select query-based urls for standard resolution. Character-based delimiter parsing is faster than regular expression parsing.
  • Support deep link callbacks in your urls to help users accomplish intent. A better approach is to support three options: Success, Failure, and Cancel.
  • Urls are best used with deep links to help users define a workflow that needs to be coordinated across multiple applications.
  • Do not place any sensitive data in the URL. Specifically, do not use any authentication tokens. These tokens can be hijacked by unknown applications.
  • Do not trust any incoming data. Always validate the URL. As an additional measure, it might be a good idea to have the application sign the data before passing the URL and validate the signature before processing it. However, in order to proceed safely, the private key must be kept on the server, and in this case, there must be a network connection.
  • Use sourceApplication to identify the source. It’s useful to have a whitelist of apps that you can always trust. The use of sourceApplication is not orthogonal to signature validation. This can be the first step before the URL begins processing.

4.2 the clipboard

The official documentation describes the clipboard as follows.

The clipboard is a secure and standardized mechanism for exchanging data within and between applications. Many operations depend on the clipboard, especially copy-cut-paste. . But you can also use the clipboard in other situations, for example, when sharing data between applications.

You can use the clipboard through the UIPasteBoard class, which accesses a shared repository where write and read objects exchange data. Write objects, also known as clipboard owners, store data on clipboard instances. Read objects access the clipboard and copy data into its address space.

Clipboards have the following advantages over deep links.

  • It has the ability to support complex data such as images.
  • It supports the representation of data in multiple forms, which can be selected based on the capabilities of the target application. For example, a message application can use plain text format, and a mail application can use rich text format from the same clipboard project.
  • Clipboard content remains even after the application is closed.

When using a clipboard, you should follow the following best practices.

  • The clipboard is essentially interprocess communication mediated by clipboard services. All IPC security rules apply (e.g., don’t send any security data, don’t trust any incoming data).
  • Because you can’t control which application accesses the clipboard, it’s always unsafe to use unless the data is encrypted.
  • Don’t use a lot of data in the clipboard. While the clipboard supports swapping images and multiple formats, keep in mind that each entry not only consumes memory, but also takes extra time to read and write.
  • When the application will use UIApplicationDidEnterBackgroundNotification notice or UIApplicationWillResignActiveNotification enters the background, to clear the clipboard. Better yet, you can implement UIApplicationDelegate’s corresponding callback methods. You can clear the clipboard by setting items to nil, as follows: mypasteboard.items = nil;
  • To prevent any type of copy/paste, inherit UITextView and return NO in the copy: action of canPerformAction.

5. Security

Applications may run in unknown execution environments and exchange data over unknown transport networks, so security should always be one of the top priorities in order to protect sensitive data for users and applications.

Whether through code execution (for example, switching from a 1024-bit DSA key to a 2048-bit RSA encryption key) or through user intervention (for example, introducing two-factor authentication or application PINS), any additional layer of security causes the application to slow down. Therefore, you need to weigh the added security measures (which lead to delays) against the user’s intent to complete.

5.1 Application Access

5.1.1 Anonymous Access

The application may or may not require validation.

There are two options for identifying devices: the Identifier for Vendor (IDFV) and the Identifier for Advertiser (IDFA).

IDFV is a persistent and unique identifier for each application on the device and is used to identify the device to the application vendor. Part of the package ID is used to generate the IDFV, so the IDFV may be different even if the application comes from the same company.

IDFA is a resettable identifier that is unique across all applications on the device. Because it is unique in many applications, it is a truly unique ID. However, the IDFA can be reset by the user. In addition, Apple has set restrictions on its use, and you must be sure to use it when submitting your app for iTunes Connect approval. This ID should only be used by the AD delivery system.

5.1.2 Authentication Access

When you need to identify users, you need to authenticate access. This does not mean that authentication must be done in your application. Here are some of the authentication options available.

  • Application code

    • Also known as an application PIN, an application PIN is a local credential that you want to add to your application, regardless of whether there is a set of credentials for logging in to the application. In effect, it is a password stored only locally on the device.
  • The game center

    • This option only applies to games. Connect to the game center with GameKit, which takes care of authenticating the user with credentials. The game center has access to user profiles, personal records, and so on, but only shares what is needed to uniquely identify the user (the user ID).

5.2 Network Security

Networks have been discussed in depth before. This section discusses best practices related to security when communicating with a remote device, either a server or a point-to-point device.

5.2.1 using HTTPS

Assuming you use HTTP as the underlying messaging protocol (TCP is the transport layer protocol), then you must use it over TLS/SSL. That said, you should always use HTTPS. However, there are several problems with using HTTPS. If these potential risks are not addressed, HTTPS may be affected.

1. The attack CRIME

Do not use SSL/TLS compression. If you are using it now, turn it off immediately before continuing. This puts you at greater risk. Using TLS Compression (Gzip, Deflate, or other formats), any request is vulnerable to CRIME(Compression Ratio info-leak Made Easy). To mitigate the risk, you can turn off TLS compression and send anti-crime cookies to each response. The simplest way is to send a single random sequence cookie.

2. BREACH attack

If request/response body compression (transfer-encoding = gzip or Deflate) is used, Your communications are subject to BREACH(Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext) attacks, This type of attack was first detected in September 2012. There is a risk when the following criteria are met.

  • Applications use HTTP compression.
  • The response reflects the user input.
  • The response reflects privacy.

There is no single way to reduce this risk. The Breach Attack lists The following in order of effectiveness.

  • HTTP compression is disabled. This approach increases the amount of data transferred and may not be a practical solution.
  • Separate privacy from user input. Place the authorization code away from the request body.
  • Random encryption is performed on each request. However, since the encryption on each request is random, multiple parallel requests may not be possible.
  • Embellish privacy. Do not send privacy in its original format.
  • Use CSRF to protect vulnerable HTML pages. On mobile native applications, CSRF is not required unless you use the mobile Web.
  • Hidden length. A better approach is to use chunked transport encoding in HTTP responses.
  • Speed limit on requests (this should be used as a last resort).

5.2.2 Locking the Certificate

HTTPS is not a panacea – taking HTTPS will not magically ensure that all communication is secure. HTTPS is based on trust in the public key used to encrypt the initial message (during an SSL handshake). A man-in-the-middle (MITM) attack captures the key used to encrypt the message.

The only way to keep the request from becoming invalid is to trust, which the network library places in the received certificate. A certificate is just a public key for signing. Therefore, if the network library trusts the signer, it will also trust the public key provided by the host. Fake root certificates provided by hackers are the main culprits that bring down all security measures.

The solution to this problem is known as certificate locking. The way this scheme works is that the application creates a custom trust level by trusting only one or more certificates that can serve as the application root certificate. This allows applications to trust only certificates from the whitelist, ensuring that unknown certificates that allow network monitoring are never installed on the device.

5.3 Local Storage

Like data exchanged over a network, data stored on a device is not protected from tampering and can be read or modified by an intruder if not handled carefully. Here are a few points to note and best practices to follow in order to protect your local storage space.

  • Local storage is not secure

    • Local storage is very easy to access on jailbroken devices.
  • Encrypted local storage

    • Local storage can be encrypted using the data protection capabilities provided by the operating system.

5.4 Data Sharing

The simple basic rule to follow when sharing data and processing incoming data is: Don’t trust each other.

Validation is always performed when data is received. The only assumption an application should make about the data is that it may be invalid and wrong. To improve security, the data is required to be signed.

Also, never send sensitive data because you don’t know which application will process it. If you do need to share sensitive data, provide tokens and ask other applications to request data from your application (or server).

5.5 Security and Application Performance

Additional encryption or security measures add up to total memory consumption, as well as increased processing time. You can’t optimize in all dimensions, you can only make trade-offs.

Sometimes, it is not necessary to use a 2048-bit RSA key; a 1024-bit DSA key may be sufficient. Other times, symmetric encryption algorithms like Rijndael are enough to keep the data secure.

Retrieving the initial value from the keystring may result in a longer load time. You should be careful in using it.

Certificate locking has its own costs and can slow down all network operations.

Creating and validating data signatures requires calculating content hashes, which means additional content delivery. Depending on the size of the content, this can take quite a bit of time, not to mention the extra time required to compute and verify the digital signature.

All of these steps add up quickly. You may have the safest and most secure app in the world, but if it takes 30 minutes to load, no one will want to use it. At this point, even five seconds can have a negative impact on the user experience and result in losing users forever, especially if other applications can fulfill the same needs.


GitHub: High Performance iOS App Development – iOS Performance

Related articles: High performance iOS App Development – Core Optimization