iOS调试之NSException

NSException是什么?

其实很多开发者接触到NSException的频率非常频繁,但很多人都不知道什么是NSException,不知道如何使用NSException。下面从一张截图开始讲起NSException。

 
 
崩溃截图.png

其实控制台输出的日志信息就是NSException产生的,一旦程序抛出异常,程序就会崩溃,控制台就会有这些崩溃日志。


NSException的基本用法

下面代码就会让你的程序崩溃

    //异常的名称
    NSString *exceptionName = @"自定义异常";
    //异常的原因
    NSString *exceptionReason = @"有bug所以程序崩溃了";
    //异常的信息
    NSDictionary *exceptionUserInfo = nil;
    
    NSException *exception = [NSException exceptionWithName:exceptionName reason:exceptionReason userInfo:exceptionUserInfo];
    
    NSString *aboutMe = @"有bug";
    
    if ([aboutMe isEqualToString:@"有bug"]) {
        //抛异常
        @throw exception;
    }

崩溃截图如下:

 
自定义异常崩溃截图.png

为什么说NSException很强大

NSException掌控着程序的生命,程序的崩溃就是NSException来控制的,你说NSException不强大吗?那为何要NSException来使程序崩溃呢?其实主要的出发点是让开发者认识到哪里的代码有问题。

下面说两个NSException的实用技巧吧

  • 1、 若自己封装一套SDK,若要提示哪里出错,那么就可以使用NSException。就像上面NSException的基本用法中的代码一样。
  • 2、可以用来捕获异常,防止程序的崩溃。当你意识到某段代码可能存在崩溃的危险,那么你就可以通过捕获异常来防止程序的崩溃。代码如下
    NSString *nilStr = nil;
    NSMutableArray *arrayM = [NSMutableArray array];
    
    @try {
        //如果@try中的代码会导致程序崩溃,就会来到@catch
        
        //将一个nil插入到可变数组中,这行代码肯定有问题
        [arrayM addObject:nilStr];
    }
    @catch (NSException *exception) {
        //如果@try中的代码有问题(导致崩溃),就会来到@catch
        
        //在这里你可以进行相应的处理操作
        
        //如果你要抛出异常(让程序崩溃),就写上 @throw exception
        
    }
    @finally {
        
        //@finally中的代码是一定会执行的
        
        //你可以在这里进行一些相应的操作
    }
  • 3、最最实用的一个技术点就是利用 分类(category) + runtime + 异常的捕获 来防止Foundation一些常用方法使用不当而导致的崩溃。其原理就是利用category、runtime来交换两个方法,并且在方法中捕获异常进行相应的处理。(这里需要了解一些关于runtime的知识点,若对runtime不熟悉的朋友,可以先去了解下runtime的方法交换)。下面直接附上大概的使用方法。

添加分类,利用runtime交换方法

//
//  NSMutableArray+Extension.m
//  categoryTest
//
//  Created by LXL on 18/11/26.
//  Copyright © 2018年 chenfanfang. All rights reserved.
//

#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>

@implementation NSMutableArray (Extension)

+ (void)load {
    Class arrayMClass = NSClassFromString(@"__NSArrayM");
    
    //获取系统的添加元素的方法
    Method addObject = class_getInstanceMethod(arrayMClass, @selector(addObject:));
    
    //获取我们自定义添加元素的方法
    Method avoidCrashAddObject = class_getInstanceMethod(arrayMClass, @selector(avoidCrashAddObject:));
    
    //将两个方法进行交换
    //当你调用addObject,其实就是调用avoidCrashAddObject
    //当你调用avoidCrashAddObject,其实就是调用addObject
    method_exchangeImplementations(addObject, avoidCrashAddObject);
}

- (void)avoidCrashAddObject:(id)anObject {
    @try {
        [self avoidCrashAddObject:anObject];//其实就是调用addObject
    }
    @catch (NSException *exception) {
        //能来到这里,说明可变数组添加元素的代码有问题
        //你可以在这里进行相应的操作处理
        
        NSLog(@"异常名称:%@   异常原因:%@",exception.name, exception.reason);
    }
    @finally {
        //在这里的代码一定会执行,你也可以进行相应的操作
    }
}
@end

验证上面的代码的确可以捕获异常,并且不会崩溃

    NSString *nilStr = nil;
    NSMutableArray *arrayM = [NSMutableArray array];
    [arrayM addObject:nilStr];

控制台输出截图如下

 
捕获到的异常信息.png

 

 

 


 IOS 收集崩溃信息

  MyUncaughtExceptionHandler.h

#import <Foundation/Foundation.h>
// 崩溃日志
@interface MyUncaughtExceptionHandler : NSObject
 
+ (void)setDefaultHandler;
 
+ (NSUncaughtExceptionHandler *)getHandler;
  
+ (void)TakeException:(NSException *) exception;
 
@end

  MyUncaughtExceptionHandler.m

#import "MyUncaughtExceptionHandler.h"
 
// 返回沙盒地址
NSString * applicationDocumentsDirectory() {
    return [NSSearchPathForDirectoriesInDomains (NSCachesDirectory , NSUserDomainMask , YES ) firstObject];

}
// 出现崩溃时的回调函数
void UncaughtExceptionHandler(NSException * exception) {
    NSArray * arr = [exception callStackSymbols];
    // 崩溃的原因  可以有崩溃的原因(数组越界,字典nil,调用未知方法...) 崩溃的控制器以及方法
    NSString * reason = [exception reason];
    NSString * name = [exception name];
    
    NSString * url = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[arr componentsJoinedByString:@"\n"]];
    NSString * path = [applicationDocumentsDirectory() stringByAppendingPathComponent:@"ExceptionLog.txt"];
    // 将txt文件写入沙盒
    [url writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
    
}
 
@implementation MyUncaughtExceptionHandler
 
+ (void)setDefaultHandler{
    NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
}
 
+ (NSUncaughtExceptionHandler *)getHandler{
    return NSGetUncaughtExceptionHandler();
}
 
+ (void)TakeException:(NSException *)exception {
    NSArray * arr = [exception callStackSymbols];
    NSString * reason = [exception reason];
    NSString * name = [exception name];
    NSString * url = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[arr componentsJoinedByString:@"\n"]];
    NSString * path = [applicationDocumentsDirectory() stringByAppendingPathComponent:@"ExceptionLog.txt"];
    [url writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
@end

StatisRequestErrorData.h

#import <Foundation/Foundation.h>
 
@interface StatisRequestErrorData : NSObject
 
+ (void)showXcodeInfo;
 
@end

 StatisRequestErrorData.m

#import "StatisRequestErrorData.h"
#import "MyUncaughtExceptionHandler.h"
 
@implementation StatisRequestErrorData
 
#pragma mark -- 崩溃日志
+ (void)showXcodeInfo{
    
    [MyUncaughtExceptionHandler setDefaultHandler];
    
    // 发送崩溃日志
    NSString *path = [NSSearchPathForDirectoriesInDomains (NSCachesDirectory , NSUserDomainMask , YES ) firstObject];
    NSString *dataPath = [path stringByAppendingPathComponent:@"ExceptionLog.txt"];
    NSData *data = [NSData dataWithContentsOfFile:dataPath];
    if (data != nil) {
        [StatisRequestErrorData sendExceptionLogWithData:data];
    }else{
        NSLog(@"没有崩溃日志");
    }
}
 
#pragma mark -- 发送崩溃日志
+ (void)sendExceptionLogWithData:(NSData *)data
{
    NSLog(@"======数据上传奔溃日志成功=========");
    NSString * path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"ExceptionLog.txt"];
    [StatisRequestErrorData removeDocumentWithFilePath:path];
}
 
// 删除本地奔溃日志
+ (BOOL)removeDocumentWithFilePath:(NSString*)filePath{
    
    BOOL isRemove = false;
    NSFileManager* fileManager=[NSFileManager defaultManager];
    if ([[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
        isRemove = [fileManager removeItemAtPath:filePath error:nil];
    }
    return isRemove;
}
 
@end

在AppDelegate中

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//统计计奔溃日志
  [StatisRequestErrorData showXcodeInfo];
  return YES;
}

 reason 是异常的原因

 name 是异常的名称

 


利用category + runtime + 异常的捕获 来写一个防止崩溃的框架

 

posted on 2018-11-27 01:51  梁飞宇  阅读(1123)  评论(0)    收藏  举报