IT评测·应用市场-qidao123.com

标题: iOS内存管理之MRC [打印本页]

作者: 盛世宏图    时间: 2022-9-16 17:16
标题: iOS内存管理之MRC
前言:

在iOS中,使用引用计数来管理OC对象内存
一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。
调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1。
内存管理的经验总结
当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease释放它。
想拥有某个对象,就让他的引用计数+1;不想再拥有某个对象,就让他的引用计数-1。
一、 MRC 手动管理内存(Manual Reference Counting)

1、引用计数器

引用计数器:
一个整数,表示为「对象被引用的次数」。系统需要根据对象的引用计数器来判断对象是否需要被回收。
关于「引用计数器」,有以下几个特点:
2、引用计数器操作

  1. // 创建一个对象,默认引用计数器是 1
  2. RHPerson *person1 = [[RHPerson alloc] init];
  3. NSLog(@"retainCount = %zd", [person1 retainCount]);
  4. // 只要给对象发送一条 retain 消息,引用计数器加1
  5. [person1 retain];
  6. NSLog(@"retainCount = %zd", [person1 retainCount]);
  7. // 只要给对象发送一条 release 消息,引用计数器减1
  8. [person1 release];
  9. NSLog(@"retainCount = %zd", [person1 retainCount]);
  10. // 当 retainCount 等于0时,对象被销毁
  11. [person1 release];
  12. NSLog(@"--------------");
复制代码
  1. 2022-07-11 16:09:24.102850+0800 Interview01-内存管理[8035:264221] retainCount = 1
  2. 2022-07-11 16:09:24.103083+0800 Interview01-内存管理[8035:264221] retainCount = 2
  3. 2022-07-11 16:09:24.103126+0800 Interview01-内存管理[8035:264221] retainCount = 1
  4. 2022-07-11 16:09:24.103231+0800 Interview01-内存管理[8035:264221] -[RHPerson dealloc]
  5. 2022-07-11 16:09:24.103259+0800 Interview01-内存管理[8035:264221] --------------
  6. Program ended with exit code: 0
复制代码
3、dealloc 方法

  1. - (void)dealloc {
  2.     NSLog(@"%s", __func__);
  3.     [super dealloc];
  4. }
复制代码
dealloc 使用注意:
  1. RHPerson *person1 = [[RHPerson alloc] init];
  2. [person1 release];
  3. [person1 release];
  4. [person1 release];
复制代码
二、内存管理思想

1、单个对象内存管理思想

思想一:自己创建的对象,自己持有,自己负责释放

  1. id obj1 = [[NSObject alloc] init];
  2. [obj1 release];
  3. id obj2 = [NSObject new];
  4. [obj2 release];
复制代码
思想二:非自己创建的对象,自己也能持有

  1. id obj3 = [NSArray array];
  2. [obj3 retain];
  3. [obj3 release];
复制代码
2、多个对象内存管理思想

多个对象之间往往是通过 setter 方法产生联系的,其内存管理的方法也是在 setter 方法、dealloc 方法中实现的。所以只有了解了 setter 方法是如何实现的,我们才能了解到多个对象之间的内存管理思想。
  1. #import <Foundation/Foundation.h>
  2. #import "RHRoom.h"
  3. NS_ASSUME_NONNULL_BEGIN
  4. @interface RHPerson : NSObject
  5. {
  6.     RHRoom *_room;
  7. }
  8. - (void)setRoom:(RHRoom *)room;
  9. - (RHRoom *)room;
  10. @end
  11. NS_ASSUME_NONNULL_END
复制代码
  1. #import "RHPerson.h"
  2. @implementation RHPerson
  3. - (void)setRoom:(RHRoom *)room {
  4.     if (_room != room) {
  5.         [_room release];
  6.         _room = [room retain];
  7.     }
  8. }
  9. - (RHRoom *)room {
  10.     return _room;
  11. }
  12. - (void)dealloc {
  13.     [_room release];
  14.     [super dealloc];
  15.     NSLog(@"%s", __func__);
  16. }
  17. @end
复制代码
三、 @property 参数

  1. @property(nonatomic) int val;
复制代码
  1. @property(nonatomic, assign) int val;
复制代码
  1. @property(nonatomic, retain) RHRoom *room;
复制代码
四、自动释放池

1、自动释放池

当我们不再使用一个对象的时候应该将其空间释放,但是有时候我们不知道何时应该将其释放。为了解决这个问题,Objective-C 提供了 autorelease 方法。
注意:这里只是发送 release 消息,如果当时的引用计数(reference-counted)依然不为 0,则该对象依然不会被释放。
  1. NSObject *obj = [NSObject new];
  2. [obj autorelease];
  3. NSLog(@"obj.retainCount = %zd", obj.retainCount);
复制代码
2、使用 autorelease 有什么好处呢?

3、autorelease 的原理实质上是什么?

​        autorelease 实际上只是把对 release 的调用延迟了,对于每一个 autorelease,系统只是把该对象放入了当前的 autorelease pool 中,当该 pool 被释放时,该 pool 中的所有对象会被调用 release 方法。
4、autorelease 的创建方法
  1. // 第一种方式:使用 NSAutoreleasePool 创建
  2. // 创建自动释放池
  3. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  4. // 销毁自动释放池
  5. [pool release];
复制代码
  1. // 第二种方式:使用 @autoreleasepool 创建
  2. @autoreleasepool {
  3. // 开始代表创建自动释放池
  4. // 结束代表销毁自动释放池
  5. }
复制代码
5、autorelease 的使用方法
  1. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  2. RHPerson *p = [[[RHPerson alloc] init] autorelease];
  3. [pool release];
复制代码
  1. @autoreleasepool {
  2. // 开始代表创建自动释放池
  3. RHPerson *p = [[[RHPerson alloc] init] autorelease];
  4. // 结束代表销毁自动释放池
  5. }
复制代码
6、autorelease 的注意事项

  1. @autoreleasepool {
  2. // 因为没有调用 autorelease,所以没有加入到自动释放池中
  3. RHPerson *p = [[RHPerson alloc] init];
  4. // 结束代表销毁自动释放池
  5. }
复制代码
  1. @autoreleasepool {
  2. }
  3. // 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会放到释放池
  4. Person *p = [[[Person alloc] init] autorelease];
  5. [p run];
  6. // 正确写法
  7. @autoreleasepool {
  8.     Person *p = [[[Person alloc] init] autorelease];
  9. }
  10. // 正确写法
  11. Person *p = [[Person alloc] init];
  12. @autoreleasepool {
  13.     [p autorelease];
  14. }
复制代码
7、自动释放池的嵌套使用

栈顶就是离调用 autorelease 方法最近的自动释放池。
  1. @autoreleasepool { // 栈底自动释放池
  2.     @autoreleasepool {
  3.         @autoreleasepool { // 栈顶自动释放池
  4.             Person *p = [[[Person alloc] init] autorelease];
  5.         }
  6.         Person *p = [[[Person alloc] init] autorelease];
  7.     }
  8. }
复制代码
  1. // 内存暴涨
  2. @autoreleasepool {
  3.     for (int i = 0; i < 99999; ++i) {
  4.         Person *p = [[[Person alloc] init] autorelease];
  5.     }
  6. }
复制代码
  1. // 内存不会暴涨
  2. for (int i = 0; i < 99999; ++i) {
  3.     @autoreleasepool {
  4.         Person *p = [[[Person alloc] init] autorelease];
  5.     }
  6. }
复制代码
8、autorelease 错误用法

  1. @autoreleasepool {
  2. // 错误写法, 过度释放
  3.     Person *p = [[[[Person alloc] init] autorelease] autorelease];
  4. }
复制代码
  1. @autoreleasepool {
  2.     Person *p = [[[Person alloc] init] autorelease];
  3.     [p release]; // 错误写法, 过度释放
  4. }
复制代码
五、 MRC 中避免循环引用

定义两个类 Person 类和 Dog 类
  1. #import <Foundation/Foundation.h>
  2. @class Dog;
  3. @interface Person : NSObject
  4. @property(nonatomic, retain)Dog *dog;
  5. @end
复制代码
  1. #import <Foundation/Foundation.h>
  2. @class Person;
  3. @interface Dog : NSObject
  4. @property(nonatomic, retain)Person *owner;
  5. @end
复制代码
执行以下代码:
  1. int main(int argc, const char * argv[]) {
  2.     Person *p = [Person new];
  3.     Dog *d = [Dog new];
  4.     p.dog = d; // retain
  5.     d.owner = p; // retain  assign
  6.     [p release];
  7.     [d release];
  8.     return 0;
  9. }
复制代码
就会出现 A 对象要拥有 B 对象,而 B 对应又要拥有 A 对象,此时会形成循环 retain,导致 A 对象和 B 对象永远无法释放。
那么如何解决这个问题呢?
不要让 A retain B,B retain A。
让其中一方不要做 retain 操作即可。
当两端互相引用时,应该一端用 retain,一端用 assign。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4