The RunLoop application scenario I’m going to show you today is pretty cool, and we probably don’t use it very often, but we probably do use it a lot for the SDK that does Crash collection. We’ve all heard that RunLoop can bring apps back to life, but how? Today I’m going to put it to the test.

data

  • Sunnyxx Share RunLoop Offline (This is a video about sharing and discussing RunLoop offline. Alternate address: pan.baidu.com/s/1pLm4Vf9)
  • A brief introduction to Crash in iOS.
  • IOS program crash capture and intercept (my following Demo is in this part of the code to simplify, to facilitate understanding)

The principle of

IOS applications crash, the common crash information is EXC_BAD_ACCESS, SIGABRT XXXXXXX, and here is divided into two cases, one is not caught exception, we only need to add a callback function and call an API when the application is started. The other is directly sent SIGABRT XXXXXXX, where we also need to listen for various signals and then add the callback function.

In case one, we’ve all seen this before. When we collect the App crash information, you need to add a function NSSetUncaughtExceptionHandler (& HandleException), the argument is a callback function, access to the cause of the abnormal in the callback function, the current information saved to the stack dump file, And then upload it to the server the next time you open your App.

In fact, in the HandleException callback, we can get the current RunLoop, and then get all the modes in that RunLoop, and run through it manually.

In case 2, you set up the callback function for a variety of signals to be captured, then you get the RunLoop in the callback function as well, and then you get all the modes and run them manually.

Code implementation

First, I created a handler class and added a singleton method. (See the Demo at the end of the code)

The second step is to add callbacks for exception catching and signal handling when the object is instantiated in the singleton.

- (void)setCatchExceptionHandler {/ / 1. Capture some defect in the collapse of the NSSetUncaughtExceptionHandler (& HandleException); Signal (SIGABRT, SignalHandler); // 2. signal(SIGILL, SignalHandler); signal(SIGSEGV, SignalHandler); signal(SIGFPE, SignalHandler); signal(SIGBUS, SignalHandler); signal(SIGPIPE, SignalHandler); }Copy the code

The third step is to implement the exception-catching callback and the signal callback, respectively.

Void HandleException(NSException *exception) {NSArray *callStack = [Exception callStackSymbols]; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfosetObject:callStack forKey:kCaughtExceptionStackInfoKey];
    
    CrashHandler *crashObject = [CrashHandler sharedInstance];
    NSException *customException = [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:userInfo];
    [crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES]; } void SignalHandler(int signal) {NSArray *callStack = [CrashHandler backtrace]; NSArray *callStack = [CrashHandler backtrace]; NSLog(@"Signal capture crash, stack info: %@",callStack);
    
    CrashHandler *crashObject = [CrashHandler sharedInstance];
    NSException *customException = [NSException exceptionWithName:kSignalExceptionName
                                                           reason:[NSString stringWithFormat:NSLocalizedString(@"Signal %d was raised.", nil),signal]
                                                         userInfo:@{kSignalKey:[NSNumber numberWithInt:signal]}];
    
    [crashObject performSelectorOnMainThread:@selector(handleException:) withObject:customException waitUntilDone:YES];
}
Copy the code

Fourth, add the RunLoop code that brings the app back to life

- (void)handleException:(NSException *)exception
{
    NSString *message = [NSString stringWithFormat:@"Crash cause :\n%@\n%@",
                         [exception reason],
                         [[exception userInfo] objectForKey:kCaughtExceptionStackInfoKey]];
    NSLog(@"% @",message);
    
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"The program crashed."
                                                    message:@"If you could bring the program back to life, what would your decision be?"
                                                   delegate:self
                                          cancelButtonTitle:@"Just pop and jump."
                                          otherButtonTitles:@"Back from the dead.", nil];
    [alert show];
    
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    
    while(! ignore) {for (NSString *mode in(__bridge NSArray *)allModes) {CFRunLoopRunInMode((CFStringRef)mode, 0.001,false);
        }
    }
    
    CFRelease(allModes);
    
    NSSetUncaughtExceptionHandler(NULL);
    signal(SIGABRT, SIG_DFL);
    signal(SIGILL, SIG_DFL);
    signal(SIGSEGV, SIG_DFL);
    signal(SIGFPE, SIG_DFL);
    signal(SIGBUS, SIG_DFL);
    signal(SIGPIPE, SIG_DFL);
    
    if ([[exception name] isEqual:kSignalExceptionName]) {
        kill(getpid(), [[[exception userInfo] objectForKey:kSignalKey] intValue]);
    } else{ [exception raise]; }}Copy the code

Because I’ve got an AlertView popover here, so I have to go back to the main thread. In fact, the code associated with RunLoop:

 CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    
    while(! ignore) {for (NSString *mode in(__bridge NSArray *)allModes) {CFRunLoopRunInMode((CFStringRef)mode, 0.001,false);
        }
    }
    
    CFRelease(allModes);
Copy the code

It can be written in the HandleException and SignalHandler callbacks above.

Fifth, write a piece of code that crashes

I added a click event to the ViewController to create an array out of bounds Bug:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSArray *array =[NSArray array];
    NSLog(@"% @",[array objectAtIndex:1]);
}
Copy the code

Dynamic renderings:

Sunnyxx calls it the backlight. Why? I hit the view again, and the app still crashes, just to prevent the first crash. I tested it and it was the second time the app crashed and failed to come back to life.

The sample code in this article comes from RunLoopDemo04 in RunLoopDemos