原文地址: https://ctinusdev.github.io/2017/03/03/WriteWildPointer/
野指針的bug應(yīng)該算是最難查的bug之一了,因?yàn)槠潆S機(jī)性強(qiáng),且難以定位,下面就終結(jié)了幾類(lèi)常見(jiàn)的高概率野指針寫(xiě)法。
1、對(duì)象釋放后,指針沒(méi)有置空。
常見(jiàn)寫(xiě)法1:
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 396px;">
@property (nonatomic, unsafe_unretained) id obj;
</pre>
|
問(wèn)題原因:
unsafe_unretained
申明的obj并不會(huì)在對(duì)象釋放時(shí)將指針置空,如果對(duì)象釋放之后,繼續(xù)使用obj就有可能出現(xiàn)野指針的問(wèn)題。
解決方案:
盡量使用weak|strong|copy
等來(lái)代替unsafe_unretained
來(lái)修飾屬性。如果一定要使用unsafe_unretained
,記得對(duì)象釋放后,將指針置空。
常見(jiàn)寫(xiě)法2:
在objc_setAssociatedObject
方法中該用OBJC_ASSOCIATION_RETAIN_NONATOMIC
修飾的對(duì)象誤用成OBJC_ASSOCIATION_ASSIGN
問(wèn)題原因:
這個(gè)問(wèn)題和上面的常見(jiàn)寫(xiě)法1問(wèn)題是類(lèi)似的,就不重復(fù)了。
常見(jiàn)寫(xiě)法3:
NSNotification/KVO 只addObserver并沒(méi)有removeObserver
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 1054px;">
@interface viewController: UIViewController
@property (nonatomic, strong) id obj;
@end
@implementation viewController
-(void)someButtonClick:(id)sender
{
[self.obj addObserver:self forKeyPath:@"someKey" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
}
-(void)dealloc
{
//沒(méi)有移除觀(guān)察者
}
@end
</pre>
|
問(wèn)題原因:
self.obj添加了self作為觀(guān)察者后,是通過(guò)unsafe_unretained
指針引用的self,如果對(duì)象釋放之前不移除觀(guān)察,self.obj對(duì)應(yīng)keyPath發(fā)生變化時(shí),仍然會(huì)去嘗試給self指向的對(duì)象發(fā)送通知。就可能會(huì)出現(xiàn)野指針了。
解決方案:
1、原始方法,記得addObserver和removeObserver成對(duì)出現(xiàn)。
2、利用KVOController
2、對(duì)象提前釋放了
常見(jiàn)寫(xiě)法1:
異步方法block回調(diào)中,沒(méi)有強(qiáng)引用self。
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 302px;">
__weak typeof(self) weakself = self;
[obj method:^(id result) {
[weakself someMethod];
}];
-(void)someMethod
{
self.test = ....;
...
}
</pre>
|
問(wèn)題原因:
ARC下,由于性能原因self既不是strong也不是weak,而是unsafe_unretained
的。上面代碼block并沒(méi)有引用強(qiáng)引用self。若是在執(zhí)行[weakSelf someMethod]時(shí),剛好self被釋放了,那么self.test 這句的執(zhí)行就有可能造成野指針崩潰。
解決方案:
在進(jìn)入block時(shí),先強(qiáng)引用weakself。
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 427px;">
__weak typeof(self) weakself = self;
[obj method:^(id result) {
__strong typeof(self) strongself = weakSelf;
[strongself someMethod];
}];
</pre>
|
常見(jiàn)寫(xiě)法2:
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 498px;">
@interface viewController: UIViewController
@end
@implementation viewController
-(void)someButtonClick:(id)sender
{
[self.navigationController popViewControllerAnimated:NO];
[self someMethod];
}
@end
</pre>
|
問(wèn)題原因:
由于self在pop之后就會(huì)被釋放,在pop之后,繼續(xù)使用self,就可能會(huì)導(dǎo)致野指針。
解決方案:
在pop和dismiss之后不要在使用self,關(guān)于self的操作都在pop和dismiss之前。
3、對(duì)象的多次釋放。
常見(jiàn)寫(xiě)法1:
多個(gè)線(xiàn)程同時(shí)對(duì)某個(gè)對(duì)象賦值
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 678px;">
@interface SomeClass: NSObject
@property (nonatomic, strong) NSArray *array;
@end
@implementation SomeClass
- (void)viewDidLoad
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.array = @[@"test"];
});
self.array = @[@"test"];
}
@end
</pre>
|
問(wèn)題原因:
在調(diào)用setArray:時(shí),新的值會(huì)被retain,舊的值會(huì)被release。如果兩個(gè)線(xiàn)程同時(shí)執(zhí)行了setArray:,那么舊的值就可能會(huì)release放兩次。
解決方案:
找個(gè)問(wèn)題不難解決,對(duì)象賦之前先加鎖,再賦值就可以解決這類(lèi)問(wèn)題。
常見(jiàn)寫(xiě)法2:
CoreFoundation層對(duì)象Toll-Free Bridging到Foundation層中,已經(jīng)用了__bridge_transfer關(guān)鍵字轉(zhuǎn)移了對(duì)象的所有權(quán)之后,又對(duì)CoreFoundation層對(duì)象調(diào)用了一次CFRelease
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(134, 145, 148); background: rgb(239, 242, 243); line-height: 1.6; border: none; text-align: right;">
1
2
3
4
</pre>
|
<pre style="overflow: auto; font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; font-size: 13px; margin: 0px; padding: 10px; color: rgb(77, 77, 76); background: rgb(247, 247, 247); line-height: 1.6; border: none; width: 474px;">
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef cfString = CFUUIDCreateString(NULL, uuid);
NSString *string = (__bridge_transfer NSString *)cfString;
CFRelease(cfString);
</pre>
|
問(wèn)題原因:
使用__bridge_transfer
之后,cfString的所有權(quán)已經(jīng)交由ARC處理,這時(shí)再次接手動(dòng)調(diào)用release,會(huì)導(dǎo)致重復(fù)釋放的問(wèn)題。
解決方案:
1、將__bridge_transfer
改為__bridge
,不轉(zhuǎn)移對(duì)象的所有權(quán)。
2、去掉CFRelease(cfString)
;