iOS|iOS 底层 day25 内存管理 MRC copy

一、MRC
1. MRC基本介绍

  • 现在我们 iOS 开发都在使用 ARC,基本上不会使用 MRC,那为什么我们还要学习 MRC 呢?
  • 因为 ARC 的本质就是 MRC,我们只有学习了 MRC,才能理解 ARC 下的许多操作,以及解决一些诡异的问题
2. 内存泄露是什么意思?
  • 无论 ARC 还是 MRC 都是内存管理,既然是内存管理就离不开内存泄露的概念
  • 内存泄露:该释放的对象没有释放
3. 请在 MRC 下,实现 Person 对象有一条Dog,调用Dogrun 方法,最后 DogPerson 都被释放。
  • Dog.h 代码如下
#import "Person.h" @interface Person : NSObject { Dog *_dog; } - (Dog*)dog; - (void)setDog:(Dog*)dog; @end

  • Person.m 代码如下
#import "Person.h" @implementation Person - (void)setDog:(Dog*)dog{ _dog = dog; [_dog retain]; } - (Dog*)dog { return _dog; } - (void)dealloc { NSLog(@"%s", __func__); [_dog release]; [super dealloc]; } @end

  • 调用代码如下
#import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *person = [[Person alloc] init]; Dog* dog1 = [[Dog alloc] init]; person.dog = dog1; [dog1 release]; [person.dog run]; [person release]; } NSLog(@"2"); return 0; }

  • 上述代码能正常调用 dog 的 run 方法吗?
  • 上述代码能正常释放 dog 和 person 对象吗?
  • 上述代码严谨吗?在什么情况下会报错或者内存泄露?
  • 不严谨,①当给 person 重复设置值 dog1时,dog1 将无法释放;② 当给 person 换一条狗 dog2 的时候,dog1 将无法释放;
4. 思考我们如下修改 Person.m 可以解决问题吗?
#import "Person.h" @implementation Person - (void)setDog:(Dog*)dog{ [_dog release]; _dog = [dog retain]; } - (Dog*)dog { return _dog; } - (void)dealloc { NSLog(@"%s", __func__); [_dog release]; [super dealloc]; } @end

  • 如上写法还是不够严谨
  • 如下调用,开启 Xcode 的 Zombie Objects 僵尸对象检查功能,就会报错
#import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *person = [[Person alloc] init]; Dog* dog1 = [[Dog alloc] init]; // 1 person.dog = dog1; // 2 [dog1 release]; // 1 person.dog = dog1; // 内部 set 方法将 `dog1` 的引用计数器释放到 `0` [person.dog run]; [person release]; } NSLog(@"2"); return 0; }

  • 因为不够严谨,容易导致我们会将 dog1 的引用计数器释放到 0
5. 针对上一个问题,我们如何继续完善代码?
  • 修改 Person.m 代码中的 set 方法
#import "Person.h" @implementation Person - (void)setDog:(Dog*)dog{ if (_dog != dog) { [_dog release]; _dog = [dog retain]; } } - (Dog*)dog { return _dog; } - (void)dealloc { NSLog(@"%s", __func__); [_dog release]; [super dealloc]; } @end

  • 上述 Person 就是我们在 MRC 下常用的写法,也是我们 ARC 下编写代码会转换成的`最终样子。
6. 补充介绍:什么是 Xcode 的 Zombie Objects 僵尸对象检查功能?
  • 开启方法:先选中Product -> Scheme -> Edit Scheme -> Diagnostics -> 勾选Zombie Objects
  • 原理:通过生成僵尸对象来替换dealloc的实现,当对象引用计数为0的时候,将需要dealloc的对象转化为僵尸对象
  • 功能:如果之后再给这个僵尸对象发消息,则抛出异常
二、 copy
1. 拷贝的目的(记住这个,可以帮助我们理解很多代码层次的知识)
  • 产生一个副本对象,跟源对象互不影响
  • 修改了源对象,不会影响副本对象
  • 修改了副本对象,不会影响源对象
2. iOS 提供了 2 个拷贝的方法
  • copy:不可变拷贝,产生不可变副本
  • mutableCopy:可变拷贝,产生可变副本
3. 深拷贝和浅拷贝
  • 深拷贝: 内容拷贝,产生新对象
  • 浅拷贝: 指针拷贝,没有产生新的对象
4. copymutableCopyNSArray、NSMutableArray、NSString、NSMutableString、NSDictionary、NSMutableDictionary 的效果有什么不同? `copy` 和 `mutableCopy` 效果图 5.思考下面这句代码写法有问题吗?
@property(copy, nonatomic) NSMutableArray *data;

  • 这是一种不好的写法
  • data 如果用 copy 修饰,那么 data 的类型实际上就是 NSArray。然而又告诉别人这个是 NSMutableArray 类型,如果别人调用 NSMutableArray 特有的方法,就会报错。
6.为什么字符串普遍都用 copy 修饰呢? UITextField 中字符串的修饰
  • 这样我们可以保证内部拿到的字符串不轻易受外部的影响
7. 对于 Foundation 框架内部有许多类为我们实现了 copymutableCopy,那如果对于我们自定义的类,比如 Person 调用 copy 会有效果吗?
  • 不能对自定义的类之间调用 copy
  • 会报错-[Person copyWithZone:]: unrecognized selector sent to instance 0x100494530
  • 需要遵守NSCopying协议,以及实现 -[Person copyWithZone:] 方法
- (id)copyWithZone:(NSZone *)zone { Person *person = [[Person allocWithZone:zone] init]; person.age = self.age; person.weight = self.weight; return person; }

    推荐阅读