iOS - 断言处理与调试

一直想写一篇你关于断言的文章, 今天有时间赶紧写出来.
参考 Mattt 文章


一、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)
  • 这么做的意义在于两点:
    • 第一个是苹果对于断言处理在 API 层面进行了区分:
      • NSAssertNSCAssert 用来处理一般情况的断言
      • NSParameterAssertNSCParameterAssert 用来处理参数化的断言
    • 第二个是区别是在 Objective - C 和 C 之间进行了区分这样才有了:
      • NSAssertNSCAssert
      • NSParameterAssertNSCParameterAssert

二、使用 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 中循环引用的警告啦:

屏幕快照 2015-10-30 下午9.48.17.png

那如果我想在 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);
    };
}
  • 这样就 OK 了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容