Apple has been gradually enlarging the permissions of App background running. Up to today, the known background running scenarios of iOS App are as follows:

Background Task

Typically, once an App enters the background, it has only a few seconds to continue executing code before it is suspended by the system. Unless the App shows the invocation of the beginBackgroundTaskWithExpirationHandler API, can delay the App code running in the background of time, the iOS is 10 minutes before 7, iOS reduced to 3 minutes after seven. Once the time limit is reached, the system continues to Suspend the process.

Suspend means that the system suspends all code logic in the App. For example, all threads are suspended, memory reads and writes are suspended, and file access is also suspended. For example, sqLite reads and writes are suspended even in the middle of writing. To understand this, we can better analyze the behavior of our App in the background.

For most APPS, Background Task mechanism is used to extend the running time of the Background. However, once the size of App increases and business scenarios increase, it is difficult to ensure that all codes will be encapsulated into Background Task for execution. It is difficult to ensure that once a Background Task is expire, the App will voluntarily suspend all internal logic and be a good citizen of the iOS system.

Imagine that the App sends a network request in the Background Task. Due to the large delay, the response will come back after 3 minutes of Background operation, triggering some code logic, such as database writing. At this time, the iOS system will forcibly terminate the process because the Background time limit has been used up. The state of the App becomes unpredictable.

Although Apple suggests that apps should run as few key tasks as possible after entering the background, such specifications are difficult to follow for apps with complex business types. What we can do is to try our best to complete core tasks within 3 minutes after entering the background without triggering new code flows.

Background Mode

Apps with this Mode enabled can always run in the Background, such as early VOIP applications, navigation apps, apps that support Background music playing and some Bluetooth apps all need this Background Mode to run core functions properly. The side effect of this mode is that the audit is harsh and the power consumption is severe, making users more sensitive.

Background Fetch

This is a mechanism provided by Apple to wake up the App in the period specified by the system and execute a small amount of logic, which has many restrictions and is not reliable to wake up. For example, the App forcibly killed by users cannot be woken up. For example, some users will forcibly close all background fetch in the system setting in order to save electricity.

Silent Push

This is the mechanism to wake up App by setting specific fields in APN, which has more restrictions like Background fetch and cannot wake up App when it is forced to kill.

PushKit

PushKit is a new mechanism to replace the background running mode of VOIP. When a voice or video call is received, the App can be awakened by PushKit notification, which prevents the App from running in the background. PushKit wakeup is very reliable, and almost all voIp-enabled apps use this mechanism to improve the App experience.

The Background mode of PushKit is very interesting. I observed that it generally takes 30 seconds (not exactly, but roughly) to wake up in the Background, and 40 seconds if Background Task is enabled after wake up.

If you receive two PushKit notifications in a row within 30 seconds, your App’s total background running time still adds up to 30 seconds, and if you are Suspend 30 seconds later and receive a second Push, you get an additional 30 seconds. It is similar to the concept of Session. Each time a Push is received, a Session can be started for 30 seconds. A Session can process multiple pushes. A new Session can be started when a Push is received again.

Background Crash investigation

The above is a brief introduction of the existing background running mechanism of iOS App. The background running mechanism mentioned above is needed in a background crash investigation recently.

Users complain that the App’s cold start is so frequent that it consumes too much power.

At first, it was suspected that there was a background crash. The problem was that the developer checked the background logs and found no relevant crash records. So it could be BOOM, but the probability of BOOM is generally low.

Later, after I got the phone with the problem, I checked the analytics log and found the following crash report:

The App was apparently killed by the system in the background because it held a lock on a DB file while running in the background. Code 0xdead10cc, search for Apple official documentation:

The exception code 0xdead10cc indicates that an application has been terminated by iOS because it held on to a system resource (like the address book database) while running in the background.

Given the general direction of the cause, the next step is to analyze the specific Crash scenario. After carefully viewing the App run logs, the following behaviors are analyzed:

At the end of the 30 seconds, a new PushKit notification is received, which triggers a series of processes, such as SQLite reads and writes. Since the 30 seconds of background time has run out, The system forces the suspend App process.

Before suspending a process, the system performs an additional check. If the App holds the lock on the SQLite DB file (for example, while writing data) and the sqLite DB file is in a shared container, the system forces the process to be killed. And generate a Crash Report similar to the one above.

Why would the system do that? Simply, if the SQLite DB file is in a shared container, that means that the file will be accessed by both App and Extension, assuming that App’s write operation is suspended midway through execution. Extension also performs write operations on the same DB file after waking up. Then, when the App is woken up and continues the previous write operations, the write operation and DB file will be in an unpredictable state, which may cause write operation failure or DB file damage. Therefore, the system chooses to forcibly kill App.

Even if you understand the Crash process, it’s not easy to fix it. Reason as the article mentioned in the beginning, after a DaTiLiang App into the background, its code execution scenario will become very complicated, we are not simple mechanism to ensure that, after entering the background process can be completed in system within the time limit, only through log module screening, simplify the behavior after the App into the background.

Other harvest

In addition, it is worth mentioning that it can be seen from the Crash survey that not all crashes can be obtained through the Crash collection tool inside the App, and at least some known types of crashes cannot be captured by the App:

  • Foreground main thread is dead, App is Watchdog strong kill

  • App used too much memory in the foreground or background and was forcibly killed by the system, named FOOM and BOOM respectively.

  • After App is suspended in the background, it is forcibly killed by the system because it violates an Apple policy.

Therefore, even if your App has a mature Crash collection tool and background, sometimes you still need to log in Itunes Connect background to check logs, or directly check logs through Xcode, because some systems will not notify the App to forcibly kill, only the system can generate and obtain logs. These diaries can also be viewed from the user’s mobile phone in Settings->Privacy->Analytics->Analytics Data.