概述
三种主要的内存管理方式:
- 显示内存释放:C 中的 free、C++ 中的 delete;
- 基于引用计数:C++ 中的智能指针(smart pointer),Objective-C 中使用的方式;
- 垃圾回收机制:Java、JavaScript、C#;
OS X (macOS) 过去支持垃圾回收,现在已经废除了。iOS 则一直不支持垃圾回收。
引用计数
iOS 支持 MRC (Mannul Reference Counting) 手动引用计数与 ARC (Automatic Reference Counting) 自动引用计数两种管理机制。
MRC
手动引用计数主要有以下的几个相关的操作:
- 生成(分配一个对象,并且引用计数置为 1):
alloc
、cpoy
、new
; - 持有(将一个对象的引用计数自增 1):
retain
; - 释放(将一个对象的引用计数自减 1):
release
、autorelease
; - 废弃(释放一个对象,引用计数降为 0 应该废弃):
dealloc
; - 显示(显示一个对象的引用计数):
retainCount
。
为什么要引入 autorelease
?
在初始化一个对象的时候自动指明,如果对象的引用计数降为 0,则自动释放这个对象。
比如在未使用
autorelease
的时候一个代码可能是这么写的:1 2 3 4 5 6 7 8 9 10 11 12 13
- (People *)createPeople { People *people = [[People alloc] init]; people.name = @"小王"; people.age = 10; people.male = YES; return people; } - (void)func2 { People *people = [self createPeople]; [people trainingDog]; [people release]; }
如果使用了这个关键字,则代码会被写成这样:
1 2 3 4 5 6 7 8 9 10 11 12
- (People *)createPeople { People *people = [[[People alloc] init] autorelease]; people.name = @"小王"; people.age = 10; people.male = YES; return people; } - (void)func2 { People *people = [self createPeople]; [people trainingDog]; }
什么是 AutoReleasePool
?
线程在一个 Autorelease Pool 的上下文中执行,线程任务完成后销毁,主线程不会销毁;
在一次事件循环结束后,Autorelease Pool 中声明的变量会被自动释放;
如果在这个线程中定义了大量的变量,在释放变量时则会导致短时间的内存占用过高。比如下面的代码:
1 2 3 4
for (int i = 0; i < 100; i++) { NSString *fileContents = [NSString stringWithContentsOfURL:urlArray[i] encoding:NSUnicodeStringEncoding error:nil]; NSLog(@"%@", fileContents); }
使用
AutoReleasePool
则可以写成下面的形式:1 2 3 4 5 6
for (int i = 0; i < 100; i++) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *fileContents = [NSString stringWithContentsOfURL:urlArray[i] encoding:NSUnicodeStringEncoding error:nil]; NSLog(@"%@", fileContents); [pool release]; }
也可以用比较语法糖的写法:
1 2 3 4 5 6
for (int i = 0; i < 100; i++) { @autoreleasepool { NSString *fileContents = [NSString stringWithContentsOfURL:urlArray[i] encoding:NSUnicodeStringEncoding error:nil]; NSLog(@"%@", fileContents); } }
如何解决循环引用?
循环引用指两个对象相互持有,导致无法被释放的状态。比如:
1 2 3 4 5 6 7
@interface People : NSObjec @property(nonatomic, retain) Dog *dog; @end @interface Dog : NSObject @property(nonatomic, retain) People *owner; @end
解决循环引用,只需要将其中一个持有改为赋值即可:
1 2 3 4 5 6 7
@interface People : NSObjec @property(nonatomic, retain) Dog *dog; @end @interface Dog : NSObject @property(nonatomic, assign) People *owner; @end
ARC
系统会检测出何时需要保持对象,何时需要自动释放对象,何时需要释放对象,编译器会管理好对象的内存,会在何时的地方插入 retain
, release
和 autorelease
,通过生成正确的代码去自动释放或者保持对象。
相比于 MRC,ARC 使用的是以下的几个关键词:
- 生成仍然保留
alloc
、new
、cpoy
; - 持有使用关键词
strong
(相对于 MRC 中的关键词retain
); - 不持有则使用关键词
weak
(相对于 MRC 中的关键词assign
,区别),当对象没有被强持有时置为nil
; - 释放保留了之前的两个关键词,引入了新的关键词
dealloc
,它的好处是无需要释放父类;
两种拷贝协议:
NSCopying
:调用 copy 方法,需要实现 NSCopying 协议,否则 Crash。比如下面的调用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
@class People; @interface Dog : NSObject <NSCopying> @property(nonatomic, strong) NSString *name; @property(nonatomic, assign) NSInteger weight; @property(nonatomic, weak) People *owner; @end @implementation Dog - (id)copyWithZone:(nullable NSZone *)zone { Dog *dog = [[Dog alloc] init]; dog.name = self.name; dog.weight = self.weight; dog.owner = self.owner; return dog; } @end
NSMutableCopying
:字符串、数组、字典等对象,需要进行深拷贝对象,需要实现 NSMutableCopying。比如下面的调用:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
@class People; @interface Dog : NSObject <NSMutableCopying> @property(nonatomic, strong) NSString *name; @property(nonatomic, assign) NSInteger weight; @property(nonatomic, weak) People *owner; @end @implementation Dog - (id)mutableCopyWithZone:(nullable NSZone *)zone { Dog *dog = [[Dog alloc] init]; dog.name = self.name; dog.weight = self.weight; dog.owner = self.owner; return dog; } @end
这个协议的实现可以设计成 不可变与可变 两种形式,其区别就在于外界是否可以对对象进行修改(我感觉就类似于 c++ 中的引用与常量引用)。