OC语法之-内存管理
基本原理
- 管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效
- 对象的基本结构
- 每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象
- 每个OC对象内部专门有4个字节的存储空间来存储引用计数器
- 引用计数器的作用:
- 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1
- 当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出
- 操作引用计数器
- 给对象发送一条
retain
消息,可以使引用计数器值+1(retain
方法返回对象本身) - 给对象发送一条
release
消息,可以使引用计数器值-1(没有返回值) - 可以给对象发送
retainCount
消息获得当前的引用计数器值
- 给对象发送一条
- 对象的销毁
- 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
- 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
- 一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
- 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
- 不要直接调用dealloc方法
- 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
- 概念
- 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用
- 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)
- 空指针 :没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错
代码举例:
////Person.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@end
//Person.m文件
#import "Person.h"
@implementation Person
// 当一个Person对象被回收的时候,就会自动调用这个方法
- (void)dealloc
{
NSLog(@"Person对象被回收");
// super的dealloc一定要调用,而且放在最后面
[super dealloc];
}
@end
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
// 1
Person *p = [[Person alloc] init];
NSUInteger c = [p retainCount];
NSLog(@"计数器:%ld", c);
// 2 retain方法返回的是对象本身
[p retain];
// 1
[p release];
// 0 野指针:指向僵尸对象(不可用内存)的指针
[p release];
// 给野指针发送消息,会报错
// 野指针不能复活!!!
[p retain];
// -[Person setAge:]: message sent to deallocated instance 0x100109a10
// 给已经释放的对象发送了一条-setAge:消息:
p.age = 10;
//安全操作:指针p变成空指针,给空指针发送消息不报错
//p = nil;
// EXC_BAD_ACCESS : 访问了一块坏的内存(已经被回收、已经不可用的内存
// 野指针错误
// OC不存在空指针错误,给空指针发送消息,不报错
[p release];
[p release];
[p release];
[p release];
//不会报错
[nil release];
return 0;
}
内存管理原则
当一个对象进行管理的时候比较简单,但是多个对象联系起来的时候,就比较麻烦了.那么他们只要遵守以下原则,就仍然很好管理.
- 只要还有人在用某个对象,那么这个对象就不会被回收
- 你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)
- 你不想再使用(占用)(当前对象死掉或者换掉占有)某个对象,就应该让对象的计数器-1(让对象做一次release)
- 谁创建,谁release
- 如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
- 换句话说,不是你创建的,就不用你去[auto]release
- 谁retain,谁release
- 只要你调用了retain,无论这个对象是如何生成的,你都要调用release
- 总结
- 有始有终,有加就有减
- 曾经让对象的计数器+1,就必须在最后让对象计数器-1
代码举例:
//Book.h
#import <Foundation/Foundation.h>
@interface Book : NSObject
{
int _price;
}
- (void)setPrice:(int)price;
- (int)price;
@end
//Book.m
#import "Book.h"
@implementation Book
- (void)setPrice:(int)price
{
_price = price;
}
- (int)price
{
return _price;
}
- (void)dealloc
{
NSLog(@"Book对象被回收");
[super dealloc];
}
@end
//Person.h
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Person : NSObject
{
Book *_book;
}
- (void)setBook:(Book *)book;
- (Book *)book;
@end
//Person.m
#import "Person.h"
@implementation Person
- (void)setBook:(Book *)book
{ //拥有做一次retain
_book = [book retain];
}
- (Book *)book
{
return _book;
}
- (void)dealloc
{
//死前做一次release
[_book release];
NSLog(@"Person对象被回收");
[super dealloc];
}
@end
//main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"
int main()
{
// b-1
Book *b = [[Book alloc] init];
// p-1
Person *p1 = [[Person alloc] init];
//p1想占用b这本书那就使b做retain一次(封装在set方法中),p1死之前要把b做relase(封装在delloc中)一次
// b-2
[p1 setBook:b];
// p-0
// b-1
[p1 release];
p1 = nil;
// b-0
[b release];
b = nil;
return 0;
}
set方法的内存管理
从上面的例子我们可以看到:
- 当A对象(Person)想拥有B(Book)对象时,在A对象的set方法中对B对象进行一次retain操作;
- 当A对象不想拥有B对象时,在A对象的delloc中对B对象做一次Release操作
- 疑问:上面做的并不严谨,因为A对象不再拥有B对象,有两种情况:
- A对象计数器为0,被销毁时
- A对象替换掉旧的B对象,从新拥有一个新的B对象
- 那么是第2种情况该怎么办呢?很显然要对旧的B也要进行一次release,那么在哪处理呢?
- set方法的代码规范(A可能会替换掉B)
-
基本数据类型:直接复制
- (void)setAge:(int)age { _age = age; }
-
OC对象类型
- (void)setCar:(Car *)car { // 1.先判断是不是新传进来对象 if ( car != _car ) { // 2.对旧对象做一次release [_car release]; // 3.对新对象做一次retain _car = [car retain]; } }
-
- dealloc方法的代码规范(A死去不再拥有B)
- 一定要[super dealloc],而且放到最后面
- 对self(当前)所拥有的其他对象做一次release
- (void)dealloc { [_car release]; [super dealloc]; }
将上面的例子中Person中的- (void)setBook:(Book *)book
方法修改如下:
- (void)setBook:(Book *)book
{
// 1.先判断是不是新传进来对象
if (book != _book) {
// 2.对旧对象做一次release
[_book release];
// 3.对新对象做一次retain
_book = [book retain];
}
}
main.m的代码如下:
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"
int main()
{
// b-1
Book *b = [[Book alloc] init];
//b2-1
Book *b2 = [[Book alloc] init];
// p-1
Person *p1 = [[Person alloc] init];
// b-2
[p1 setBook:b];
//P1 替换新的book b-1 b2-2
[p1 setBook:b2];
// p-0
// b-1
[p1 release];
p1 = nil;
// b-0
[b release];
b = nil;
//b2-0
[b2 release];
b2 = nil;
return 0;
}
@property参数
-
之前我们学过的
@property Book *book;
生成的setter方法,是这样的:- (void)setBook:(Book *)book { _book = book ; }
-
很显然不能满足我们内存管理的要求,那么就要用到property参数了
分为三类:
- set方法内存管理相关的参数
- retain : release旧值,retain新值(适用于OC对象类型)
- assign : 直接赋值(默认,适用于非OC对象类型)
- copy : release旧值,copy新值
- 是否要生成set方法
- readwrite : 同时生成setter和getter的声明、实现(默认)
- readonly : 只会生成getter的声明、实现
- 多线程管理
- nonatomic : 性能高 (一般就用这个)
- atomic : 性能低(默认)
- setter和getter方法的名称
- setter : 决定了set方法的名称,一定要有个冒号 :
- getter : 决定了get方法的名称(一般用在BOOL类型)
代码举例:
//Person.h文件
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Person : NSObject
//非oc对象
@property (nonatomic,assign) int age;
// retain : 生成的set方法里面,release旧值,retain新值
@property (retain,nonatomic) Book *book;
//字符串用copy
@property (copy,nonatomic) NSString *name;
// 返回BOOL类型的方法名一般以is开头
@property (getter = isRich) BOOL rich;
//只生成get方法
@property (nonatomic,assign,readonly) int height;
@end
//Person.m文件
#import "Person.h"
@implementation Person
- (void)dealloc
{
[_book release];
[_name release];
[super dealloc];
}
@end
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"
int main()
{
Book *b = [[Book alloc] init];
Person *p = [[Person alloc] init];
p.book = b;
//修改getter方法名
BOOL bx = p.isRich;
int height = p.height;
NSLog(@"%ld", [b retainCount]);
[p release];
[b release];
return 0;
}
循环retain和@class
- @class
- 使用场景
-
对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类
#import"B.h" @interface A : NSObject{ B *_b; } @end #import"A.h" @interface B : NSObject{ A *_a; } @end
-
两方分别导入对方的.h文件,这种代码编译会报错。当使用@class在两个类相互声明,就不会出现编译报错
-
- 用法概括
- 使用
@class 类名;
就可以引用一个类,说明一下它是一个类 - 两方至少有一方使用@class ,或者两方都使用@class
- 在相应的.m文件中一定要使用#import导入,因为@class仅仅是说明一个类,并没有把这个类的内容导入
- 使用
- 开发中引用一个类的规范!!!
- 在.h文件中用@class来声明类
- 在.m文件中用#import来包含类的所有东西
- 使用场景
- @class 和#import的区别
@class Card;
@class仅仅是告诉编译器,Card是一个类#import
方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息- 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方式就不会出现这种问题了
- 在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类
- 循环retain
- 比如A对象retain了B对象,B对象retain了A对象
- 这样会导致A对象和B对象永远无法释放
- 解决方案
- 当两端互相引用时,应该一端用retain、一端用assign
代码举例:
//Person.h
#import <Foundation/Foundation.h>
#import "Card.h"
// @class仅仅是告诉编译器,Card是一个类
//@class Card;
@interface Person : NSObject
@property (nonatomic, retain) Card *card;
@end
//Person.m
#import "Person.h"
#import "Card.h"
@implementation Person
- (void)dealloc
{
NSLog(@"Person被销毁了");
[_card release];
[super dealloc];
}
@end
////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(@"Car被销毁了");
//assign就不需要release了
// [_person release];
[super dealloc];
}
@end
//调用:
#import <Foundation/Foundation.h>
#import "Card.h"
#import "Person.h"
int main()
{
// p - 1
Person *p = [[Person alloc] init];
// c - 1
Card *c = [[Card alloc] init];
// c - 2
p.card = c;
// p - 1 因为person是assign,所以不会+1
c.person = p;
// c - 1
[c release];
// p - 0 c - 0
[p release];
return 0;
}
autorelease
- autorelease的基本用法
- 会将对象放到一个自动释放池中
- 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
- 会返回对象本身
- 调用完autorelease方法后,对象的计数器不变
- autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用Release
- autorelease的好处
- 不用再关心对象释放的时间
- 不用再关心什么时候调用release
- autorelease的使用注意
- 占用内存较大的对象不要随便使用autorelease
- 占用内存较小的对象使用autorelease,没有太大影响
- 错误写法
-
alloc之后调用了autorelease,又调用release
@autoreleasepool { // 1 Person *p = [[[Person alloc] init] autorelease]; // 0 [p release]; }
-
连续调用多次autorelease
@autoreleasepool { Person *p = [[[[Person alloc] init] autorelease] autorelease]; }
-
- 自动释放池
- 在iOS程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
- OC对象只需要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)
- 自动释放池的创建方式
-
iOS 5.0前
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //... [pool release]; // [pool drain];
-
iOS 5.0 开始
@autoreleasepool { }
代码举例:
#import <Foundation/Foundation.h> @interface Person : NSObject @property (nonatomic, assign) int age; @end #import "Person.h" @implementation Person - (void)dealloc { NSLog(@"Person---dealloc"); [super dealloc]; } @end //调用: #import <Foundation/Foundation.h> #import "Person.h" int main() { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Person *pp = [[[Person alloc] init] autorelease]; [pool release]; // [pool drain]; @autoreleasepool { // 1 Person *p = [[[[Person alloc] init] autorelease] autorelease]; // 0 // [p release]; } return 0; } void test() { @autoreleasepool {// { 开始代表创建了释放池 // autorelease方法会返回对象本身 // 调用完autorelease方法后,对象的计数器不变 // autorelease会将对象放到一个自动释放池中 // 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作 Person *p = [[[Person alloc] init] autorelease]; p.age = 10; @autoreleasepool { // 1 Person *p2 = [[[Person alloc] init] autorelease]; p2.age = 10; } Person *p3 = [[[Person alloc] init] autorelease]; } // } 结束代表销毁释放池 }
-
- autorelease的应用
- 系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
- 开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象
-
创建对象时不要直接用类名,一般用self
+ (id)person { return [[[self alloc] init] autorelease]; }
-
代码举例:
//父类Person
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, assign) int age;
+ (id)person;
+ (id)personWithAge:(int)age;
@end
#import "Person.h"
@implementation Person
+ (id)person
{
//这里要用self比较好
return [[[self alloc] init] autorelease];
}
+ (id)personWithAge:(int)age
{
Person *p = [self person];
p.age = age;
return p;
}
- (void)dealloc
{
NSLog(@"%d岁的人被销毁了", _age);
[super dealloc];
}
@end
//子类
#import "Person.h"
@interface GoodPerson : Person
@property (nonatomic, assign) int money;
@end
#import "GoodPerson.h"
@implementation GoodPerson
@end
//调用
#import <Foundation/Foundation.h>
#import "Person.h"
#import "GoodPerson.h"
int main()
{
@autoreleasepool {
Person *p = [Person personWithAge:100];
//内部用的是self因此释放的是GoodPerson类
GoodPerson *p2 = [GoodPerson personWithAge:10];
p2.money = 100;
}
return 0;
}
void test()
{
//不用autorelease
Person *p = [[Person alloc] init];
p.age = 200;
[p release];
// 包
// Student com.mj.test
// Student com.mj.test2
// MJStudent
// SBStudent
// Next Step
@autoreleasepool
{
// 1. 封装
//直接用autorelease
// Person *p2 = [[[Person alloc] init] autorelease];
// p2.age = 100;
//将autuolease封装起来
// Person *p2 = [Person person];
// p2.age = 100;
Person *p2 = [Person personWithAge:100];
//2. 系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
//例1:
NSString *str = @"123123";
NSString *str2 = [NSString stringWithFormat:@"age is %d", 10];
//例2:
NSNumber *num = [[NSNumber alloc] initWithInt:10];
[num release];
NSNumber *num2 = [NSNumber numberWithInt:100];
}
}
ARC
- ARC是自iOS 5之后增加的新特性,完全消除了手动管理内存的烦琐,编译器会自动在适当的地方插入适当的retain、release、autorelease语句。你不再需要担心内存管理,因为编译器为你处理了一切
- ARC 是编译器特性,而不是 iOS 运行时特性,它也不是类似于其它语言中的垃圾收集器。因此 ARC 和手动内存管理性能是一样的,有时还能更加快速,因为编译器还可以执行某些优化
- ARC的判断准则:只要没有强指针指向对象,就会释放对象
- ARC特点
- 不允许调用release、retain、retainCount
- 允许重写dealloc,但是不允许调用[super dealloc]
- @property的参数
- strong :成员变量是强指针(适用于OC对象类型)
- weak :成员变量是弱指针(适用于OC对象类型)
- assign : 适用于非OC对象类型
- 以前的retain改为用strong
- 指针分2种:
- 强指针:默认情况下,所有的指针都是强指针 __strong
- 弱指针:__weak
- 弱指针指向的对象被回收后,弱指针会自动变为nil指针,不会引发野指针错误
- 当两端循环引用的时候,解决方案:
- ARC
- 1端用strong,另1端用weak
- 非ARC
- 1端用retain,另1端用assign
// 错误写法(没有意义的写法)(这一行过后,对象就会被销毁) __weak Person *p = [[Person alloc] init]; //打印nil,p自动为nil,不会出现野指针错误 NSLog(@"%@", p); NSLog(@"------------");
- ARC
- MRA与ARC之间的转换
- 如何查看一个项目是非ARC还是MRC?
- 点击项目名->build setting->搜索auto ->Aplle LVVM 9.0 - Language - Objective - C -> Objective - C Automatic Reference Counting - 是否为YES
- 在ARC项目中,如何让某一个.m文件支持MRC?
- Build Phases -> Compile Sources ->找到相应文件 -双击输入
-fno-objc-arc
即可
- Build Phases -> Compile Sources ->找到相应文件 -双击输入
- 在MRC项目中,如何让某一个.m文件支持ARC?
- Build Phases -> Compile Sources ->找到相应文件 -双击输入
-f-objc-arc
即可
- Build Phases -> Compile Sources ->找到相应文件 -双击输入
- 如何让一个MRC项目转化为ARC项目?
- Xcode ->Edit->Convert-> To Object-C ->ARC
- 如何查看一个项目是非ARC还是MRC?
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
//一段用weak
@property (nonatomic, weak) Person *person;
@end
#import "Dog.h"
@implementation Dog
- (void)dealloc
{
NSLog(@"Dog--dealloc");
}
@end
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
//一端用strong
@property (nonatomic, strong) Dog *dog;
@end
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"Person--dealloc");
}
@end
//调用
int main()
{
Person *p = [[Person alloc] init];
Dog *d = [[Dog alloc] init];
p.dog = d;
d.person = p;
return 0;
}