有人想在iOS程序发布之后,收集程序崩溃的信息以便改进程序,今天我给大家介绍个比较实用的方法。
崩溃信息的收集完成后会在沙盒生成txt报告,但是这个crash收集也不能收集所有的程序崩溃信息,例如栈溢出或者对象指针损坏等等,这些崩溃会导致Crash收集的代码不会执行。
废话不多说,先直接讲添加方法,源代码一起奉献;
在appdelegate里的 application:didFinishLoading方法中添加
[[CrashManager sharedInstance] manageCrashes];
当然,你也能设置代理让你的delegate接受崩溃信息,
[[CrashManager sharedInstance] setCrashDelegate:self selector:@selector(notifyException:stackTrace:)];
下面是源代码:CrashManager.h
#import <Foundation/Foundation.h>
#import "SynthesizeSingleton.h"
/**
* Manages any crashes that occur while the app is running.
* If a crash occurs while CrashManager is managing crashes, it will write
* a crash report to a file, allow a user-defined delegate to do some more processing,
* then let the application finish crashing.
*/
@interface CrashManager : NSObject
{
/** The full path to the error report that this manager will write to. */
NSString* errorReportPath;
/** The delegate to inform when a crash occurs (can be nil). */
id delegate;
/** The selector to call on the delegate. */
SEL callbackSelector;
}
SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(CrashManager);
/**
* Start managing crashes.
*/
- (void) manageCrashes;
/**
* Stop managing crashes.
*/
- (void) stopManagingCrashes;
/** The error report's path.
* Note: If you set this to a value that doesn't start with "/", it will
* be expanded to a full path relative to the Documents directory.
*/
@property(readwrite,nonatomic,retain) NSString* errorReportPath;
/** The error report. If there is no report, this will be nil. */
@property(readonly,nonatomic) NSString* errorReport;
/** If true, there is an error report present. */
@property(readonly,nonatomic) bool errorReportPresent;
/**
* Delete the error report (if any).
*/
- (void) deleteErrorReport;
/**
* Set the delegate and selector to call when a crash occurs.
* The selector must take an NSException* and NSArray* parameter like so: <br>
* - (void) notifyException:(NSException*) exception stackTrace:(NSArray*) stackTrace <br>
* stackTrace will be an array of StackTraceEntry.
*
* @param delegate The delegate to call.
* @param selector The selector to invoke.
*/
- (void) setCrashDelegate:(id) delegate selector:(SEL) selector;
@end
然后是CrashManager.m
#import "CrashManager.h"
#include <unistd.h>
#import "StackTracer.h"
#import <UIKit/UIKit.h>
#import "MacAdress.h"
#import "NSString+SHA.h"
/** Maximum number of stack frames to capture. */
#define kStackFramesToCapture 40
/** The default file to store error reports to. */
#define kDefaultReportFilename @"error_report.txt"
/** The exception name to use for raised signals. */
#define kSignalRaisedExceptionName @"SignalRaisedException"
@interface CrashManager (Private)
/**
* Called automatically to handle an exception.
*
* @param exception The exception that was raised.
* @param stackTrace The stack trace.
*/
- (void) handleException:(NSException*) exception stackTrace:(NSArray*) stackTrace;
/**
* Called automatically to handle a raised signal.
*
* @param signal The signal that was raised.
* @param stackTrace The stack trace.
*/
- (void) handleSignal:(int) signal stackTrace:(NSArray*) stackTrace;
/**
* Get the name of a signal.
*
* @param signal The signal to get a name for.
* @return The name of the signal.
*/
- (NSString*) signalName:(int) signal;
/**
* Store an exception and stack trace to disk.
* It will be stored to the file specified in reportFilename.
*
* @param exception The exception to store.
* @param stackTrace The stack trace to store.
*/
- (void) storeErrorReport:(NSException*) exception stackTrace:(NSArray*) stackTrace;
@end
/**
* Install the exception and signal handlers.
*/
static void installHandlers();
/**
* Remove the exception and signal handlers.
*/
static void removeHandlers();
/**
* Exception handler.
* Sets up an appropriate environment and then calls CrashManager to
* deal with the exception.
*
* @param exception The exception that was raised.
*/
static void handleException(NSException* exception);
/**
* Signal handler.
* Sets up an appropriate environment and then calls CrashManager to
* deal with the signal.
*
* @param exception The exception that was raised.
*/
static void handleSignal(int signal);
static void installHandlers()
{
NSSetUncaughtExceptionHandler(&handleException);
signal(SIGILL, handleSignal);
signal(SIGABRT, handleSignal);
signal(SIGFPE, handleSignal);
signal(SIGBUS, handleSignal);
signal(SIGSEGV, handleSignal);
signal(SIGSYS, handleSignal);
signal(SIGPIPE, handleSignal);
}
static void removeHandlers()
{
NSSetUncaughtExceptionHandler(NULL);
signal(SIGILL, SIG_DFL);
signal(SIGABRT, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGSYS, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
}
static void handleException(NSException* exception)
{
removeHandlers();
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSArray* stackTrace = [[StackTracer sharedInstance] generateTraceWithMaxEntries:kStackFramesToCapture];
[[CrashManager sharedInstance] handleException:exception stackTrace:stackTrace];
[pool drain];
[exception raise];
}
static void handleSignal(int signal)
{
removeHandlers();
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSArray* stackTrace = [[StackTracer sharedInstance] generateTraceWithMaxEntries:kStackFramesToCapture];
[[CrashManager sharedInstance] handleSignal:signal stackTrace:stackTrace];
[pool drain];
// Note: A signal doesn't need to be re-raised like an exception does.
}
@implementation CrashManager
SYNTHESIZE_SINGLETON_FOR_CLASS(CrashManager);
- (id) init
{
if(nil != (self = [super init]))
{
self.errorReportPath = kDefaultReportFilename;
}
return self;
}
- (void) dealloc
{
[errorReportPath release];
[super dealloc];
}
- (void) setCrashDelegate:(id) delegateIn selector:(SEL) selectorIn
{
delegate = delegateIn;
callbackSelector = selectorIn;
}
- (void) manageCrashes
{
installHandlers();
}
- (void) stopManagingCrashes
{
removeHandlers();
}
- (NSString*) errorReportPath
{
return errorReportPath;
}
- (void) setErrorReportPath:(NSString*) filename
{
if([filename characterAtIndex:0] != '/')
{
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* dir = [paths objectAtIndex:0];
errorReportPath = [[dir stringByAppendingPathComponent:filename] retain];
}
else
{
errorReportPath = [filename retain];
}
}
- (bool) errorReportPresent
{
return [[NSFileManager defaultManager] fileExistsAtPath:errorReportPath];
}
- (NSString*) errorReport
{
NSError *error = nil;
return [NSString stringWithContentsOfFile:errorReportPath encoding:NSUTF8StringEncoding error:&error];
}
- (void) deleteErrorReport
{
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:errorReportPath error:&error];
if(nil != error)
{
NSLog(@"Warning: %s: Could not delete %@: %@", __PRETTY_FUNCTION__, errorReportPath, error);
}
}
- (void) handleException:(NSException*) exception stackTrace:(NSArray*) stackTrace
{
[self storeErrorReport:exception stackTrace:stackTrace];
[delegate performSelector:callbackSelector withObject:exception withObject:stackTrace];
}
- (void) handleSignal:(int) signal stackTrace:(NSArray*) stackTrace
{
NSException* exception = [NSException exceptionWithName:kSignalRaisedExceptionName
reason:[self signalName:signal]
userInfo:nil];
[self storeErrorReport:exception stackTrace:stackTrace];
[delegate performSelector:callbackSelector withObject:exception withObject:stackTrace];
}
- (void) storeErrorReport:(NSException*) exception stackTrace:(NSArray*) stackTrace
{
NSString* data = [NSString stringWithFormat:@"=============异常崩溃报告=============\n%@\nApp: %@\nVersion: %@\nID: %@\nReason: %@: %@\n%@\n\n\n",
[NSDate date],
[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"],
[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"],
[[MacAdress macAdress] sha1],
[exception name],
[exception reason],
[[StackTracer sharedInstance] printableTrace:stackTrace]];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:errorReportPath]) {
NSString *tmp = [NSString stringWithContentsOfFile:errorReportPath encoding:NSUTF8StringEncoding error:nil];
data = [tmp stringByAppendingFormat:@"%@",data];
}
[data writeToFile:errorReportPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (NSString*) signalName:(int) signal
{
switch(signal)
{
case SIGABRT:
return @"Abort";
case SIGILL:
return @"Illegal Instruction";
case SIGSEGV:
return @"Segmentation Fault";
case SIGFPE:
return @"Floating Point Error";
case SIGBUS:
return @"Bus Error";
case SIGPIPE:
return @"Broken Pipe";
default:
return [NSString stringWithFormat:@"Unknown Signal (%d)", signal];
}
}
@end
具体错误路径的StackTracer.h
#import <Foundation/Foundation.h>
#import "SynthesizeSingleton.h"
/**
* Generates and formats stack traces.
*/
@interface StackTracer : NSObject
{
/** The current application process's name. */
NSString* processName;
}
/** Make this class into a singleton. */
SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(StackTracer);
/**
* Generate a stack trace with the default maximum entries (currently 40).
*
* @return an array of StackTraceEntry.
*/
- (NSArray*) generateTrace;
/**
* Generate a stack trace with the specified maximum entries.
*
* @param maxEntries The maximum number of lines to trace.
* @return an array of StackTraceEntry.
*/
- (NSArray*) generateTraceWithMaxEntries:(unsigned int) maxEntries;
/**
* Create an "intelligent" trace from the specified trace.
* This is designed primarily for stripping out useless trace lines from
* an exception or signal trace.
*
* @param stackTrace The trace to convert (NSArray containing StackTraceEntry).
* @return An intelligent trace with the useless info stripped.
*/
- (NSArray*) intelligentTrace:(NSArray*) stackTrace;
/**
* Turn the specified stack trace into a printabe string.
*
* @param stackTrace The stack trace to convert (NSArray containing StackTraceEntry).
* @retun A string representation of the stack trace.
*/
- (NSString*) printableTrace:(NSArray*) stackTrace;
/**
* Turn the specified stack trace into a condensed printable string.
* The condensed entries are space separated, and only contain the object class
* (if any) and the selector call.
*
* @param stackTrace The stack trace to convert (NSArray containing StackTraceEntry).
* @return A condensed string representation of the stack trace.
*/
- (NSString*) condensedPrintableTrace:(NSArray*) stackTrace;
@end
/**
* A single stack trace entry, recording all information about a stack trace line.
*/
@interface StackTraceEntry: NSObject
{
unsigned int traceEntryNumber;
NSString* library;
unsigned int address;
NSString* objectClass;
bool isClassLevelSelector;
NSString* selectorName;
int offset;
NSString* rawEntry;
}
/** This entry's position in the original stack trace. */
@property(readonly,nonatomic) unsigned int traceEntryNumber;
/** Which library, framework, or process the entry is from. */
@property(readonly,nonatomic) NSString* library;
/** The address in memory. */
@property(readonly,nonatomic) unsigned int address;
/** The class of object that made the call. */
@property(readonly,nonatomic) NSString* objectClass;
/** If true, this is a class level selector being called. */
@property(readonly,nonatomic) bool isClassLevelSelector;
/** The selector (or function if it's C) being called. */
@property(readonly,nonatomic) NSString* selectorName;
/** The offset within the function or method. */
@property(readonly,nonatomic) int offset;
/** Create a new stack trace entry from the specified trace line.
* This line is expected to conform to what backtrace_symbols() returns.
*/
+ (id) entryWithTraceLine:(NSString*) traceLine;
/** Initialize a stack trace entry from the specified trace line.
* This line is expected to conform to what backtrace_symbols() returns.
*/
- (id) initWithTraceLine:(NSString*) traceLine;
@end
StackTracer.m
#import "StackTracer.h"
#include <execinfo.h>
#include <unistd.h>
/** The maximum number of stack trace lines to use if none is specified. */
#define kDefaultMaxEntries 40
@implementation StackTracer
SYNTHESIZE_SINGLETON_FOR_CLASS(StackTracer);
- (id) init
{
if(nil != (self = [super init]))
{
processName = [[[NSProcessInfo processInfo] processName] retain];
}
return self;
}
- (void) dealloc
{
[processName release];
[super dealloc];
}
- (NSArray*) generateTrace
{
return [self generateTraceWithMaxEntries:kDefaultMaxEntries];
}
- (NSArray*) generateTraceWithMaxEntries:(unsigned int) maxEntries
{
// Get the stack trace from the OS.
void* callstack[maxEntries];
int numFrames = backtrace(callstack, maxEntries);
char** symbols = backtrace_symbols(callstack, numFrames);
// Create StackTraceEntries.
NSMutableArray* stackTrace = [NSMutableArray arrayWithCapacity:numFrames];
for(int i = 0; i < numFrames; i++)
{
[stackTrace addObject:[StackTraceEntry entryWithTraceLine:[NSString stringWithUTF8String:symbols[i]]]];
}
// symbols was malloc'd by backtrace_symbols() and so we must free it.
free(symbols);
return stackTrace;
}
- (NSArray*) intelligentTrace:(NSArray*) stackTrace
{
int startOffset = 0;
// Anything with this process name at the start is going to be part of the
// exception/signal catching. We skip that.
for(int i = startOffset; i < [stackTrace count]; i++)
{
StackTraceEntry* entry = [stackTrace objectAtIndex:i];
if(![processName isEqualToString:entry.library])
{
startOffset = i;
break;
}
}
// Beneath that is a bunch of runtime error handling stuff. We skip this as well.
for(int i = startOffset; i < [stackTrace count]; i++)
{
StackTraceEntry* entry = [stackTrace objectAtIndex:i];
if(0xffffffff == entry.address)
{
// Signal handler stack trace is useless up to "0xffffffff 0x0 + 4294967295"
startOffset = i + 1;
break;
}
if([@"__objc_personality_v0" isEqualToString:entry.selectorName])
{
// Exception handler stack trace is useless up to "__objc_personality_v0 + 0"
startOffset = i + 1;
break;
}
}
// Look for the last point where it was still in our code.
// If we don't find anything, we'll just use everything past the exception/signal stuff.
for(int i = startOffset; i < [stackTrace count]; i++)
{
StackTraceEntry* entry = [stackTrace objectAtIndex:i];
// If we reach the "main" function, we've exhausted the stack trace.
// Since we couldn't find anything, start from the previously calculated starting point.
if([@"main" isEqualToString:entry.selectorName])
{
break;
}
// If we find something from our own code, use one level higher as the starting point.
if([processName isEqualToString:entry.library])
{
startOffset = i - 1;
break;
}
}
NSMutableArray* result = [NSMutableArray arrayWithCapacity:[stackTrace count] - startOffset];
for(int i = startOffset; i < [stackTrace count]; i++)
{
StackTraceEntry* entry = [stackTrace objectAtIndex:i];
// We don't care about intermediate forwarding functions.
if(![@"___forwarding___" isEqualToString:entry.selectorName] &&
![@"_CF_forwarding_prep_0" isEqualToString:entry.selectorName])
{
[result addObject:[stackTrace objectAtIndex:i]];
}
}
return result;
}
- (NSString*) printableTrace:(NSArray*) stackTrace
{
NSMutableString* string = [NSMutableString stringWithCapacity:[stackTrace count] * 100];
for(StackTraceEntry* entry in stackTrace)
{
[string appendString:[entry description]];
[string appendString:@"\n"];
}
return string;
}
- (NSString*) condensedPrintableTrace:(NSArray*) stackTrace
{
NSMutableString* string = [NSMutableString stringWithCapacity:[stackTrace count] * 50];
bool firstRound = YES;
for(StackTraceEntry* entry in stackTrace)
{
if(firstRound)
{
firstRound = NO;
}
else
{
// Space separated.
[string appendString:@" "];
}
if(nil != entry.objectClass)
{
// -[MyClass myMethod:anExtraParameter] or
// +[MyClass myClassMethod:anExtraParameter]
NSString* levelPrefix = entry.isClassLevelSelector ? @"+" : @"-";
[string appendFormat:@"%@[%@ %@]", levelPrefix, entry.objectClass, entry.selectorName];
}
else
{
// my_c_function
[string appendFormat:@"%@", entry.selectorName];
}
}
return string;
}
@end
@implementation StackTraceEntry
static NSMutableCharacterSet* objcSymbolSet;
+ (id) entryWithTraceLine:(NSString*) traceLine
{
return [[[self alloc] initWithTraceLine:traceLine] autorelease];
}
- (id) initWithTraceLine:(NSString*) traceLine
{
if(nil == objcSymbolSet)
{
objcSymbolSet = [[NSMutableCharacterSet alloc] init];
[objcSymbolSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
[objcSymbolSet formUnionWithCharacterSet:[NSCharacterSet characterSetWithRange:NSMakeRange('!', '~' - '!')]];
}
if(nil != (self = [super init]))
{
rawEntry = [traceLine retain];
NSScanner* scanner = [NSScanner scannerWithString:rawEntry];
if(![scanner scanInt:(int*)&traceEntryNumber]) goto done;
if(![scanner scanUpToString:@" 0x" intoString:&library]) goto done;
if(![scanner scanHexInt:&address]) goto done;
if(![scanner scanCharactersFromSet:objcSymbolSet intoString:&selectorName]) goto done;
if([selectorName length] > 2 && [selectorName characterAtIndex:1] == '[')
{
isClassLevelSelector = [selectorName characterAtIndex:0] == '+';
objectClass = [[selectorName substringFromIndex:2] retain];
if(![scanner scanUpToString:@"]" intoString:&selectorName]) goto done;
if(![scanner scanString:@"]" intoString:nil]) goto done;
}
if(![scanner scanString:@"+" intoString:nil]) goto done;
if(![scanner scanInt:&offset]) goto done;
done:
if(nil == library)
{
library = @"???";
}
if(nil == selectorName)
{
selectorName = @"???";
}
[library retain];
[objectClass retain];
[selectorName retain];
}
return self;
}
- (void) dealloc
{
[rawEntry release];
[library release];
[objectClass release];
[selectorName release];
[super dealloc];
}
@synthesize traceEntryNumber;
@synthesize library;
@synthesize address;
@synthesize objectClass;
@synthesize isClassLevelSelector;
@synthesize selectorName;
@synthesize offset;
- (NSString*) description
{
return rawEntry;
}
@end
有想要完整demo工程的,请移步下载。稍后给出链接。