iOS - 断言处理与调试

一、Objective-C 中的断言:

  • Objective-C 中的断言处理使用的是 NSAssertionHandler :

    每个线程拥有它自己的断言处理器,它是 NSAssertionHandler 类的实例对象。当被调用时,一个断言处理器打印一条包含方法和类名(或者函数名)的错误信息。然后它抛出一个 NSInternalInconsistencyException 异常。

  • 基础类中定义了两套断言宏
    • NSAssert / NSCAssert
      /** NSAssert */
      #if !defined(_NSAssertBody)
      #define NSAssert(condition, desc, ...)    \
      do {                \
      __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
      if (!(condition)) {        \
             NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
             __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
         [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
         object:self file:__assert_file__ \
             lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
      }                \
         __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
      } while(0)
      #endif
      /** NSCAssert */
      #if !defined(_NSCAssertBody)
      #define NSCAssert(condition, desc, ...) \
      do {                \
      __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
      if (!(condition)) {        \
             NSString *__assert_fn__ = [NSString stringWithUTF8String:__PRETTY_FUNCTION__]; \
             __assert_fn__ = __assert_fn__ ? __assert_fn__ : @"<Unknown Function>"; \
             NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
             __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
         [[NSAssertionHandler currentHandler] handleFailureInFunction:__assert_fn__ \
         file:__assert_file__ \
             lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
      }                \
         __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
      } while(0)
      #endif
    • NSParameterAssert / NSCParameterAssert
      /** NSParameterAssert */
      #define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
      /** NSCParameterAssert */
      #define NSCParameterAssert(condition) NSCAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)
  • #小心使用NSAssert,可以看到它的定义中出现了一个self, 有可能在你的block中你会发现你明明没有self的strong引用,但是仍然出现了循环引用。就看看你是否使用了NSAssert,这个宏被展开之后持有了self,那么有可能就会出现引用不释放的问题。

    而使用NSCassert,就不会有这样的问题了。因为它定义使用的handleFailureInFunction函数,并没有self引用。

  • 这么做的意义在于两点:
    • 第一个是苹果对于断言处理在 API 层面进行了区分: 
      • NSAssert 与 NSCAssert 用来处理一般情况的断言
      • NSParameterAssert 与 NSCParameterAssert 用来处理参数化的断言
    • 第二个是区别是在 Objective - C 和 C 之间进行了区分这样才有了:
      • NSAssert 与 NSCAssert
      • NSParameterAssert 与 NSCParameterAssert

 


二、使用 NSAssertionHandler

  • 从 Xcode 4.2 开始,发布构建默认关闭了断言,它是通过定义 NS_BLOCK_ASSERTIONS 宏实现的。也就是说,当编译发布版时,任何调用 NSAssert 等的地方都被有效的移除了。
    尽管基础类库的断言宏在它们自己的权力下十分有用————虽然只用于开发之中。NSAssertionHandler 还提供了一套优雅地处理断言失败的方式来保留珍贵的现实世界的使用信息。

Pay Attension:

据说,许多经验丰富的 Objective-C 开发者们告诫不要在生产环境中使用 NSAssertionHandler。基础类库中的断言处理是用来在一定安全距离外来理解和感激的。请小心行事如果你决定在对外发布版的应用中使用它。

  • NSAssertionHandler 是一个很直接的类,带有两个需要在子类中实现的方法:-handleFailureInMethod:... (当 NSAssert / NSParameterAssert 失败时调用)和 -handleFailureInFunction:... (当 NSCAssert / NSCParameterAssert 失败时调用)。
  • 接下来看一个使用的实例
    #pragram 第一步,创建一个继承自NSAssertionHandler 的类:LoggingAssertionHandler 用来专门处理断言
    #import <Foundation/Foundation.h>
    @interface LoggingAssertionHandler : NSAssertionHandler
    @end
    #import "LoggingAssertionHandler.h"
    @implementation LoggingAssertionHandler
    /** 重写两个失败的回调方法,在这里执行我们想要抛出的错误(打印或者直接报错) */
    - (void)handleFailureInMethod:(SEL)selector object:(id)object file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ...{
      NSLog(@"NSAssert Failure: Method %@ for object %@ in %@#%li", NSStringFromSelector(selector), object, fileName, (long)line);
      NSException *e = [NSException
                        exceptionWithName: NSStringFromSelector(selector)
                        reason: format
                        userInfo: nil];
      @throw e;
    }
    - (void)handleFailureInFunction:(NSString *)functionName file:(NSString *)fileName lineNumber:(NSInteger)line description:(NSString *)format, ...{
      NSLog(@"NSCAssert Failure: Function (%@) in %@#%li", functionName, fileName, (long)line);
    }
    @end
  • 每个线程都可以指定断言处理器。 想设置一个 NSAssertionHandler 的子类来处理失败的断言,在线程的threadDictionary 对象中设置 NSAssertionHandlerKey 字段即可。

    大部分情况下,你只需在 

    -application:didFinishLaunchingWithOptions:

    中设置当前线程的断言处理器。

  • AppDelegate 中的处理
    - (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
    NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init];
    [[[NSThread currentThread] threadDictionary] setValue:assertionHandler
                                                   forKey:NSAssertionHandlerKey];
    // ...
    return YES;
    }
  • 这样我们就完成再当前线程中使用我们自定义的断言处理器的配置,那么接下来,如果有和我们条件不同的情况都直接会回调对应着的那两个失败的方法,我们可以在那俩个方法中按自己的输出意愿来处理你的话术。
  • 具体应用
    #import "ViewController.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    - (void)viewDidLoad {
      [super viewDidLoad];
      NSObject*mc = [NSObject new];
      mc = @2;
      NSAssert(mc == nil, @"我不为空了");
    }
    @end
    根据输出情况可以看到是完全按照我们所需要的来输出的
    2015-10-30 21:33:14.529 NSAssert[20537:678428] *** 
    Terminating app due to uncaught exception 'viewDidLoad', reason: '我不为空了'

三、使用上的注意点

  • 仔细观察 NSAssert 的宏定义 ,你会发现 self 的痕迹,有 self 的地方就一定要注意 block 容易产生的循环引用问题。
    /** NSAssert */
    #if !defined(_NSAssertBody)
    #define NSAssert(condition, desc, ...)    \
    do {                \
    __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
    if (!(condition)) {        \
         NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
         __assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
     [[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
     object:self file:__assert_file__ \
         lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
    }                \
     __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
    } while(0)
    #endif
  • 接下来举个例子:
    /** 创建一个 preson 类 */
    #import <Foundation/Foundation.h>
    typedef void(^mitchelBlock)(int num);
    @interface person : NSObject
    @property(nonatomic, copy)mitchelBlock block;
    @end
    #import "person.h"
    @implementation person
    - (instancetype)init{
      if (self = [super init]) {
          dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
              if (self.block) {
                  self.block(1);
              }
          });
      }
      return self;
    }
    @end
    /** ViewController 中的代码 */
    #import "ViewController.h"
    #import "person.h"
    @interface ViewController ()
    @property(nonatomic, strong)person * aPerson;
    @end
    @implementation ViewController
    - (void)viewDidLoad {
      [super viewDidLoad];
      NSObject*mc = [NSObject new];
      mc = @2;
      self.aPerson = [person new];
      self.aPerson.block = ^(int num){
          NSAssert(mc == nil, @"我不为空了");
          NSLog(@"%d",num);
      };
    }
    @end
    这样我们就会看到 Block 中循环引用的警告啦:


    那如果我想在 Block 中使用断言怎么办呐?用 NSCAssert 替换 NSAssertNSCParameterAssert 来替换 NSParameterAssert
    - (void)viewDidLoad {
      [super viewDidLoad];
      NSObject*mc = [NSObject new];
      mc = @2;
      self.aPerson = [person new];
      self.aPerson.block = ^(int num){
          NSCAssert(mc == nil, @"我不为空了");
          NSCParameterAssert(num>5);
      };
    }
  • 哦了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值