面试题引发的思考:
Q: 谈一谈对手动内存管理的理解?
- 在iOS中,使用 引用计数 来管理OC对象的内存;
- 一个新创建的OC对象引用计数默认是1;
当引用计数减为0,OC对象就会销毁,释放其占用的内存空间; - 调用
retain
会让OC对象的引用计数+1;
调用release
会让OC对象的引用计数-1;
Q: 内存管理的经验总结:
- 当调用
alloc
、new
、copy
、mutableCopy
方法返回了一个对象;
在不需要这个对象时,要调用release
或者autorelease
来释放它; - 想拥有某个对象,就让它的引用计数+1;
不想再拥有某个对象,就让它的引用计数-1; - 可以通过以下私有函数来查看自动释放池
的情况extern void _objc_autoreleasePoolPrint(void);
Q: 关键字@synthesize
和@dynamic
区别是什么?
-
@synthesize
:
编译器期间,让编译器自动生成getter
/setter
方法;
当有自定义的存或取方法时,自定义会屏蔽自动生成该方法。 -
@dynamic
:
告诉编译器,不自动生成getter
/setter
方法,避免编译期间产生警告;
然后由自己实现存取方法。
0. 新版本iOS项目设置为MRC运行崩溃解决办法
Xcode -> Targets --> Build Settings --> 搜索“automatic reference counting”,MRC模式设置为“NO”,ARC模式设置为“YES”。
野指针访问修改main
函数:
1. 内存管理介绍
(1) 内存管理
iOS的内存管理一般指的是 OC对象 的内存管理:
- 因为OC对象分配在 堆内存,堆内存需要程序员自己去动态分配和回收;
- 基础数据类型(非OC对象)则分配在 栈内存 中,超过作用域就会由系统检测回收;
- 如果我们在开发过程中,对内存管理得不到位,就有可能造成内存泄露。
(2) 内存管理种类
我们通常讲的内存管理分为两种:MRC 和 ARC。
- MRC(Manual Reference Counting):指的是手动内存管理,
在开发过程中需要开发者手动去编写内存管理的代码;- ARC(Automatic Reference Counting):指的是自动内存管理,
由LLVM编译器和OC运行时库生成相应内存管理的代码。
本文主要介绍关于MRC环境下的内存管理方法。
2. 代码分析
(1) 示例一
首先在“TARGETS”的“Build Setting”分类下搜索“Automatic Reference Counting”,选择“NO”,关闭ARC,将项目改成MRC手动内存管理。
创建一个Person
类:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
@end
@implementation Person
- (void)dealloc {
NSLog(@"%s", __func__);
[super dealloc];
}
@end
1> 手动释放
运行以下程序,可以手动进行release
操作:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
NSLog(@"%zd", person.retainCount);
[person release];
}
return 0;
}
// 打印结果
Demo[1234:567890] 1
Demo[1234:567890] -[Person dealloc]
以上写法是MRC代码的标准写法,如果创建person
对象之后,不执行[person release];
语句,就会造成内存泄漏,因为该释放的对象person
没有释放。
2> 自动释放
运行以下程序,通过autorelease
自动进行release
操作:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 自动释放 - 在自动释放池结束的时候,会对调用autorelease的对象进行release操作
Person *person = [[[Person alloc] init] autorelease];
NSLog(@"start");
}
NSLog(@"end");
return 0;
}
// 打印结果
Demo[1234:567890] start
Demo[1234:567890] -[Person dealloc]
Demo[1234:567890] end
自动释放:在自动释放池结束的时候,会对调用autorelease
的对象进行release
操作。
(2) 示例二
创建一个Dog
类:
// TODO: ----------------- Dog类 -----------------
@interface Dog : NSObject
- (void)run;
@end
@implementation Dog
- (void)run {
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[super dealloc];
}
@end
创建一个Person
类:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject {
Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end
@implementation Person
- (void)setDog:(Dog *)dog {
_dog = dog; // 将dog的内存地址赋值给成员变量_dog
}
- (Dog *)dog {
return _dog;
}
- (void)dealloc {
NSLog(@"%s", __func__);
[super dealloc];
}
@end
1> 情况一:
运行以下程序:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog = [[Dog alloc] init];
Person *person = [[ Person alloc] init];
[person setDog:dog];
[[person dog] run];
[dog release];
[person release];
}
return 0;
}
// 打印结果
Demo[1234:567890] -[Dog run]
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]
按照顺序释放对象没有问题。
2> 情况二:
接下来运行以下程序:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog = [[Dog alloc] init];
Person *person = [[ Person alloc] init];
[person setDog:dog];
[dog release];
[[person dog] run];
[person release];
}
return 0;
}
// 打印结果
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Dog run]: message sent to deallocated instance 0x1007a73b0
程序崩溃,报错
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
坏地址访问,因为执行[[person dog] run];
语句时dog
对象已经销毁。
所以要保证person
对象没被销毁,dog
对象也不能被销毁。
(3) 代码优化版本一
0> 对Person
类进行优化:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject {
Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end
@implementation Person
- (void)setDog:(Dog *)dog {
_dog = [dog retain]; // dog引用计数+1
}
- (Dog *)dog {
return _dog;
}
- (void)dealloc {
[_dog release]; // 释放_dog
_dog = nil;
NSLog(@"%s", __func__);
// 子类先释放,然后释放父类
[super dealloc];
}
@end
在Person
的setDog:
方法实现时,对dog
进行引用计数+1;
在Person
的dealloc
方法实现时,对_dog
进行释放,引用计数-1。
1> 情况一:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog = [[Dog alloc] init]; // dog引用计数:1
Person *person = [[ Person alloc] init];
[person setDog:dog]; // dog引用计数:2
[dog release]; // dog引用计数:1
[[person dog] run];
[person release]; // dog引用计数:0
}
return 0;
}
// 打印结果
Demo[1234:567890] -[Dog run]
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]
对以上代码进行分析可知:
对Person
类的优化满足:person
对象没被销毁,dog
对象也不能被销毁的需求。
2> 情况二:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog1 = [[Dog alloc] init]; // dog1引用计数:1
Dog *dog2 = [[Dog alloc] init]; // dog2引用计数:1
Person *person = [[ Person alloc] init];
[person setDog:dog1]; // dog1引用计数:2
[person setDog:dog2]; // dog2引用计数:2
[dog1 release]; // dog1引用计数:1
[dog2 release]; // dog2引用计数:1
[person release]; // dog2引用计数:0
}
return 0;
}
// 打印结果
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]
对以上代码进行分析可知:
对Person
类的优化不满足:person
对象持有Dog
类创建的多个dog
对象情况下,person
对象没被销毁,dog
对象也不能被销毁的需求。
(4) 代码优化版本二
0> 对Person
类进行优化:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject {
Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end
@implementation Person
- (void)setDog:(Dog *)dog {
[_dog release]; // 先释放以前的_dog
_dog = [dog retain]; // dog引用计数+1
}
- (Dog *)dog {
return _dog;
}
- (void)dealloc {
[_dog release]; // 释放_dog
_dog = nil;
NSLog(@"%s", __func__);
// 子类先释放,然后释放父类
[super dealloc];
}
@end
在Person
的setDog:
方法实现时,先释放以前的_dog
,然后对dog
进行引用计数+1;
在Person
的dealloc
方法实现时,对_dog
进行释放,引用计数-1。
1> 情况一:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog1 = [[Dog alloc] init]; // dog1引用计数:1
Dog *dog2 = [[Dog alloc] init]; // dog2引用计数:1
Person *person = [[ Person alloc] init];
[person setDog:dog1]; // dog1引用计数:2
[person setDog:dog2]; // dog2引用计数:2,dog1引用计数:1
[dog1 release]; // dog1引用计数:0
[dog2 release]; // dog2引用计数:1
[person release]; // dog2引用计数:0
}
return 0;
}
// 打印结果
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]
对以上代码进行分析可知:
对Person
类的优化满足:person
对象持有Dog
类创建的多个dog
对象情况下,person
对象没被销毁,dog
对象也不能被销毁的需求。
2> 情况二:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog = [[Dog alloc] init]; // dog引用计数:1
Person *person = [[ Person alloc] init];
[person setDog:dog]; // dog引用计数:2
[dog release]; // dog引用计数:1
[person setDog:dog]; // dog引用计数:0
[person setDog:dog];
[person release];
}
return 0;
}
程序崩溃,报错:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
以上代码进行分析可知:
对Person
类的优化不满足:person
对象持有同一个dog
对象情况下,person
对象没被销毁,dog
对象也不能被销毁的需求。
(5) 代码优化版本三
0> 对Person
类进行优化:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject {
Dog *_dog;
}
- (void)setDog:(Dog *)dog;
- (Dog *)dog;
@end
@implementation Person
- (void)setDog:(Dog *)dog {
if (_dog != dog) { // dog与_dog不相等,进行操作
[_dog release]; // 先释放以前的_dog
_dog = [dog retain]; // dog引用计数+1
}
}
- (Dog *)dog {
return _dog;
}
- (void)dealloc {
// [_dog release]; // 释放_dog
// _dog = nil;
self.dog = nil;
NSLog(@"%s", __func__);
// 子类先释放,然后释放父类
[super dealloc];
}
@end
在Person
的setDog:
方法实现时,判断dog
与_dog
不相等时,进行以下操作
:先释放以前的_dog
,然后对dog
进行引用计数+1;
在Person
的dealloc
方法实现时,对_dog
进行释放,引用计数-1。
1> 情况一:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog = [[Dog alloc] init]; // dog引用计数:1
Person *person = [[ Person alloc] init];
[person setDog:dog]; // dog引用计数:2
[dog release]; // dog引用计数:1
[person setDog:dog];
[person setDog:dog];
[person release]; // dog引用计数:0
}
return 0;
}
// 打印结果
Demo[1234:567890] -[Dog dealloc]
Demo[1234:567890] -[Person dealloc]
以上代码进行分析可知:
对Person
类的优化满足:person
对象持有同一个dog
对象情况下,person
对象没被销毁,dog
对象也不能被销毁的需求。
3. MRC下关键字使用
(1) 关键字@synthesize
和@dynamic
- MRC下使用
@property
只会生成setter
、getter
方法的声明;- 如果想生成
_age
成员变量和setter
、getter
方法的实现还要使用@synthesize
关键字。
以下是MRC环境下两个关键字的使用方法:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
// 自动生成属性的setter、getter声明
@property(nonatomic, assign) int age;
@property(nonatomic, retain) Dog *dog;
@end
@implementation Person
// 自动生成成员变量和属性的setter、getter实现
@synthesize age = _age;
@synthesize dog = _dog;
// 基础数据类型(非OC对象)则分配在栈内存中,超过作用域就会由系统检测回收。
- (void)setAge:(int)age {
_age = age;
}
- (int)age {
return _age;
}
// OC对象分配在堆内存,堆内存需要程序员自己去动态分配和回收。
- (void)setDog:(Dog *)dog {
if (_dog != dog) {
[_dog release];
_dog = [dog retain];
}
}
- (Dog *)dog {
return _dog;
}
// OC对象需要手动释放
- (void)dealloc {
[_dog release];
_dog = nil;
[super dealloc];
}
@end
- 使用
assign
修饰生成的setter
方法没有内存管理相关的东西,所以assign
一般用来修饰基本数据类型;- 使用
retain
修饰生成的setter
方法有内存管理相关的东西,所以retain
一般用来修饰对象类型。
(2) 简略写法
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
@property(nonatomic, assign) int age;
@property(nonatomic, retain) Dog *dog;
// 工厂方法
+ (instancetype)person;
@end
@implementation Person
+ (instancetype)person {
return [[[self alloc] init] autorelease];
}
- (void)dealloc {
self.dog = nil;
[super dealloc];
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person person];
}
return 0;
}
MRC环境下,一般会给类添加一个工厂方法,可以自动释放对象,操作简单。
4. MRC下代码写法
@interface ViewController ()
@property(nonatomic, retain) NSMutableArray *array;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 写法一
NSMutableArray *array = [[NSMutableArray alloc] init];
self.array = array;
[array release];
// 写法二
self.array = [[NSMutableArray alloc] init];
[self.array release];
// 写法三
self.array = [[[NSMutableArray alloc] init] autorelease];
// 写法四
// Foundation框架,一般使用类方法创建对象,其内部调用autorelease
self.array = [NSMutableArray array];
}
- (void)dealloc {
self.array = nil;
[super dealloc];
}
@end
网友评论