Some methods to detect iOS APP performance

Some methods to detect iOS APP performance

[[183880]]

First, if you encounter application lag or excessive memory usage, you can usually use Instruments to detect it. But for complex situations, you may need to use the child thread to monitor the main thread. Here are some introductions to these methods:

Time Profiler

You can view the methods that take too much time in multiple threads. First, check Hide System Libraries on the right to filter the information. Then, the Call Tree will be sorted by the time-consuming threads by default, and the single thread will also be sorted by the corresponding time-consuming methods. After selecting the method, you can double-click the Heaviest Stack Trace on the right to view the specific time-consuming operation code, so that you can optimize it in a targeted manner without over-optimizing in some places that will not affect performance much.

Allocations

Here you can perform Generations before and after each action, compare the memory increase, and view the specific method and code location that caused the memory increase. The specific operation is to click Mark Generation in Generation Analysis on the right, which will generate a Generation. When you switch to other pages or another event occurs after a period of time, click Mark Generation again to generate a new Generation. Repeat this process to generate multiple Generations. When you check these Generations, you will see the size of Growth. If it is too large, you can click in to view the corresponding thread that occupies a large amount of space and check the corresponding code block in the Heaviest Stack Trace on the right, and then perform corresponding processing.

Leak

You can see the overflow at the corresponding time point in the Leaks section in the upper area. After selecting it, you can see the leaked object in Statistics>Allocation Summary in the lower area. You can also view the specific corresponding code area through Stack Trace.

When developing, you need to pay attention to how to avoid some performance issues

NSDateFormatter

Through the detection of Instruments, it is found that the time consumption of creating NSDateFormatter or setting the properties of NSDateFormatter is always at the front. How to deal with this problem? It is recommended to add properties or create static variables, so that the number of creation and initialization can be reduced to ***. Another way is to directly use C or this NSData Category to solve it https://github.com/samsoffes/sstoolkit/blob/master/SSToolkit/NSData%2BSSToolkitAdditions.m

UIImage

The main thing here is that it will affect the memory overhead. You need to weigh imagedNamed and imageWithContentsOfFile. After understanding the characteristics of the two, use the latter for images that only need to be displayed once. This will reduce memory consumption, but page display will increase Image IO consumption, which needs to be paid attention to. Since imageWithContentsOfFile does not cache, it needs to be loaded once before each page display. This IO operation is also a point that needs to be considered.

Page Loading

If a page has too much content and too many views, the partial view content that needs to be scrolled to be seen in the long page will be loaded synchronously by starting a new thread.

Optimize *** loading time

You can use Time Profier to view the time taken for startup. If it is too long, you can use Heaviest Stack Trace to find time-consuming methods and modify them.

How to monitor lag

Another way is to monitor performance issues in the program. You can take a look at this Demo first, the address is https://github.com/ming1016/DecoupleDemo. In this way, you can use this program to record the user's lag operations after going online, and send them to your own server at a regular interval, so that you can collect performance issues on a larger scale. As we all know, the lags perceived at the user level come from the main thread that handles all UIs, including large calculations performed on the main thread, a large number of IO operations, or heavier drawing work. How to monitor the main thread? First of all, you need to know that the main thread, like other threads, is driven by NSRunLoop. You can first take a look at the general logic of CFRunLoopRun.

  1. int32_t __CFRunLoopRun()
  2.  
  3. {
  4.  
  5. __CFRunLoopDoObservers(KCFRunLoopEntry);
  6.  
  7. do
  8.  
  9. {
  10.  
  11. __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
  12.  
  13. __CFRunLoopDoObservers(kCFRunLoopBeforeSources); //The processing time from here to kCFRunLoopBeforeWaiting is the key to perceive the jam
  14.  
  15.   
  16.  
  17. __CFRunLoopDoBlocks();
  18.  
  19. __CFRunLoopDoSource0(); //Handle UI events
  20.  
  21.   
  22.  
  23. //GCD dispatch main queue
  24.  
  25. CheckIfExistMessagesInMainDispatchQueue();
  26.  
  27.   
  28.  
  29. //Before sleep
  30.  
  31. __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
  32.  
  33.   
  34.  
  35. //wait for msg
  36.  
  37. mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();
  38.  
  39.   
  40.  
  41. //Waiting
  42.  
  43.   
  44.  
  45. //After sleeping, wake up
  46.  
  47. __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
  48.  
  49.   
  50.  
  51. //Timer wake up
  52.  
  53. if (wakeUpPort == timerPort)
  54.  
  55. __CFRunLoopDoTimers();
  56.  
  57.   
  58.  
  59. //Asynchronous processing
  60.  
  61. else if (wakeUpPort == mainDispatchQueuePort)
  62.  
  63. __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
  64.  
  65.   
  66.  
  67. //UI, animation
  68.  
  69. else  
  70.  
  71. __CFRunLoopDoSource1();
  72.  
  73.   
  74.  
  75. // Ensure synchronization
  76.  
  77. __CFRunLoopDoBlocks();
  78.  
  79.   
  80.  
  81. } while (!stop && !timeout);
  82.  
  83.   
  84.  
  85. //Exit RunLoop
  86.  
  87. __CFRunLoopDoObservers(CFRunLoopExit);
  88.  
  89. }

Based on this RunLoop, we can measure it through CFRunLoopObserverRef. Use dispatch_semaphore_t in GCD to start a new thread, set a limit value and the number of occurrences, and then obtain the scenes on the main thread that exceed the limit value and the number of occurrences between kCFRunLoopBeforeSources to kCFRunLoopBeforeWaiting and then to kCFRunLoopAfterWaiting, dump the stack, and send it to the server for collection. Through the stack, we can find the method that has the problem.

  1. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
  2.  
  3. {
  4.  
  5. MyClass *object = (__bridge MyClass*)info;
  6.  
  7. object->activity = activity;
  8.  
  9. }
  10.  
  11.   
  12.  
  13. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
  14.  
  15. SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;
  16.  
  17. lagMonitor->runLoopActivity = activity;
  18.  
  19.   
  20.  
  21. dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
  22.  
  23. dispatch_semaphore_signal(semaphore);
  24.  
  25. }
  26.  
  27.   
  28.  
  29. - (void)endMonitor {
  30.  
  31. if (!runLoopObserver) {
  32.  
  33. return ;
  34.  
  35. }
  36.  
  37. CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
  38.  
  39. CFRelease(runLoopObserver);
  40.  
  41. runLoopObserver = NULL ;
  42.  
  43. }
  44.  
  45.   
  46.  
  47. - (void)beginMonitor {
  48.  
  49. if (runLoopObserver) {
  50.  
  51. return ;
  52.  
  53. }
  54.  
  55. dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore ensures synchronization
  56.  
  57. //Create an observer
  58.  
  59. CFRunLoopObserverContext context = {0,(__bridge void*)self, NULL , NULL };
  60.  
  61. runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
  62.  
  63. kCFRunLoopAllActivities,
  64.  
  65. YES,
  66.  
  67. 0,
  68.  
  69. &runLoopObserverCallBack,
  70.  
  71. &context);
  72.  
  73. //Add the observer to the common mode observation of the main thread runloop
  74.  
  75. CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
  76.  
  77.   
  78.  
  79. //Create a child thread monitoring
  80.  
  81. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  82.  
  83. //The child thread starts a continuous loop for monitoring
  84.  
  85. while (YES) {
  86.  
  87. long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 30*NSEC_PER_MSEC));
  88.  
  89. if (semaphoreWait != 0) {
  90.  
  91. if (!runLoopObserver) {
  92.  
  93. timeoutCount = 0;
  94.  
  95. dispatchSemaphore = 0;
  96.  
  97. runLoopActivity = 0;
  98.  
  99. return ;
  100.  
  101. }
  102.  
  103. //Two runloop states, BeforeSources and AfterWaiting, can detect whether there is a freeze.
  104.  
  105. if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
  106.  
  107. // The result appears three times
  108.  
  109. if (++timeoutCount 3) {
  110.  
  111. continue ;
  112.  
  113. }
  114.  
  115.   
  116.  
  117. //Put the code for reporting stack information to the server here
  118.  
  119.   
  120.  
  121. } // end activity
  122.  
  123. }// end semaphore wait
  124.  
  125. timeoutCount = 0;
  126.  
  127. }// end while
  128.  
  129. });
  130.  
  131.   
  132.  
  133. }

Sometimes the jam is caused by abnormal, excessive, or large data, or by abnormal operation. Such situations may be difficult to encounter in daily development and testing, but they will occur in real life, especially when the user audience is wide. In this way, this method of collecting jams is still valuable.

Stack dump method

The first is to directly call the system function to obtain the stack information. This method can only obtain simple information. It cannot cooperate with dSYM to obtain the specific line of code that has a problem, and the types are also limited. The main idea of ​​this method is to obtain the error signal through signal. The code is as follows

  1. static   int s_fatal_signals[] = {
  2.  
  3. SIGABRT,
  4.  
  5. SIGBUS,
  6.  
  7. SIGFPE,
  8.  
  9. SIGILL,
  10.  
  11. SIGSEGV,
  12.  
  13. SIGTRAP,
  14.  
  15. SIGTERM,
  16.  
  17. SIGKILL,
  18.  
  19. };
  20.  
  21.   
  22.  
  23. static   int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]);
  24.  
  25.   
  26.  
  27. void UncaughtExceptionHandler(NSException *exception) {
  28.  
  29. NSArray *exceptionArray = [exception callStackSymbols]; //Get the current call stack information
  30.  
  31. NSString *exceptionReason = [exception reason]; // Very important, the reason for the crash
  32.  
  33. NSString *exceptionName = [exception name ]; //Exception type
  34.  
  35. }
  36.  
  37.   
  38.  
  39. void SignalHandler( int code)
  40.  
  41. {
  42.  
  43. NSLog(@ "signal handler = %d" ,code);
  44.  
  45. }
  46.  
  47.   
  48.  
  49. void InitCrashReport()
  50.  
  51. {
  52.  
  53. //System error signal capture
  54.  
  55. for ( int i = 0; i signal(s_fatal_signals[i], SignalHandler);
  56.  
  57. }
  58.  
  59.   
  60.  
  61. //oc does not capture the exception
  62.  
  63. NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
  64.  
  65. }
  66.  
  67. int main( int argc, char * argv[]) {
  68.  
  69. @autoreleasepool {
  70.  
  71. InitCrashReport();
  72.  
  73. return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  74.  
  75. }
  76.  
  77. }

The report generated by PLCrashReporter seems to be able to locate the specific location of the problematic code.

  1. NSData *lagData = [[[PLCrashReporter alloc]
  2.  
  3. initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]] generateLiveReport];
  4.  
  5. PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error: NULL ];
  6.  
  7. NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormat:PLCrashReportTextFormatiOS];
  8.  
  9. //Upload the string to the server
  10.  
  11. NSLog(@"lag happen, detail below:
  12.  
  13. %@",lagReportString);

The content in the stack in the test demo exceeds the number of characters in the WeChat text, so it is omitted in this article.

<<:  Talk about MVVM and chained network request architecture

>>:  iOS system swipe right to return to the global control solution

Recommend

The spring breeze blows, the flowers smile, why is your nose so noisy?

Spring is the most beautiful season of the year, ...

The secret to making Tik Tok video ads a hit!

How to shoot a good Douyin video with strong sale...

Is there really life under the ice layer of Enceladus?

A mysterious extraterrestrial ocean, who lives th...

During the epidemic, how can brands effectively implement grass-roots marketing?

During the epidemic, all walks of life were inevi...

What kind of programmer is a programmer who wrote 1 million lines of code?

Today, I was browsing the social network and sudd...

Kidney disease can not eat soy products? It's all a misunderstanding

Diet management is an extremely important part of...

Insights: Don’t put your feet into other people’s shoes

Famous Artists Gallery | Lou Shibai, a native of ...

What happened to the "spy whale" that can help people pick up mobile phones?

In 2019, a beluga whale suspected to be a Russian...

Android touch events (notes)

There are not many articles with similar titles o...