如何编写安全的iOS代码

遍历集合先Copy

如果你正在遍历的集合是mutable的,在你遍历的同时,可能另外一个线程对这个集合做了修改,一个Crash将出现.

1
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection was mutated while being enumerated.'

如何避免呢?
拿到NSArray/NSMutableArray、NSDictionary/NSMutableDictionary都copy一下。

集合操作要预判
提前做好判断,保证key/value不为nil,数组不会越界。

1
2
3
4
5
6
addObject:
removeObjectAtIndex:
insertObjectAtIndex:
setObject:forKey:
@[]
@{}

NSKeyedArchiver别常用
在iOS8上会抛exception,非要用加上@try{}@catch{}

KVO成对出现

有add必要有remove,否则极有可能Crash,可以使用FaceBook的FBKVOController

tableView的delegate要置空
viewController的dealloc把tableView的delegate置空

iOS8及以前,delegate属性不是weak,不会自动设为nil。否则做异步网络请求,退出页面就容易出现野指针。

不要在Category覆盖系统方法

不要坑别人

Api需要特定线程,记得加断言
如果一个api需要在特定线程执行,记得加上断言判断,否则调用方容易在不合适的线程调用,导致Crash

多线程环境下懒加载变量要加锁

1
2
3
4
5
6
7
8
9
10
11
12
- (id *)pen
{
if (!_pen) {
@synchronized(self) {
if (!_pen) {
_pen = [[BTPen alloc] init];
_pen = @"tea";
}
}
}
return _pen;
}

NSNotificationCenter要防止Self为空
NSNotificationCenter在iOS 8及更老系统上存在多线程bug,selector执行到一半时可能会因为self销毁而触发crash.

1
2
3
4
5
6
7
8
9
- (void)receiveTesNotify:(NSNotification *)notify
{
__weak typeof(self) weakSelf = self;
__strong typeof(self) strongSelf = wself;
if (! weakSelf) {
return;
}
[strongSelf action];
}

使用dispatch_sync要小心

容易死锁。下面将一个Block同步到主线程的队列里。因为你用同步,必须先让你执行,但你放到主线程队列里,却要等待主线程原来的任务执行完。

1
2
3
4
5
6
7
- (void)test {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_block_t block = ^() {
NSLog(@"Hello");
};
dispatch_sync(mainQueue, block);
}

cancelPreviousPerformRequestsWithTarget要防御

对receiver使用cancelPreviousPerformRequestsWithTarget,会导致receiver引用计数减1。如果调用前receiver的引用计数就是1,调用完receiver就会被释放。

1
2
3
4
5
6
7
8
9
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf)
{
//self已经被销毁
return;
}
// 再调用会Crash
[self action];

在dealloc中,不要将self作为参数传递

如果self在此时被retain住,到下个runloop周期再次释放,则会造成多次释放crash。

1
2
3
-(void)dealloc{
[self doSome:self];
}

因为当前已经在self所指向对象的销毁阶段,如果在doSome:中将self放到了autorelease pool中,那么self会被retain住,计划下个runloop周期再进行销毁;但是dealloc运行结束后,self对象的内存空间就直接被回收了,self变成了野指针
当到了下个runloop周期,self指向的对象实际上已经被销毁,会因为非法访问造成crash问题

使用锁要避免出现特殊逻辑锁没有被回收

如果某个方法里锁未被回收,下次调用该方法会造成线程死锁

可能提前出现return,要在return前回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void) doSomething{
[self.lock lock];
if (type == 1){
.....
[self.lock unlock];
return;
}
else if(type == 2){
.....
[self.lock unlock];
return;
}
.....
[self.lock unlock];
}

可能出现异常,要在finally回收

1
2
3
4
5
6
7
8
9
10
- (void) doSomething{
[self.lock lock];
@try{
...
}@catch(NSException* ex){
}@finally{
......
[self.lock unlock];
}
}
Blacktea wechat
ex. subscribe to my blog by scanning my public wechat account
记录生活于感悟,您的支持将鼓励我继续创作!