A Brief Discussion on iOS Crash (Part 2)

A Brief Discussion on iOS Crash (Part 2)

A Brief Discussion on iOS Crash (Part 1)

1. Zombie Objects

1. Overview

  • Zombie object: An object that has been released. Generally speaking, accessing a released object or sending a message to it will cause an error. Because the memory block pointed to by the pointer believes that you have no access or it cannot execute the message, the kernel will throw an exception (EXC) indicating that you cannot access the storage area (BAD ACCESS). (EXC_BAD_ACCESS type error)
  • NSZombieEnabled (turn on zombie mode) is generally used to debug and solve this type of problem.

2. Use NSZombieEnabled

  • NSZombieEnabled provided by Xcode replaces the implementation of dealloc by generating zombie objects. When the object reference count is 0, the object that needs dealloc is converted into a zombie object. If you send a message to this zombie object later, an exception will be thrown. First select Product -> Scheme -> Edit Scheme -> Diagnostics -> Check the Zombie Objects item, and it will be displayed as follows:

Set NSZombieEnabled.png

  • Then, in Product -> Scheme -> Edit Scheme -> Arguments, set the two variables NSZombieEnabled and MallocStackLoggingNoCompact to YES. The display is as follows:

Set NSZombieEnabled and MallocStackLoggingNoCompact.png

  • If only Zombie Objects is set, if the crash occurs in the current call stack, the system can locate the cause of the crash to the specific code; but if the crash does not occur in the current call stack, the system only informs the crash address, so we need to add the variable MallocStackLoggingNoCompact to let Xcode record the history of alloc for each address, and then restore the address through commands.
  • Before Xcode 6, you can use gdb, and use the info malloc-history address command to restore the crash address to a specific line of code. After Xcode 7, you can only use lldb and use the bt command to print the call stack. The following is a crash debugged in zombie mode and viewed with bt.

bt effect.png

Note: Before releasing a version, you need to remove all the settings for zombie object detection. Otherwise, every time you access an object through a pointer, you will check whether the object pointed to by the pointer is a zombie object, which will affect efficiency.

3. Notes in the code

In the ARC era, to avoid accessing released memory, the following points should be noted in the code:

  • Check code 1: You cannot use assgin or unsafe_unretained to modify a pointer to an OC object

Assgin and unsafe_unretained indicate that the object is not retained and is a weak reference. If the object pointed to by the pointer is released, they become wild pointers and a crash is likely to occur.

Suggestion 1: assign is only used to modify OC basic types such as NSInteger, and C data types such as short, int, double, and structure, and does not modify object pointers;

Suggestion 2: OC object properties are generally modified with the strong keyword (default).

Suggestion 3: If you need to weakly reference an OC object, it is recommended to use the weak keyword, because after the object referenced by the weak pointer is recycled, the weak pointer will be assigned to nil (null pointer), and there will be no problem sending any message to nil. Using weak to modify the properties of a proxy object is a good example.

  • Check code 2: Core Foundation and other low-level operations

Core Foundation and other low-level operations do not support ARC and require manual memory management.

Recommendation: Pay attention to the creation and release of CF objects.

2. Wild pointer

1. Overview

  • A wild pointer is a pointer that points to a deleted object or a pointer that has not requested access to a restricted memory area. A wild pointer here is mainly caused by the pointer not being set to null after the object is released. This type of crash occurs randomly and is difficult to find. A common approach is to increase the recurrence rate of this type of crash during the development phase and try to find and solve it as much as possible.
  • Sending a release message to an OC object only marks the memory occupied by the object as available for release, but the system does not reclaim the memory immediately; if other messages are sent to the object at this time, a crash may or may not occur. The following figure shows what may happen when accessing a wild pointer (a pointer to a deleted object).

What might happen if you access a wild pointer?

  • From the above figure, we can see that the crash caused by wild pointers is relatively random, but if the randomly filled data is inaccessible, the crash is inevitable. Our idea is to find a way to fill the memory pointed by the wild pointer with inaccessible data, so that the random crash becomes a certain crash.

2. Set Malloc Scribble

The Malloc Scribble provided by Xcode can fill in inaccessible data in the memory after the object is released, turning random occurrences into non-random occurrences. Select Product->Scheme->Edit Scheme->Diagnostics ->Check the Malloc Scribble item, and the result is as follows:

Set Malloc Scribble.png

Enable Scribble is set. After the object applies for memory, the memory applied for is filled with 0xaa. After the memory is released, the memory released is filled with 0x55. If the memory is accessed without being initialized, or is accessed after being released, a crash will occur.

Note: This method can only be used when running code in Xcode, so it is not suitable for testers. Based on fishhook, you can select the interface for hook object release (C's free function) to achieve the same effect as setting Enable Scribble. For details, please refer to How to locate random crashes caused by wild pointers in Obj-C (I): first increase the wild pointer crash rate, How to locate random crashes caused by wild pointers in Obj-C (II): make non-necessary crashes become necessary, and How to locate random crashes caused by wild pointers in Obj-C (III): add some black technology to make the crash self-introduction.

3. Notes in the code

Check for low-level operations such as using assgin or unsafe_unretained to modify pointers to OC objects and Core Foundation.

3. Memory Leak

1. Overview

  • Memory leaks refer to the failure to release memory that no longer references an object. Even though ARC helps us solve many problems, memory leaks are still common; generally, after development, we need to do some basic memory leak troubleshooting.
  • Memory leak detection generally uses Analyzer (static analysis) + Leaks + MLeaksFinder (third-party tool)

2-1. Static Analysis (Analyzer)

  • The Analyzer provided by Xcode can find potential errors in the code, such as memory leaks, unused functions and variables, by analyzing the syntax structure and memory status of the code context when the program is not running. Select Product->Analyze (shortcut command+shift+B) to use it.
  • Analyzer mainly analyzes four types of problems:

1) Logical error: accessing a null pointer or uninitialized variable, etc.;

2) Memory management errors: such as memory leaks, etc.; Core Foundation does not support ARC

3) Declaration error: variable never used;

4) API call error: The used libraries and frameworks are not included.

  • After the Analyzer is executed, common warning types include:

1) Memory Warning (Memory)

For example:

  1. - (UIImage *)clipImageWithRect:(CGRect)rect{
  2.  
  3.   
  4.  
  5. CGFloat scale = self.scale;
  6.  
  7. CGImageRef clipImageRef = CGImageCreateWithImageInRect(self.CGImage,
  8.  
  9. CGRectMake(rect.origin.x * scale,
  10.  
  11. rect.origin.y * scale,
  12.  
  13. rect. size .width * scale,
  14.  
  15. rect. size .height * scale));
  16.  
  17.   
  18.  
  19. CGRect smallBounds = CGRectMake(0, 0, CGImageGetWidth(clipImageRef)/scale, CGImageGetHeight(clipImageRef)/scale);
  20.  
  21. UIGraphicsBeginImageContextWithOptions(smallBounds. size , YES, scale);
  22.  
  23. CGContextRef context = UIGraphicsGetCurrentContext();
  24.  
  25.   
  26.  
  27. CGContextTranslateCTM(context, 0, smallBounds. size .height);
  28.  
  29. CGContextScaleCTM(context, 1.0, -1.0);
  30.  
  31. CGContextDrawImage(context, CGRectMake(0, 0, smallBounds. size .width, smallBounds. size .height), clipImageRef);
  32.  
  33.   
  34.  
  35. UIImage* clipImage = UIGraphicsGetImageFromCurrentImageContext();
  36.  
  37.   
  38.  
  39. UIGraphicsEndImageContext();
  40.  
  41. CGImageRelease(clipImageRef); //If not added, memory leak will be warned: Potential leak of an object stored into   'clipImageRef'  
  42.  
  43. return clipImage;
  44.  
  45. }

Analysis: Analyzer detects memory leaks. The most common ones are memory leaks starting with CG or CF. Memory is allocated but forgotten to be released. Another type is that the memory allocated by C is not paired with new delete or malloc free.

2) Invalid data warning (Dead store)

For example:

  1. //Wrong practice, Analyzer will tell you after analysis: Value stored to 'dataArray' during its initialization is never read  
  2.  
  3. NSMutableArray *dataArray = [[NSMutableArray alloc] init];
  4.  
  5. dataArray = _otherDataArray;
  6.  
  7.   
  8.  
  9. //Correct approach
  10.  
  11. NSMutableArray *dataArray = nil;
  12.  
  13. dataArray = _otherDataArray;

Analysis: dataArray has been initialized and allocated memory, and then assigned values ​​by another variable array, resulting in one data source applying for two pieces of memory, causing a memory leak.

3) Logic error monitoring

For example:

  1. //Wrong practice, Analyzer will tell you after analysis: Property of mutable type 'NSMutableArray' has 'copy' attribute, an immutable object will be stored instead  
  2.  
  3. @property (nonatomic, copy) NSMutableArray *dataArr;
  4.  
  5.   
  6.  
  7. //Correct approach
  8.  
  9. @property (nonatomic, strong) NSMutableArray *dataArr;

Analysis: NSMutableArray is a mutable data type and its objects should be modified with strong.

Note: The Analyzer makes judgments based on the code by the compiler, so the judgments may not be accurate. Therefore, if you encounter a prompt, you should check it in conjunction with the context of the code. Some circular references that cause memory leaks cannot be analyzed by the Analyzer.

2-2. Memory leak detection tool (Leaks)

  • Leaks provided by Xcode can help you find memory leaks in running programs. Select Product-> Profile (shortcut command+i to call up the Instrument tool interface) -> Leaks. Switch to Call Tree mode, and select Separate by Thread (analyze by thread), Invert Call Tree (invert call tree output), and Hide System Libraries (hide system library files) at the bottom. Finally, click the red button to start "recording", and the effect is as follows:

Leaks debugging interface.png

  • In the Leaks debugging interface, 1 is the Allocations template, which displays memory allocation; 2 is the Leaks template, where you can view memory leaks. If a red X appears, it means there is a memory leak; the main frame area will display the leaked object. The Call Tree options are described as follows:
CALL TREE options illustrate
Separate by Category By type, expand All Heap Allocations to display the allocation of heap memory in different methods.
Separate by Thread Analyzing by thread makes it easier to identify problem threads that consume resources. This is especially true for the main thread, which has to process and render all interface data. Once it is blocked, the program will inevitably freeze or stop responding.
Invert Call Tree Output the call tree in reverse order. Display the method with the deepest call hierarchy at the top, making it easier to find the most time-consuming operation.
Hide System Libraries Hide system library files. Filter out various system calls and only display your own code calls.
Flattern Recursion Merge recursion. Merge multiple stacks generated by the same recursive function (because the recursive function calls itself) into one

2-3. Troubleshooting MLeaksFinder (strongly recommended)

MLeaksFinder is a third-party tool launched by the WeChat Reading team to simplify memory leak detection. It is also one of the memory leak tools in our current project.

  • Features: Simple integration, mainly checks for leaks in the UI (UIView and UIViewController).
  • Principle: Without intruding the development code, by hooking the pop and dismiss methods of UIViewController and UINavigationController, check whether the view, subviews of the view, etc. of the ViewController object still exist after a short period of time after the ViewController object is popped or dismissed.
  • Implementation: Add a method - willDealloc method to the base class NSObject, use the weak pointer to point to itself, and after a short period of time (3 seconds), check again whether the weak pointer is valid. If it is valid, there is a memory leak.
  • Integration: It is very convenient to import it through Cocoapods or directly drag the code into the project. If a memory leak occurs, a warning box will pop up to indicate the location of the memory leak.

Note: For details, please refer to: MLeaksFinder: Accurate iOS memory leak detection tool and MLeaksFinder new features

3. Notes in the code (circular references under ARC are the main cause of memory leaks)

  • Check code 1: Core Foundation, Core Graphics, etc. operations

Core Foundation, CoreGraphics and other operations do not support ARC and require manual memory management.

Suggestion: Pay attention to the creation and release of CF and CG objects.

  • Check code 2: The use of NSTimer/CADisplayLink, because the target of the NSTimer/CADisplayLink object will strongly reference self, and self will strongly reference the NSTimer/CADisplayLink object.

Suggestion: Use extension methods, use block or target to weakly reference the target object to break the retain cycle. For specific implementation, refer to iOS Record 8: Solving the circular reference of NSTimer/CADisplayLink

  • Check code 3: block usage code.

Suggestion: Use weakSelf and strongSelf in pairs to break the block reference cycle (blocks that do not reference self will not cause circular references, so there is no need to use weakSelf and strongSelf)

Principle: Define a weak reference (weakSelf) outside the block, pointing to the self object; capture the weak reference (weakSelf) inside the block to ensure that self is not held by the block; when executing the method inside the block, generate a strong reference (strongSelf) pointing to the object (self object) pointed to by the weak reference (weakSelf); the self object is actually held inside the block, but the life cycle of this strong reference (strongSelf) is only during the execution of this block, and it is released immediately after the execution of the block.

4. Abandoned Memory

1. Overview

  • Abandoned Memory refers to memory that is still referenced by objects but can no longer be used in program logic.
  • To troubleshoot this type of problem, it is recommended to use Allocation provided by Xcode, which can track the memory allocation of the application.

2. Use Allocation

  • Allocation provided by Xcode can track the memory allocation of the application. Developers can repeatedly operate the App to check the memory baseline changes; they can even set Mark Generation to compare the memory growth between multiple Generations. This part of the growth is the memory that we did not release in time. Through Product-> Profile (shortcut command+i, call up the Instrument tool interface) -> Allocations. Finally, click the red button to start "recording", the effect is as follows:

Allocation interface Statistics Detail.png

  • The above picture shows the interface under Statistics Detail Type. The following are some descriptions of the names:
DETAIL column name illustrate
Graph Type options
Category Type, or CF object, or OC object, or raw block memory
Persistent Bytes Unreleased memory and size
Persistent The number of unreleased objects
Transient The number of objects that have been released
Total Bytes Total memory size
Total Total number of objects used
Transient / Total Bytes Freed memory size/total used memory size

ALLOCATION TYPE illustrate
All Heap & Anonymous All heap memory and other memory
All Heap Allocations All heap memory
All Anonymous VM All other memory
  • The following figure shows the interface after switching to Call Tree.

Allocation interface Call Tree display.png

CALL TREE Column Name illustrate
Bytes Used The amount of memory used
Count The total number of symbols used
Symbol Name Symbolic Name

Note: For detailed explanation of these terms, see Instrument-Allocations.

  • Click "Mark Generation" at intervals (such as 2 minutes) to determine the memory growth between generations. These growths may be memory that has not been released in time. According to the proportion of memory occupied, find the part with the highest proportion, then find our own code, and then analyze and solve the problem.

Allocation interface Mark Generation is displayed.png

3. Notes in the code

Omitted, the precautions are the same as those in the memory leak code.

<<:  Code to handle iOS horizontal and vertical screen rotation

>>:  Talk about the routines in data mining competitions and the limitations of deep learning

Recommend

Thanks to Chinese companies, Qualcomm's profits are higher than Apple's

Just as the National Development and Reform Commis...

Mars' leopard print may be the most powerful evidence of life

NASA 's Perseverance rover has discovered pos...

The secret of Baidu and Google's long-lasting success

Whether it is Google, Baidu or Yandex (the largest...

When your period coincides with an exam or competition, you can either...

When your period comes, life or death is unpredic...

New broadband players may disrupt the pricing structure

The broadband market has become less peaceful sin...

What would happen if you dropped your Motorola Droid Turbo into water?

If you want to test whether the water resistance ...

618 Marketing Promotion Plan Creation Guide, 1 Step to Get It Done

Every marketing plan with soul must not be a pile...