iOS - 斷言處理與調(diào)試

一直想寫(xiě)一篇你關(guān)于斷言的文章, 今天有時(shí)間趕緊寫(xiě)出來(lái).
參考 Mattt 文章


一、Objective - C 中的斷言:

  • Objective - C 中的斷言處理使用的是 NSAssertionHandler :

每個(gè)線程擁有它自己的斷言處理器罪裹,它是 NSAssertionHandler 類的實(shí)例對(duì)象妙色。當(dāng)被調(diào)用時(shí)娶牌,一個(gè)斷言處理器打印一條包含方法和類名(或者函數(shù)名)的錯(cuò)誤信息。然后它拋出一個(gè) NSInternalInconsistencyException 異常。

  • 基礎(chǔ)類中定義了兩套斷言宏
    • 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)
  • 這么做的意義在于兩點(diǎn):
    • 第一個(gè)是蘋(píng)果對(duì)于斷言處理在 API 層面進(jìn)行了區(qū)分:
      • NSAssertNSCAssert 用來(lái)處理一般情況的斷言
      • NSParameterAssertNSCParameterAssert 用來(lái)處理參數(shù)化的斷言
    • 第二個(gè)是區(qū)別是在 Objective - C 和 C 之間進(jìn)行了區(qū)分這樣才有了:
      • NSAssertNSCAssert
      • NSParameterAssertNSCParameterAssert

二适室、使用 NSAssertionHandler

  • 從 Xcode 4.2 開(kāi)始勿负,發(fā)布構(gòu)建默認(rèn)關(guān)閉了斷言馏艾,它是通過(guò)定義 NS_BLOCK_ASSERTIONS 宏實(shí)現(xiàn)的。也就是說(shuō)奴愉,當(dāng)編譯發(fā)布版時(shí)琅摩,任何調(diào)用 NSAssert 等的地方都被有效的移除了。
    盡管基礎(chǔ)類庫(kù)的斷言宏在它們自己的權(quán)力下十分有用————雖然只用于開(kāi)發(fā)之中锭硼。NSAssertionHandler 還提供了一套優(yōu)雅地處理斷言失敗的方式來(lái)保留珍貴的現(xiàn)實(shí)世界的使用信息房资。

Pay Attension:

據(jù)說(shuō),許多經(jīng)驗(yàn)豐富的 Objective-C 開(kāi)發(fā)者們告誡不要在生產(chǎn)環(huán)境中使用 NSAssertionHandler檀头『湟欤基礎(chǔ)類庫(kù)中的斷言處理是用來(lái)在一定安全距離外來(lái)理解和感激的。請(qǐng)小心行事如果你決定在對(duì)外發(fā)布版的應(yīng)用中使用它暑始。

  • NSAssertionHandler 是一個(gè)很直接的類搭独,帶有兩個(gè)需要在子類中實(shí)現(xiàn)的方法:-handleFailureInMethod:... (當(dāng) NSAssert / NSParameterAssert 失敗時(shí)調(diào)用)和 -handleFailureInFunction:... (當(dāng) NSCAssert / NSCParameterAssert 失敗時(shí)調(diào)用)。
  • 接下來(lái)看一個(gè)使用的實(shí)例
#pragram 第一步廊镜,創(chuàng)建一個(gè)繼承自NSAssertionHandler 的類:LoggingAssertionHandler 用來(lái)專門(mén)處理斷言
#import <Foundation/Foundation.h>
@interface LoggingAssertionHandler : NSAssertionHandler
@end
#import "LoggingAssertionHandler.h"
@implementation LoggingAssertionHandler
/** 重寫(xiě)兩個(gè)失敗的回調(diào)方法牙肝,在這里執(zhí)行我們想要拋出的錯(cuò)誤(打印或者直接報(bào)錯(cuò)) */
  - (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
  • 每個(gè)線程都可以指定斷言處理器。 想設(shè)置一個(gè) NSAssertionHandler 的子類來(lái)處理失敗的斷言期升,在線程的threadDictionary 對(duì)象中設(shè)置 NSAssertionHandlerKey 字段即可惊奇。

大部分情況下,你只需在

 -application:didFinishLaunchingWithOptions:

中設(shè)置當(dāng)前線程的斷言處理器播赁。

  • AppDelegate 中的處理
 - (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSAssertionHandler *assertionHandler = [[LoggingAssertionHandler alloc] init];
  [[[NSThread currentThread] threadDictionary] setValue:assertionHandler
                                                 forKey:NSAssertionHandlerKey];
  // ...
  return YES;
}
  • 這樣我們就完成再當(dāng)前線程中使用我們自定義的斷言處理器的配置颂郎,那么接下來(lái),如果有和我們條件不同的情況都直接會(huì)回調(diào)對(duì)應(yīng)著的那兩個(gè)失敗的方法容为,我們可以在那倆個(gè)方法中按自己的輸出意愿來(lái)處理你的話術(shù)乓序。
  • 具體應(yīng)用
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
 - (void)viewDidLoad {
    [super viewDidLoad];
    NSObject*mc = [NSObject new];
    mc = @2;
    NSAssert(mc == nil, @"我不為空了");
}
@end

根據(jù)輸出情況可以看到是完全按照我們所需要的來(lái)輸出的

2015-10-30 21:33:14.529 NSAssert[20537:678428] *** 
Terminating app due to uncaught exception 'viewDidLoad', reason: '我不為空了'

三寺酪、使用上的注意點(diǎn)

  • 仔細(xì)觀察 NSAssert 的宏定義 ,你會(huì)發(fā)現(xiàn) self 的痕跡,有 self 的地方就一定要注意 block 容易產(chǎn)生的循環(huán)引用問(wèn)題替劈。
/** 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
  • 接下來(lái)舉個(gè)例子:
/** 創(chuàng)建一個(gè) 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

這樣我們就會(huì)看到 Block 中循環(huán)引用的警告啦:

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

那如果我想在 Block 中使用斷言怎么辦吶寄雀?用 NSCAssert 替換 NSAssertNSCParameterAssert 來(lái)替換 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 了陨献。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盒犹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子眨业,更是在濱河造成了極大的恐慌急膀,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件龄捡,死亡現(xiàn)場(chǎng)離奇詭異卓嫂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)聘殖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)晨雳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人奸腺,你說(shuō)我怎么就攤上這事餐禁。” “怎么了洋机?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵坠宴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我绷旗,道長(zhǎng)喜鼓,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任衔肢,我火速辦了婚禮庄岖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘角骤。我一直安慰自己隅忿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布邦尊。 她就那樣靜靜地躺著背桐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蝉揍。 梳的紋絲不亂的頭發(fā)上链峭,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音又沾,去河邊找鬼弊仪。 笑死熙卡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的励饵。 我是一名探鬼主播驳癌,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼役听!你這毒婦竟也來(lái)了颓鲜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤禾嫉,失蹤者是張志新(化名)和其女友劉穎灾杰,沒(méi)想到半個(gè)月后蚊丐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體熙参,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年麦备,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了孽椰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凛篙,死狀恐怖黍匾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情呛梆,我是刑警寧澤锐涯,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站填物,受9級(jí)特大地震影響纹腌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜滞磺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一升薯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧击困,春花似錦涎劈、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至脸哀,卻和暖如春蹦浦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背企蹭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工白筹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留智末,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓徒河,卻偏偏與公主長(zhǎng)得像系馆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顽照,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容