我們知道,在ObjC中向nil發(fā)送任何消息都不會(huì)導(dǎo)致崩潰,然而,在某些情況下這可能只是南柯一夢(mèng)~
此次的問(wèn)題,就發(fā)生我們的項(xiàng)目使用鏈?zhǔn)紸PI之后
鏈?zhǔn)紸PI的使用
如下為一個(gè)使用了鏈?zhǔn)紸PI的People類代碼:
@interface People : NSObject
- (People *(^)(NSString *sth))eat;
@end
@implementation People
- (People *(^)(NSString *sth))eat
{
return ^(NSString *sth) {
NSLog(@"I eat %@", sth);
return self;
};
}
@end
要說(shuō)的是,以上代碼并沒有什么問(wèn)題的,鏈?zhǔn)紸PI十分的簡(jiǎn)潔好用。但是在以下情景中使用鏈?zhǔn)紸PI,可能整個(gè)App都不好了
int main(int argc, char * argv[])
{
People *a = [[People alloc] init];
a.eat(@"蘋果").eat(@"香蕉").eat(@"橘子");
a = nil;
a.eat(@"香蕉");
}
將a置為nil后,它就無(wú)福消受大香蕉了。此時(shí)的結(jié)果也只有一個(gè),奔潰~~~~~~~
報(bào)錯(cuò)信息如下:
error: Execution was interrupted, reason: Attempted to dereference an invalid pointer..
The process has been returned to the state before expression evaluation.
ObjC中的nil
ObjC中向nil對(duì)象發(fā)送任何消息都不會(huì)崩潰,不用懷疑,這并沒有什么問(wèn)題,網(wǎng)絡(luò)上也有大量文章介紹其原理。但是此處為什么崩潰了呢?
原因是、其實(shí) a.eat(@"蘋果") 并不是單純的一次消息發(fā)送,而是做了以下兩步操作:
- 第一步:調(diào)用a.eat,可以理解為取得這個(gè)block
- 第二步:傳參并執(zhí)行這個(gè)block
顯然,問(wèn)題就出在了第二步上,在a為nil時(shí),a.eat為nil,第二步就等價(jià)執(zhí)行了nil(@"蘋果")。所以,崩的也不冤。
所以在此類場(chǎng)景下,建議優(yōu)先判斷指針是否為nil,再?zèng)Q定是否執(zhí)行之后的操作。還有另外一個(gè)原因是:一次if判斷相較于消息發(fā)送來(lái)講是非??斓牟僮髁?。實(shí)測(cè)代碼和結(jié)果如下:
int main(int argc, char * argv[])
{
/// 測(cè)試次數(shù)
int testCount = 100000000;
/// 每次測(cè)試中,消息發(fā)送的執(zhí)行次數(shù)
int executeCount = 1;
People *x = [[People alloc] init];
People *y = nil;
NSDate *date1 = [NSDate date];
// 測(cè)試A:消息發(fā)送,sayHello為一空方法
for (int i = 0; i < testCount; i++) {
for (int j = 0; j < executeCount; j++) {
[x sayHello];
}
}
NSDate *date2 = [NSDate date];
// 測(cè)試B:消息發(fā)送,對(duì)象為nil
for (int i = 0; i < testCount; i++) {
for (int j = 0; j < executeCount; j++) {
[y sayHello];
}
}
NSDate *date3 = [NSDate date];
// 測(cè)試C:if判空,不執(zhí)行消息發(fā)送
for (int i = 0; i < testCount; i++) {
if (y) { // 執(zhí)行if判斷后,可避免n多條無(wú)意義語(yǔ)句的執(zhí)行
for (int j = 0; j < executeCount; j++) {
[y sayHello];
}
}
}
NSDate *date4 = [NSDate date];
// 打印結(jié)果
NSLog(@"A (!= nil): %lf", [date2 timeIntervalSinceDate:date1]);
NSLog(@"B ( = nil): %lf", [date3 timeIntervalSinceDate:date2]);
NSLog(@"C (nil+if): %lf", [date4 timeIntervalSinceDate:date3]);
}
測(cè)試結(jié)果如下(各執(zhí)行1,0000,0000次測(cè)試,executeCount為每輪測(cè)試中方法的執(zhí)行次數(shù)):
| executeCount | A (執(zhí)行空方法) | B (Object為nil) | C (nil+if判空) |
|---|---|---|---|
| 1 | 0.607867 | 0.491741 | 0.203444 |
| 10 | 3.852057 | 3.015501 | 0.210288 |
| 50 | 20.307179 | 15.178183 | 0.205914 |
- 由A、B可知,向nil發(fā)送消息還是比較慢的操作;
- 但B、C可知,if判斷不涉及消息發(fā)送,執(zhí)行速度非??欤矣纱丝杀苊舛鄺l無(wú)意義語(yǔ)句的執(zhí)行(向nil發(fā)送消息),帶來(lái)是時(shí)間紅利更是明顯。
針對(duì)此種情景的解決方案
上文已經(jīng)提到通過(guò)判斷對(duì)象是否為nil,再?zèng)Q定執(zhí)行后續(xù)操作這一解決方案,這也最為簡(jiǎn)單高效。但是這一方案較容易出現(xiàn)漏判的情況,所以以下兩種配合的方案也值得考慮:
- 1、根據(jù)業(yè)務(wù)場(chǎng)景將鏈?zhǔn)紸PI抽出到OC方法中統(tǒng)一執(zhí)行,這樣如果對(duì)象為nil時(shí),就到不了執(zhí)行鏈?zhǔn)紸PI這一步了。同時(shí)這樣也有助于功能模塊的進(jìn)一步細(xì)分;
- 2、確保對(duì)象釋放后,有關(guān)該對(duì)象的所有邏輯操作已取消(比如頁(yè)面銷毀時(shí)結(jié)束所有未完成的網(wǎng)絡(luò)請(qǐng)求、DB操作),這個(gè)要結(jié)合具體業(yè)務(wù)場(chǎng)景進(jìn)行處理。