iOS 底层 - OC对象内存管理之MRC

本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !

作为一名开发者来说对内存管理一定不会感到陌生,那么内存管理到底是管理哪部分的内存呢?

对iOS 程序内存布局比较了解的应该会知道内存布局分为:保留区、代码段、数据段、堆区、栈区、内核区;真正需要开发者参与管理的是:在堆空间申请的内存;如果程序员不释放,程序结束后,会由操作系统回收; 这里的结束也有可能是 崩溃导致 的结束

我多句嘴啊,为什么栈空间不需要开发者参与管理内存 ?

对啊,这是为什么呢 ? 嗯 原来是由编译器自动分配释放,所以不需要开发者参与咯 !

简单描述栈空间的内存管理原则 ?

分配:编译阶段由编译器来给参数值、局部变量等分配存储空间
释放:当超出其作用域时,由编译器控制从栈中弹出。

Manual Reference Counting :MRC, 手动引用计数

Automatic Reference Counting : ARC ,自动引用计数

  • MRC:遵循谁申请、谁添加、谁释放的原则。需要手动处理内存计数的 +1-1。从12年iOS5开始,逐步被ARC(自动引用计数)模式取代。

  • ARC取代了MRC后,在App编译阶段,由编译器(LLVM)在合适的位置添加了OC对象的内存管理代码。

OC对象的内存管理原则:

  • 在iOS中,使用引用计数来管理OC对象的内存

  • 一个新创建的OC对象引用计数默认是1,当引用计数减为0时;OC对象就会销毁,释放其占用的内存空间

  • 调用retain会让OC对象的引用计数 +1,调用release会让OC对象的引用计数 -1

内存管理的经验总结

  • 当调用allocnewcopymutableCopy方法返回了一个对象,当不需要这个对象时,就要调用release或者autorelease来释放它

  • [NSMutableArray array][NSMutableDictionary dictionary]等创建出来的对象不需要再添加内存管理代码(releaseretain),
    因为方法内部已经实现的内存管理代码

  • 想拥有某个对象,就让它的引用计数 加一; 不想再拥有时,就让它的引用计数 减一

  • MRC环境OC对象的声明:使用retain关键字修饰,在dealloc的父类方法调用前释放; 不需要实现setter方法的内存管理代码,因为用retain关键字修饰后已经自动实现了setter方法、getter方法以及内存管理代码

MRC环境OC对象的声明示例

@property (nonatomic, retain)XYHCar  *car;

- (void)dealloc
{
//    [_car release];
//    _car = nil;
//或
    self.car = nil;    
    // 父类的dealloc放到最后
    [super dealloc];
}

  • 可以通过以下私有函数来查看自动释放池的情况
void _objc_autoreleasePoolPrint(void)

这很奇怪,既然是私有函数怎么能够被外界调用呢 ?怎么做到的呢 ?
可以的,使用 extern 关键字(前提是该函数没有被struct修饰为静态方法)对该函数在需要用到的地方重新声明一遍,真正调用时编译器会去查找有没有这个函数,只要有就会调用啦 ! 比如:

示例代码

传入void表示没有参数
extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
   @autoreleasepool { //  r1 = push()
       
       XYHOlg *olg1 = [[[XYHOlg alloc] init] autorelease];
       XYHOlg *olg2 = [[[XYHOlg alloc] init] autorelease];
       
       @autoreleasepool { // r2 = push()
           for (int i = 0; i < 6; i++) {
               XYHOlg *olg3 = [[[XYHOlg alloc] init] autorelease];
               NSLog(@"%@",olg3);
           }
           @autoreleasepool { // r3 = push()
               XYHOlg *olg4 = [[[XYHOlg alloc] init] autorelease];
               _objc_autoreleasePoolPrint();
               NSLog(@"%@,%@,%@",olg1,olg2,olg4);
           } // pop(r3)
       } // pop(r2)
       
   } // pop(r1)
   
   return 0;
}

输出结果
(hot) :当前 pool page
(cold):不是当前 pool page
(hot)(cold): 就一个pool page,怎么着都行

objc[1363]: ##############
objc[1363]: AUTORELEASE POOLS for thread 0x1000d1dc0
objc[1363]: 12 releases pending.
objc[1363]: [0x100805000]  ................  PAGE  (hot) (cold)
objc[1363]: [0x100805038]  ################  POOL 0x100805038 第一个自动释放池
objc[1363]: [0x100805040]       0x10051fbc0  XYHOlg
objc[1363]: [0x100805048]       0x10051fc10  XYHOlg
objc[1363]: [0x100805050]  ################  POOL 0x100805050 第二个自动释放池
objc[1363]: [0x100805058]       0x10051fc20  XYHOlg
objc[1363]: [0x100805060]       0x10051fc30  XYHOlg
objc[1363]: [0x100805068]       0x100520130  XYHOlg
objc[1363]: [0x100805070]       0x100520140  XYHOlg
objc[1363]: [0x100805078]       0x100520150  XYHOlg
objc[1363]: [0x100805080]       0x100520160  XYHOlg
objc[1363]: [0x100805088]  ################  POOL 0x100805088 第三个自动释放池
objc[1363]: [0x100805090]       0x100520170  XYHOlg
objc[1363]: ##############
Program ended with exit code: 0

release和autorelease的区别 ?

  • release:在使用完对象后调用
  • autorelease:
    在初始化的时候调用,
    一个对象被autorelease,则该对象内存地址将被追加到当前任务的AutoreleasePool(自动释放池)里,通过AutoreleasePoolPage对象来管理,在合适的时机释放
    AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage双向链表的形式组合而成(分别对应结构中的parent指针和child指针)

ARC环境声明OC对象可以用assign来修饰吗 ?为什么?

  • 当然可以用来修饰OC对象,且编译器还不会报错,但不推荐
  • assign关键字修饰的OC对象在 setter方法不会生成 相关的内存管理代码
  • assign关键字修饰的对象释放后,指针不会被清空 依然指向释放前的对象;这很危险,在后续的操作中如果用到该对象会出现悬垂指针访问导致崩溃
  • assign: 多用来修饰基本数据类型

小小知识点:指针

注意:这里的内存管理 范围OC对象 ,至于基本数据类型如:intfloat不在范围之内;

OC对象的引用计数都是自然数吗 ?

绝大多数是的,只有NSString变NSTaggedPointerString时引用计数为其内存地址的十进制数

在开发中就没有需要用到OC对象之外的数据类型了吗 ?他们的内存管理该如何实现 ?

在iOS开发过程中可能会用到的语言有很多,比如:CC++RNSwiftJavascript等;这其中最多使用到是C的一些数组、字符串、指针, C语言中对堆内存的申请是调用malloc函数实现、释放是调用free函数实现;
Javascript 具有自动垃圾回收机制,执行环境会管理代码执行过程中使用的内存。

示例代码:

int _tmain(int argc, _TCHAR* argv[])
{
    char *p = (char *)malloc(1024*1024*1024);//在堆中申请了内存
    memset(p, 'a', sizeof(int) * 10);//初始化内存
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        p[i] = i + 65;
    }
    print_array(p, 10);
    free(p);//释放申请的堆内存
}

你可能感兴趣的:(iOS 底层 - OC对象内存管理之MRC)