《编写高质量OC代码》已顺利完成一二三四五六七八篇!
附上连接:
iOS 编写高质量Objective-C代码(一)—— 简介
iOS 编写高质量Objective-C代码(二)—— 面向对象
iOS 编写高质量Objective-C代码(三)—— 接口和API设计
iOS 编写高质量Objective-C代码(四)—— 协议与分类
iOS 编写高质量Objective-C代码(五)—— 内存管理机制
iOS 编写高质量Objective-C代码(六)—— block专栏
iOS 编写高质量Objective-C代码(七)—— GCD专栏
iOS 编写高质量Objective-C代码(八)—— 系统框架html
本篇的主题是iOS中的 “内存管理机制”。web
说到iOS内存管理,逃不过iOS的两种内存管理机制:MRC & ARC。 先简单介绍一下: MRC(manual reference counting): “手动引用计数” ,由开发者管理内存。 ARC(automatic reference counting):“自动引用计数”,从
iOS 5
开始支持, ,由编译器帮忙管理内存。编程
iOS 4
以前,全部iOS开发者必需要手动管理内存,即手动管理对象的内存分配和释放。首先,不断插入retain
、release
等内存管理语句,大大加大了工做量和代码量。其次,在面对一些多线程并发操做时,开发者手动管理内存并不简单,还可能会带来不少没法预知的问题。 因此,苹果从iOS 5
开始引入ARC机制,由编译器帮忙管理内存。在编译期,编译器会自动加上内存管理语句。这样,开发者能够更加关注业务逻辑。数组
下面进入正题:编写高质量Objective-C代码(五)——内存管理篇。安全
这里引入《Objective-C 高级编程 iOS与OSX多线程和内存管理》这本书的例子: 很经典的图解:bash
解释: 1.开灯:引伸为:“ 建立对象 ”。 2.关灯:引伸为:“ 销毁对象 ”。多线程
场景 | 对应OC的动做 | 对应OC的方法 |
---|---|---|
上班开灯 | 生成对象 | alloc/new/copy/mutableCopy等 |
须要照明 | 持有对象 | retain |
不须要照明 | 解除持有 | release |
下班关灯 | 销毁对象 | dealloc |
若是以为本书中的例子说的有点抽象难懂,不要紧,请看下面图解示例: ~提示:实箭头为强引用,虚箭头为弱引用。~ 并发
这里有个set方法的例子:框架
- (void)setObject:(id)object {
[object retain];// Added by ARC
[_object release];// Added by ARC
_object = object;
}
复制代码
解释:set方法将保留新值,释放旧值,而后更新实例变量。这三个语句的顺序很重要。 若是先release
再retain
。那么该对象可能已经被回收,此时retain
操做无效,由于对象已释放。这时实例变量就变成了悬挂指针。~悬挂指针:指针指nil的指针。~异步
main
函数里就有一个autoreleasepool
(自动释放池)。int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
autorelease
能延长对象的生命周期,在对象跨越“方法调用边界”后(就是}
后)依然能够存活一段时间。
循环引用(retain cycle
)又称为“保留环”。 造成循环引用的缘由:是对象之间互相经过强指针指向对方(或者说互相强持有对方)。 在开发中,咱们不但愿出现循环引用,由于会形成内存泄漏。 解决方案:有一方使用弱引用(weak reference
),解开循环引用,让多个对象均可以释放。 PS:关于如何检验项目中有无内存泄漏:参考这篇博客。
,在ARC环境下,禁止🚫调用:retain
、release
、autorelease
、dealloc
方法。
使用ARC时必须遵循的方法命名规则: 若方法名以alloc
、new
、copy
、mutableCopy
开头,则规定返回的对象归调用者。
变量的内存管理语义:
对比一下MRC和ARC在代码上的区别
MRC环境下:
- (void)setObject:(id)object {
[_object release];
_object = [object retain];
}
复制代码
这样会出现一种边界状况,若是新值和旧值是同一个对象,那么会先释放掉,object就变成悬挂指针。
ARC环境下:
- (void)setObject:(id)object {
_object = object;
}
复制代码
ARC会用一种更安全的方式解决边界问题:先保留新值,再释放旧值,最后更新实例变量。
同时,ARC能够经过修饰符来改变局部变量和实例变量的语义:
修饰符 | 语义 |
---|---|
__strong | 默认,强持有,保留此值。 |
__weak | 不保留此值,安全。对象释放后,指针置nil。 |
__unsafe_unretained | 不保留此值,不安全。对象释放后,指针依然指向原地址(即不置nil)。 |
__autoreleasing | 此值在方法返回时自动释放。 |
MRC中,开发者须要在dealloc
中动插入必要的清理代码(cleanup code)。 而ARC会借用Objective-C++
的一项特性来完成清理任务,回收OC++对象时,会调用C++的析构函数:底层走.cxx_destruct
方法。而当释放OC对象时,ARC在.cxx_destruct
底层方法中添加所须要的清理代码(这个方法底层的某个时机会调用dealloc
方法)。 不过若是有非OC的对象,仍是要重写dealloc
方法。好比CoreFoundation
中的对象或是malloc()
分配在堆中的内存依然须要清理。这时要适时调用CFRetain
/CFRelease
。
- (void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocatedMemoryBlob);
}
复制代码
调用dealloc
方法时,对象已经处于回收状态了。这时不能调用其余方法,尤为是异步执行某些任务又要回调的方法。若是异步执行完回调的时候对象已经摧毁,会直接crash。
dealloc
方法里要作些释放相关的事情,好比:
举个例子:
- (void)viewDidLoad {
//....
[webView addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"canGoForward" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
[webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
self.backItem.enabled = self.webView.canGoBack;
self.forwardItem.enabled = self.webView.canGoForward;
self.title = self.webView.title;
self.progressView.progress = self.webView.estimatedProgress;
self.progressView.hidden = self.webView.estimatedProgress>=1;
}
- (void)dealloc {
[self.webView removeObserver:self forKeyPath:@"canGoBack"];//< 移除KVO
[self.webView removeObserver:self forKeyPath:@"canGoForward"];
[self.webView removeObserver:self forKeyPath:@"title"];
[self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
}
复制代码
- (void)viewDidLoad {
//......
// 添加响应通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tabBarBtnRepeatClick) name:BQTabBarButtonDidRepeatClickNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(titleBtnRepeatClick) name:BQTitleButtonDidRepeatClickNotification object:nil];
}
// 移除通知
- (void)dealloc {
// [[NSNotificationCenter defaultCenter] removeObserver:self name:BQTabBarButtonDidRepeatClickNotification object:nil];
// [[NSNotificationCenter defaultCenter] removeObserver:self name:BQTitleButtonDidRepeatClickNotification object:nil];
// 或者使用一个语句所有移除
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
复制代码
异常只应在发生严重错误后抛出。 用的很差会形成内存泄漏:在try
块中,若是先保留了某个对象,而后在释放它以前又抛出了异常,那么除非catch块能解决问题,不然对象所占内存就会泄漏。
缘由:C++
的析构函数由Objective-C
的异常处理例程来运行。因为抛出异常会缩短生命期,因此发生异常时必须析构,否则就内存泄漏,而这时若是文件句柄(file handle)等系统资源没有正确清理,就会发生内存泄漏。
try
块内所创立的对象清理干净。-fobjc-arc-exceptions
标志。但很影响性能。因此建议最好仍是不要用。但有种状况是可使用的:Objective-C++
模式。PS:在运行期系统,C++
与Objective-C
的异常互相兼容。也就是说其中任一语言抛出的异常,能用另外一语言所编的**“异常处理程序”**捕获。而在编写Objective-C++
代码时,C++处理异常所用的代码与ARC实现的附加代码相似,编译器自动打开-fobjc-arc-exceptions
标志,其性能损失不大。
最后,仍是建议:
nil
或者0
(例如:初始化的参数不合法,方法返回nil或0)NSError
这条比较简单,内容主旨就是标题:以弱引用避免循环引用(Retain Cycle)
weak
)。weak
,ARC下,对象释放时,指针会置nil
。@autoreleasepool
):每次for循环都会直接释放内存,从而下降了内存的峰值。尤为,在遍历处理一些大数组或者大字典的时候,可使用自动释放池来下降内存峰值,例如:
NSArray *qiShare = /*一个很大的数组*/
NSMutableArray *qiShareMembersArray = [NSMutableArray new];
for (NSStirng *name in qiShare) {
@autoreleasepool {
QiShareMember *member = [QiShareMember alloc] initWithName:name];
[qiShareMembersArray addObject:member];
}
}
复制代码
PS:自动释放池的原理:排布在“栈”中,对象执行autorelease消息后,系统将其放入最顶端的池里(进栈),而清空自动释放池就是把对象销毁(出栈)。而调用出栈的时机:就是当前线程执行下一次事件循环时。
如上图,勾选这里能够开启僵尸对象设置。开启以后,系统在回收对象时,不将其真正的回收,而是把它的isa指针
指向特殊的僵尸类(zombie class),变成僵尸对象。僵尸类可以响应全部的选择子,响应方式为:打印一条包含消息内容以及其接收者的消息,而后终止应用程序。
僵尸对象简单原理:在Objective-C的运行期程序库、Foundation框架以及CoreFoundation框架的底层加入了实现代码。在系统即将回收对象时,经过一个环境变量
NSZombieEnabled
识别是僵尸对象——不完全回收,isa
指针指向僵尸类而且响应全部选择子。
在苹果引入ARC以后retainCount已经正式废弃,任什么时候候都无法调用这个retainCount方法来查看引用计数了,由于这个值实际上已经没有准确性了(并且在ARC环境下也调用不了)。可是在MRC下仍是能够正常使用的。
最后,特别致谢:《Effective Objective-C 2.0》第五章。