我們知道骑篙,在ObjC中向nil發(fā)送任何消息都不會(huì)導(dǎo)致崩潰,然而袄膏,在某些情況下這可能只是南柯一夢~
此次的問題,就發(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
要說的是甫煞,以上代碼并沒有什么問題的菇曲,鏈?zhǔn)紸PI十分的簡潔好用。但是在以下情景中使用鏈?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后常潮,它就無福消受大香蕉了。此時(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對象發(fā)送任何消息都不會(huì)崩潰喊式,不用懷疑,這并沒有什么問題萧朝,網(wǎng)絡(luò)上也有大量文章介紹其原理岔留。但是此處為什么崩潰了呢?
原因是检柬、其實(shí) a.eat(@"蘋果") 并不是單純的一次消息發(fā)送献联,而是做了以下兩步操作:
- 第一步:調(diào)用a.eat,可以理解為取得這個(gè)block
- 第二步:傳參并執(zhí)行這個(gè)block
顯然何址,問題就出在了第二步上里逆,在a為nil時(shí),a.eat為nil用爪,第二步就等價(jià)執(zhí)行了nil(@"蘋果")
运悲。所以,崩的也不冤项钮。
所以在此類場景下班眯,建議優(yōu)先判斷指針是否為nil,再?zèng)Q定是否執(zhí)行之后的操作烁巫。還有另外一個(gè)原因是:一次if判斷相較于消息發(fā)送來講是非呈鸢快的操作了。實(shí)測代碼和結(jié)果如下:
int main(int argc, char * argv[])
{
/// 測試次數(shù)
int testCount = 100000000;
/// 每次測試中亚隙,消息發(fā)送的執(zhí)行次數(shù)
int executeCount = 1;
People *x = [[People alloc] init];
People *y = nil;
NSDate *date1 = [NSDate date];
// 測試A:消息發(fā)送磁餐,sayHello為一空方法
for (int i = 0; i < testCount; i++) {
for (int j = 0; j < executeCount; j++) {
[x sayHello];
}
}
NSDate *date2 = [NSDate date];
// 測試B:消息發(fā)送,對象為nil
for (int i = 0; i < testCount; i++) {
for (int j = 0; j < executeCount; j++) {
[y sayHello];
}
}
NSDate *date3 = [NSDate date];
// 測試C:if判空阿弃,不執(zhí)行消息發(fā)送
for (int i = 0; i < testCount; i++) {
if (y) { // 執(zhí)行if判斷后诊霹,可避免n多條無意義語句的執(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]);
}
測試結(jié)果如下(各執(zhí)行1,0000,0000次測試,executeCount為每輪測試中方法的執(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í)行速度非常快怔蚌,且由此可避免多條無意義語句的執(zhí)行(向nil發(fā)送消息)巩步,帶來是時(shí)間紅利更是明顯。
針對此種情景的解決方案
上文已經(jīng)提到通過判斷對象是否為nil桦踊,再?zèng)Q定執(zhí)行后續(xù)操作這一解決方案椅野,這也最為簡單高效。但是這一方案較容易出現(xiàn)漏判的情況籍胯,所以以下兩種配合的方案也值得考慮:
- 1竟闪、根據(jù)業(yè)務(wù)場景將鏈?zhǔn)紸PI抽出到OC方法中統(tǒng)一執(zhí)行,這樣如果對象為nil時(shí)芒炼,就到不了執(zhí)行鏈?zhǔn)紸PI這一步了。同時(shí)這樣也有助于功能模塊的進(jìn)一步細(xì)分术徊;
- 2本刽、確保對象釋放后,有關(guān)該對象的所有邏輯操作已取消(比如頁面銷毀時(shí)結(jié)束所有未完成的網(wǎng)絡(luò)請求赠涮、DB操作)子寓,這個(gè)要結(jié)合具體業(yè)務(wù)場景進(jìn)行處理。