說在前面的話
什么叫斷言?你會(huì)想到這個(gè)東西NSAsert.但是,不好意思.今天我所要說的不是這個(gè).這個(gè)是系統(tǒng)的,即將學(xué)習(xí)的是自定義的斷言.強(qiáng)烈建議:先把系統(tǒng)的NSAsert弄明白了,再繼續(xù)...
- copy下面的Code,然后運(yùn)行:
// NSAssert是一個(gè)宏定義
NSAssert(NO, @"哎呀呀,這是怎么了,怎么這么不小心呢...");
-
然后熟悉又該死的日志出現(xiàn)了:
哦,我明白了 再將copy的那段code的'NO',換成'YES',在運(yùn)行.
小總結(jié):終于明白NSAssert是干什么的(≧▽≦)/啦啦啦,終于可以自定義斷言了.系統(tǒng)的斷言最可恨了,不成立竟然直接程序崩潰,這不是我想要的效果.
自定義斷言前序
初識(shí)NSError
- copy下面的code,然后運(yùn)行:
NSError* error = [NSError errorWithDomain:@"HG, Error!" code:205 userInfo:@{@"errorKey":@"errorValue"}];
HGLog(@"%@", error);
- 看結(jié)果如下:

- 小總結(jié):很多時(shí)候,都見過NSError,但是......,你懂的!現(xiàn)在應(yīng)該對(duì)NSError有初步的認(rèn)識(shí)了
初識(shí)runtime
在OC中,什么是runtime是什么?簡(jiǎn)單的說就是運(yùn)行時(shí).那什么是運(yùn)行時(shí)呢?總的來說就是:OC沒有runtime,就沒有OC.看似一句廢話,其實(shí)不是廢話.接下來,我直接給出即將需要的一段runtime代碼.
- 創(chuàng)建一個(gè)NSObject的分類HGAssert,在.h文件中,代碼如下:
.h文件
#import <Foundation/Foundation.h>
@interface NSObject (HGAssert)
+ (NSError *)hg_error;
+ (void)setHg_error:(NSError *)error;
@end
到這里,你會(huì)很驚訝的發(fā)現(xiàn),盡然是一對(duì)setter與getter方法.但是仔細(xì)想想又不對(duì)了.因?yàn)橐话闱闆r一對(duì)setter與getter方法是要有一個(gè)實(shí)例關(guān)聯(lián)的,但是這是一個(gè)分類.學(xué)過OC的小盆友都知道,分類中是不能有實(shí)例的!對(duì),你太聰明了.先來看看這個(gè)分類里面的.m文件是怎么實(shí)現(xiàn)的吧.
- 分類HGAssert中.m代碼如下:
#import "NSObject+HGAssert.h"
#import <objc/runtime.h>
@implementation NSObject (HGAssert)
#pragma mark - 利用運(yùn)行時(shí),間接的在分類中添加實(shí)例.HGErrorKey將作為一種關(guān)聯(lián)
static const char HGErrorKey = '\0';
+ (NSError *)hg_error
{
return objc_getAssociatedObject(self, &HGErrorKey);
}
+ (void)setHg_error:(NSError *)error
{
objc_setAssociatedObject(self, &HGErrorKey, error, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
在這段代碼中,有三個(gè)東西可能你有點(diǎn)陌生:1.#import <objc/runtime.h>,2.objc_getAssociatedObject,3.objc_setAssociatedObject.這就是runtime中的核心技術(shù)之一.詳細(xì)的內(nèi)容請(qǐng)查看官方文檔學(xué)習(xí).
- setter方法與getter方法的作用就是:在setter方法中設(shè)置一個(gè)值,用getter方法來獲取.只是在普通的類中是通過實(shí)例將setter與getter方法來關(guān)聯(lián)的.在分類中如果也想要實(shí)現(xiàn)對(duì)應(yīng)的setter與getter方法相關(guān)聯(lián),就要利用runtime技術(shù).使用一個(gè)HGErrorKey類關(guān)聯(lián).
- 開心了吧,終于知道怎么在一個(gè)分類中關(guān)聯(lián)一個(gè)getter與setter方法了吧.
作業(yè):通過上述解說,參照如下代碼,請(qǐng)找出異同,并思考其用處:
.h文件
#import <UIKit/UIKit.h>
@interface UIView (HG)
@property (nonatomic, assign) CGFloat x;
@end
.m文件
#import "UIView+HG.h"
@implementation UIView (HG)
- (void)setX:(CGFloat)x
{
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (CGFloat)x
{
return self.frame.origin.x;
}
@end
- 這里的@property語法與普通類中的@property語法有何異同?
- 請(qǐng)思考這個(gè)分類的用途,并補(bǔ)全這個(gè)分類的其它功能(y, centerX, centerY, width, height, size, origin ,......)
驗(yàn)收
- 感謝網(wǎng)友@zy30651提醒,我沒有將下面用到的類(HGAssertMode)貼出來.此類代碼如下:
.h文件
#import <Foundation/Foundation.h>
@interface HGAssertMode : NSObject
@end
.m文件
#import "HGAssertMode.h"
@implementation HGAssertMode
@end
這個(gè)類主要的用途是:驗(yàn)證上面的繼承于NSObject的分類(HGAssert)中的兩個(gè)方法(setHg_error:與hg_error).
- 請(qǐng)看代碼
NSError* error = [NSError errorWithDomain:@"HG, Error!" code:205 userInfo:@{@"errorKey":@"errorValue"}];
// HGLog(@"%@", error);
[HGAssertMode setHg_error:error];
NSError* otherError = [HGAssertMode hg_error];
HGLog(@"%p \n%p", error, otherError);
- 結(jié)果

錯(cuò)誤,不一定是BUG
細(xì)心的同學(xué),又發(fā)現(xiàn)了一個(gè)BUG:在分類HGAssert中,我實(shí)現(xiàn)的是類方法,不是實(shí)例方法.其實(shí)都是類似的,不信你試試.這里要弄成類方法,是為了接下來的主題:<看我們的大標(biāo)題>
到這里,如果你都明白了上面的東西,說明你比我更優(yōu)秀!恭喜你,你趕快往下看吧!
進(jìn)入我們的大主題
我想要實(shí)現(xiàn)一個(gè)功能,計(jì)算一個(gè)數(shù)字的平方.代碼實(shí)現(xiàn)如下:
// 參數(shù)num職能是NSString與NSNumber是有效.
- (NSNumber*)square:(id)num {
return [NSNumber numberWithInt:([num intValue]*[num intValue])];
}
咋一看,這個(gè)方法木有BUG.仔細(xì)一看,大大的有問題:如果我傳入的參數(shù)不是NSString與NSNumber中的一個(gè)呢?那就閃退了.當(dāng)然這里采取的解決方式很多,但是為了說明主題.接下來使用斷言來解決這個(gè)BUG.
- 跟隨系統(tǒng)NSAssert的思想,也弄一個(gè)自己的宏斷言,代碼如下:
// 構(gòu)建錯(cuò)誤
#define HGBuildError(clazz, msg) \
NSError *error = [NSError errorWithDomain:msg code:205 userInfo:nil]; \
[clazz setHg_error:error];
/**
* 斷言
* @param condition 條件
* @param returnValue 返回值
*/
#define HGAssertError(condition, returnValue, clazz, msg) \
[clazz setHg_error:nil]; \
if ((condition) == NO) { \
HGBuildError(clazz, msg); \
HGLog(@"%@",msg);\
return returnValue;\
}
到這里,自定義的宏斷言,就成功了.接下來,完善一下上面的方法.
- 完善后的square:方法如下:
// 參數(shù)num職能是NSString與NSNumber是有效.
- (NSNumber*)square:(id)num {
HGLog(@"斷言前");
HGAssertError(([num isKindOfClass:[NSString class]] || [num isKindOfClass:[NSNumber class]]), nil, [NSNumber class], @"參數(shù)只能傳入NSString或者NSNumber的實(shí)例")
HGLog(@"斷言后");
return [NSNumber numberWithInt:([num intValue]*[num intValue])];
}
- 檢驗(yàn)square:方法
- 1

- 2

總結(jié)
- 不用斷言,可以么?當(dāng)然可以,像上面的例子,不用斷言,一句判斷就能搞定.但是為什么我們還要學(xué)習(xí)斷言呢?這個(gè)問題是在是太經(jīng)典了!1+1=2,你知道么?那你為什么要知道1+1=2呢?
- 經(jīng)檢驗(yàn),我們的斷言實(shí)現(xiàn)成功了!一個(gè)小小的功能,我寫了這么多的東西,這也在我的意料之外.其實(shí),我也是在<MJExtension>中見到的.看到"斷言"一詞,我是滿頭的霧霾!所以才自己實(shí)現(xiàn)了一遍.在過程中,難免有不當(dāng)之處,望評(píng)論指出!謝謝!
