Objective-C提供的内存管理方式:
一、Objective-C提供了三种内存管理方式:
1. Manual Reference Counting(MRC,手动管理计数,在开发ios4.1之前版本的项目时,我们要自己负责使用引用计数器来管理内存,比如要手动retain、release、autorelease等,而在其后的版本可以使用ARC,让系统自己管理内存)
2. Automatic Reference Counting(ARC,自动引用计数,ios4.1版本之后推出的)
3. Garbage Collection(垃圾回收,ios不支持垃圾回收)
单个对象的内存管理
一、为什么要内存管理
1. 移动设备的内存有限,为了可以使移动设备上的软件运行流畅
2. 如果对象不再使用了,就应该回收它的内存空间,防止内存泄露
二、内存管理的范围
任何继承了NSObject的对象,对其他数据类型(int、char、double、float、struct、enum等)无效
三、引用计数器
(1)对象的基本结构
1. 每个OC对象都有自己的引用计数器,是个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象
2. 每个OC对象内部专门有8个字节的存储空间来存储引用计数器
(2)引用计数器的作用
1. 当使用alloc、new或copy创建一个新对象时,新对象的引用计数器默认就是1
2. 当一个对象的引用计数器值为0时,对象占用的内存空间就会被系统回收,换句话说,如果对象的引用计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出
四、引用计数器的操作
1. 给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)
2. 给对象发送一条release消息,可以使引用计数器值-1(没有返回值)
3. 给对象发送一条retainCount消息,可以获得当前引用计数器值
五、对象的销毁
1. 当一个对象的引用计数器的值为0时,那么它将被销毁,其占用的内存空间被系统回收
2. 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
3. 一般重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
4. 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
5. 不要直接调用dealloc方法
6. 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
六、僵尸对象、野指针和空指针
1. 僵尸对象:所占用内存空间已经被回收的对象,僵尸对象不能再使用
2. 野指针:(1)指向僵尸对象的指针,给野指针发送消息会报错(EXC_BAD_ACCESS),(2)定义的指针没有经行初始化
3. 空指针:没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错
七、如何将项目从ARC模式改为MRC
八、单个对象内存管理的代码演示
(1)Person类的设计
Person.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age; // 年龄
@end
Person.m文件
#import "Person.h"
@implementation Person
// 重写dealloc方法
- (void)dealloc
{
NSLog(@"Person被销毁了");
// 一定要调用[super dealloc],并且要放到最后调用
[super dealloc];
}
@end
-设计注意:
1. 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
(2)main.m文件
设计注意:
1. 当使用alloc、new或copy创建一个新对象时,新对象的引用计数器默认就是1
2. 给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)
3. 给对象发送一条release消息,可以使引用计数器值-1(没有返回值)
4. 给对象发送一条retainCount消息,可以获得当前引用计数器值
5. 当一个对象的引用计数器的值为0时,那么它将被销毁,其占用的内存空间被系统回收
6. 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
7. 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
多个对象的内存管理
一、多对象内存管理原则分析
(1) 原则分析
1. 只要还有人在用某个对象,那么这个对象就不会被回收
2. 只要你想用这个对象,那么就让对象的计数器+1
3. 当你不再使用这个对象时,就让对象的计数器-1
(2)谁创建,谁release
1. 如果你通过alloc、new、copy或mutablecopy来创建一个对象,那么你必须调用release或autorelease
2. 换句话说,不是你创建的,就不用你去release或autorelease
(3)谁retain谁release
1. 只要你调用了retain,无论这个对象是如何生成的,你都要调用release
(4)总结:
1. 有始有终,有加就有减
2. 曾经让对象的计数器+1,就必须在最后让对象的计数器-1
二、set方法内存管理代码规范
(1)基本数据类型:直接赋值
例:
- (void)setAge:(int)age
{
_age = age;
}
(2)OC对象类型
例:
- (void)setBook:(Book *)book
{
// 先判断传进来的是否是新对象
if (book != _book)
{
// 对旧对象做一次release操作
[_book release];
// 对新对象做一次retain操作
_book = [book retain];
}
}
三、dealloc方法的代码规范(不要直接调用dealloc方法)
1. 一定要调用[super dealloc],而且要放到最后调用
2. 对当前对象所拥有的其他对象做一次release操作
例:
- (void)dealloc
{
[_book release];
[super dealloc];
}
四、set方法内存管理代码实现(多对象内存管理演示)
(1)Person类的设计
Person.h文件
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Person : NSObject
{
Book *_book; // 书
int _age; // 年龄
}
// book的setter和getter声明
- (void)setBook:(Book *)book;
- (Book *)book;
// age的setter和getter声明
- (void)setAge:(int)age;
- (int)age;
@end
Person.m文件
#import "Person.h"
@implementation Person
// book的setter和getter实现
- (void)setBook:(Book *)book
{
// 先判断传进来的是否是新对象
if (book != _book)
{
// 对旧对象做一次release操作
[_book release];
// 对新对象做一次retain操作
_book = [book retain];
}
}
- (Book *)book
{
return _book;
}
// age的setter和getter声明
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
// 重写dealloc方法
- (void)dealloc
{
[_book release];
NSLog(@"%d岁的人被销毁了", _age);
[super dealloc];
}
@end
(2)Book类的设计
Book.h文件
#import <Foundation/Foundation.h>
@interface Book : NSObject
@end
Book.m文件
#import "Book.h"
@implementation Book
// 重写dealloc方法
- (void)dealloc
{
NSLog(@"书被销毁了");
[super dealloc];
}
@end
-设计注意:
1. 基本数据类型:直接赋值
例:
- (void)setAge:(int)age
{
_age = age;
}
- (2)OC对象类型
例:
- (void)setBook:(Book *)book
{
// 先判断传进来的是否是新对象
if (book != _book)
{
// 对旧对象做一次release操作
[_book release];
// 对新对象做一次retain操作
_book = [book retain];
}
}
- 一般重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
例:
- (void)dealloc
{
[_book release];
NSLog(@"%d岁的人被销毁了", _age);
[super dealloc];
}
- 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
(3)main.m文件
五、set方法内存管理(@property内存管理)
(1)@property Book *book;会自动生成setter的实现(没有管理内存)为:
- (void)setBook:(Book *)book
{
_book = book;
}
(2)@property(retain)Book *book;会自动生成setter的实现(已经管理了内存)为:
- (void)setBook:(Book *)book
{
if (book != _book)
{
[_book release];
_book = [book retain];
}
}
六、set方法内存管理(@property的参数)
(1)set方法内存管理相关的参数
1. retain:release旧值,retain新值(适用于OC对象类型)
2. assign:直接赋值(默认,适用于非OC对象类型)
3. copy:release旧值,copy新值
(2)是否要生成set方法
1. readwrite:同时生成setter和getter的声明和实现(默认)
2. readonly:只生成getter的声明和实现
(3)多线程管理
1. nonatomic:性能高(一般就用这个,对属性不加锁,多线程下线程不安全)
2. atomic:性能低(默认,对属性加锁,多线程下线程安全)
(4)setter和getter方法的名称
1. setter:决定了set方法的名称,一定要有个冒号
2. getter:决定了get方法的名称(一般用在成员变量为BOOL类型)
七、模型设计(综合)练习
要求设计一个微博用户类,包含以下属性:姓名、微博号码、密码、头像、性别、手机和生日
设计一个类表示一条微博,包含以下属性:微博内容、微博配图、发送时间、微博发送人、转发的微博、被评论数和被转发数
(1)User(用户)类的设计
User.h文件
#import <Foundation/Foundation.h>
typedef enum
{
kSexMan, // 男
KsexWomen // 女
} Sex;
typedef struct
{
int year;
int month;
int day;
} Date;
@interface User : NSObject
@property(nonatomic, retain)NSString *name; // 姓名
@property(nonatomic, retain)NSString *account; // 微博账号
@property(nonatomic, retain)NSString *password; // 微博密码
@property(nonatomic, retain)NSString *icon; // 微博头像
@property(nonatomic, assign)Sex sex;
@property(nonatomic, retain)NSString *phoneNum; // 手机号码
@property(nonatomic, assign)Date brithday; // 生日
@end
User.m文件
#import "User.h"
@implementation User
- (void)dealloc
{
[_name release];
[_account release];
[_password release];
[_icon release];
[_phoneNum release];
NSLog(@"%@被释放了", _name);
[super dealloc];
}
@end
(2)Status(微博)类的设计
Status.h文件
#import <Foundation/Foundation.h>
#import "User.h"
@interface Status : NSObject
@property(nonatomic, retain)NSString *contents; // 微博内容
@property(nonatomic, retain)NSString *icon; // 微博配图
@property(nonatomic, assign)time_t time; // 发送时间
@property(nonatomic, retain)User *user; // 微博发送人
@property(nonatomic, retain)Status *retweetStatus; // 转发的微博
@property(nonatomic, assign)int commentCounts; // 被评论数
@property(nonatomic, assign)int retweetCounts; // 被转发数
@end
Status.m文件
#import "Status.h"
@implementation Status
- (void)dealloc
{
[_contents release];
[_icon release];
[_user release];
[_retweetStatus release];
NSLog(@"微博%@被销毁了", _contents);
[super dealloc];
}
@end
(3)main.m文件
循环retain和循环引用的问题
一、循环retain和@class
(1)@class的作用:仅仅告诉编译器,某个名称是个类
例: @class Person; 仅仅告诉编译器Person是一个类
(2)在开发中引用一个类的规范
1. 在.h文件中@class来声明类
2. 在.m文件中用#import来包含类的所有东西
(3)循环retain
1. 比如A对象retain了B对象,B对象retain了A对象
2. 这样会导致A对象和B对象永远无法释放
(4)两端循环引用的解决方案
1. 一端使用retain
2. 另一端使用assign
(5)#import和@class的区别
1. #import方式会包含被引用类的所用信息,包括被引用类的变量和方法,@class方式只是告诉编译器在A.h文件中,B *b只是类的声明,具体这个类有什么信息,这里不需要知道,等实现文件中真正要用到的时候,才会真正去查看B类中的信息
2. 如果有上百个头文件都#import同一个文件,或者这些文件依次被#import,那么一旦最开是的头文件稍微有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,相对来讲,使用@class方式就不会出现这种问题了
3. 在.m实现文件中,如果需要引用到被引用类的实体变量或方法时,还需要使用#import方式引入被引用类
(6)@class的好处
1. 解决了循环引用问题(例:A文件中 #import “B.h”;B文件中 #import “A.h”)
2. 可以提高性能
二、解决循环retain和循环引用的代码实现
设计要求:每个人都有一张身份证,每张身份证都代表一个人
(1)Person类的设计
Person.h文件
#import <Foundation/Foundation.h>
@class Card;
@interface Person : NSObject
@property(nonatomic, retain)Card *card; // 身份证
@end
Person.m文件
#import "Person.h"
#import "Card.h"
@implementation Person
- (void)dealloc
{
[_card release];
NSLog(@"Person被销毁了");
[super dealloc];
}
@end
(2)Card类的设计
Card.h文件
#import <Foundation/Foundation.h>
@class Person;
@interface Card : NSObject
@property(nonatomic, assign)Person *person; // 人
@end
Card.m文件
#import "Card.h"
#import "Person.h"
@implementation Card
- (void)dealloc
{
NSLog(@"Card被销毁了");
[super dealloc];
}
@end
(3)main.m文件
-设计注意:
1. 一端使用retain,另一端使用assign来解决循环retain问题
2. 使用@class来解决循环包含问题
autorelease
一、autorelease的基本使用
1. 会将对象放到一个自动释放池中
2. 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
3. autorelease方法会返回对象本身
4. 调用完autorelease方法后,对象的计数器不变
二、autorelease的好处
1. 不用再关心对象释放的时间
2. 不用再关心什么时候调用release
三、autorelease使用注意
1. 占用内存较大的对象不要随便使用autorelease(因为autorelease不能精准的控制对象什么时候release,只有在自动释放池销毁的时候,里面的对象才会做一次release操作)对于这种延迟释放机制还是尽量少用
2. 占用内存较小的对象使用autorelease,没有太大的影响
3. 不要把大量循环操作放到同一个@autoreleasepool之间,这样造成内存峰值上升
四、自动释放池
(1)自动释放池的介绍
1. 在ios程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在的(栈结构的特点:先进后出)
2. 当一个对象调用autorelease方法时,会将这个对象放到栈顶的自动释放池中
(2)自动释放池的创建
1. ios5.0前
// 自动释放池的创建
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 自动释放池的销毁方式一:
[pool drain];
// 自动释放池的销毁方式一:
[pool release];
- ios5.0后
五、autorelease的使用规律
1. 系统中自带的方法里面没有包含alloc、new、copy的,说明返回的对象都是经过autorelease的
2. 开发中经常会提供一些类方法,用来快速创建一个已经autorelease过的对象
六、autorelease的使用细节
1. 开发中经常会提供一些类方法,用来快速创建一个已经autorelease过的对象
2. 创建对象时不要直接用类名,一般用self
七、autorelease的应用代码演示
要求:设计一个Person类,设计一个类方法,可以快速创建一个autorelease过的对象,创建的人有不同的年龄;设计一个学生类,调用父类方法,可以快速创建一个autorelease过的对象
(1)Person类的设计
Person.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic, assign)int age;
+ (instancetype)person;
+ (instancetype)personWithAge:(int)age;
@end
Person.m文件
#import "Person.h"
@implementation Person
+ (instancetype)person
{
Person *p = [[[self alloc] init] autorelease];
return p;
}
+ (instancetype)personWithAge:(int)age
{
Person *p = [self person];
p.age = age;
return p;
}
- (void)dealloc
{
NSLog(@"%d岁的人被销毁了", _age);
[super dealloc];
}
@end
(2)Student类的设计
Student.h文件
#import "Person.h"
@interface Student : Person
@property(nonatomic, assign)int no;
@end
Student.m文件
#import "Student.h"
@implementation Student
@end
(3)main.m文件