Objective-C的MRC手动内存管理
0x01 自动释放池的创建
autorelease
NSObject类提供了一个autorelease方法,该方法预先设定了一条会在未来某个时间发送的release消息,其返回值是接收这条消息的对象:
- (id) autorelease;
当给一个对象发送autorelease消息时,实际上是将该对象添加到了自动释放池中。
当自动释放池被销毁时,会向该池中的所有对象发送release消息。
NSAutoreleasePool对象
使用NSAutoreleasePool对象可以创建一个自动释放池,在创建和释放NSAutoreleasePool对象之间的代码都会使用该池:
NSAutoreleasePool *pool;
pool = [NSAutoreleasePool new];
//Coding in here...
[pool release];
0x02 自动释放池的工作原理
在我们创建基于Foundation的项目时,其实编译器已经自动帮我们创建了@autorelease的自动释放池:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
1、当我们把代码写在@autoreleasepool的花括号里时,所有的代码都是自动释放的;
2、任何在@autoreleasepool的花括号定义的变量,在括号外是无法使用的,类似于C语言的数据有效范围;
3、iOS 5.0之后已经淘汰使用NSAutoreleasePool对象创建自动释放池的方法,因为@autoreleasepool更直观,而且Objective-C语言创建和释放内存的能力远在我们之上。
4、autorelease只是把对象扔到自动释放池里面(延迟了对象被销毁的时间),并不可以改变对象的计数器;
5、改变计数器只有retain和release两种方法;
6、autorelease和release不能同时存在:
autoreleasepool{
Car *c = [[[Car alloc] init] autorelease];
[c release];
}
//错误!!!
//对对象执行二次释放会出现野指针错误!!!
7、autoreleasepool是以栈结构存在的,栈结构的特点就是先进后出;
8、autorelease的缺点是不能精确控制对象被销毁的时间,内存处理效率低(适用于占用内存比较小的对象,对象里面包含的属性主要是基本数据类型)。如果是在一个循环中出现大量的迭代,必须自己在循环中创建一个自动释放池来定期清理:
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
int i;
for (i = 0; i < 1000000; i++) {
id object = [someArray objectAtIndex: i];
NSString *desc = [object descrption];
// and do something with the description
if (i % 1000 == 0) {
[pool release];
pool = [[NSAutoreleasePool alloc] init];
}
}
[pool release];
9、在调用函数的过程中自动释放池不会被销毁,程序不会随即销毁自动释放池,因此不必保留使用的每一个对象;
10、自动释放池被清理的时间是完全确定的:要么在代码中自己手动销毁,要么是使用APPKit时在时间循环结束时销毁。
C++ 智能指针 shared_ptr, 更多智能指针
shared_ptr 是C++11提供的一种智能指针类,它足够智能,可以在任何地方都不使用时自动删除相关指针,从而帮助彻底消除内存泄漏和悬空指针的问题。
它遵循共享所有权的概念,即不同的 shared_ptr 对象可以与相同的指针相关联,并在内部使用引用计数机制来实现这一点。
每个 shared_ptr 对象在内部指向两个内存位置:
- 指向对象的指针。
- 用于控制引用计数数据的指针。
共享所有权如何在参考计数的帮助下工作:
- 当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
- 当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。
创建 shared_ptr 对象
使用原始指针创建 shared_ptr 对象
std::shared_ptr<int>p1(newint());
上面这行代码在堆上创建了两块内存:1:存储int
。2:用于引用计数的内存,管理附加此内存的 shared_ptr 对象的计数,最初计数将为1。
检查 shared_ptr 对象的引用计数
p1.use_count();
创建空的 shared_ptr 对象
因为带有参数的 shared_ptr 构造函数是 explicit 类型的,所以不能像这样std::shared_ptr<int> p1 = new int();
隐式调用它构造函数。创建新的shared_ptr对象的最佳方法是使用std :: make_shared:
std::shared_ptr<int> p1 = std::make_shared<int>();
std::make_shared一次性为int
对象和用于引用计数的数据都分配了内存,而new
操作符只是为int
分配了内存。
分离关联的原始指针
要使 shared_ptr 对象取消与相关指针的关联,可以使用reset()
函数:
不带参数的reset():
p1.reset();
它将引用计数减少1,如果引用计数变为0,则删除指针。
带参数的reset():
p1.reset(newint(34));
在这种情况下,它将在内部指向新指针,因此其引用计数将再次变为1。
使用nullptr重置:
p1 =nullptr;
shared_ptr是一个伪指针
shared_ptr充当普通指针,我们可以将*
和->
与 shared_ptr 对象一起使用,也可以像其他 shared_ptr 对象一样进行比较;
完整示例
#include <iostream>
#include <memory> // 需要包含这个头文件
int main()
{
// 使用 make_shared 创建空对象
std::shared_ptr<int> p1 = std::make_shared<int>();
*p1 = 78;
std::cout << "p1 = " << *p1 << std::endl; // 输出78
// 打印引用个数:1
std::cout << "p1 Reference count = " << p1.use_count() << std::endl;
// 第2个 shared_ptr 对象指向同一个指针
std::shared_ptr<int> p2(p1);
// 下面两个输出都是:2
std::cout << "p2 Reference count = " << p2.use_count() << std::endl;
std::cout << "p1 Reference count = " << p1.use_count() << std::endl;
// 比较智能指针,p1 等于 p2
if (p1 == p2) {
std::cout << "p1 and p2 are pointing to same pointer\n";
}
std::cout<<"Reset p1 "<<std::endl;
// 无参数调用reset,无关联指针,引用个数为0
p1.reset();
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
// 带参数调用reset,引用个数为1
p1.reset(new int(11));
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
// 把对象重置为NULL,引用计数为0
p1 = nullptr;
std::cout << "p1 Reference Count = " << p1.use_count() << std::endl;
if (!p1) {
std::cout << "p1 is NULL" << std::endl; // 输出
}
return 0;
}
2012 年的时候,SF 上也有人提问过类似的问题:
Is it possible to introduce Automatic Reference Counting (ARC) to C++?
个人觉得,如果语言加上 ARC 特性,那么也需要加上 AutoreleasePool 才能完美配合,而且内存管理模型也需要大改。
MyClass getMyClass() {
// 这里要保证返回对象不析构,需要 AutoreleasePool 来让返回的对象延迟释放
return MyClass();
}
但是 C++ 向后兼容的历史包袱导致不能增加这种特性。
这就导致想向增加或者借鉴某个语言的优点时,就显得非常困难。
最后只能造出智能指针这半自动的玩意。
真心希望 C++ 能大胆革新,果断在新标准中抛弃历史包袱。
南来地,北往的,上班的,下岗的,走过路过不要错过!
======================个性签名=====================
之前认为Apple 的iOS 设计的要比 Android 稳定,我错了吗?
下载的许多客户端程序/游戏程序,经常会Crash,是程序写的不好(内存泄漏?刚启动也会吗?)还是iOS本身的不稳定!!!
如果在Android手机中可以简单联接到ddms,就可以查看系统log,很容易看到程序为什么出错,在iPhone中如何得知呢?试试Organizer吧,分析一下Device logs,也许有用.