内存管理
1.内存管理的基本原理
移动设备的内存极其有限,在有限的内存中发挥程序的最高使用效率,这需要对内存使用达到极致。一般OC的内存管理是靠引用计数器来判断对象存在的标志。通过引用计数器的加减来回收或返回对象。因此,对引用计数器的操作就是OC内存管理的核心。
2.引用计数器的作用
OC的每个对象中都有一个引用计数器,引用计数器就是一个整型数。这个整型数在OC对象中用4个字节空间来存储。当对象执行alloc,new或是copy等操作时,引用计数器就加1,当对象执行release操作时对象计数器就减1。一般用retainCount来返回计数器的值。
Car.h
#import <Foundation/Foundation.h>
@interface Car : NSObject
@end
Car.m
#import "Car.h"
@implementation Car
@end
main.m
#import <Foundation/Foundation.h>
#import "Car.h"
int main(int argc, const char * argv[])
{
Car *c = [[Car alloc] init];
NSLog(@"Car is %ld",[c retainCount]);
[c retain];
NSLog(@"Car is %ld",[c retainCount]);
[c release];
NSLog(@"Car is %ld",[c retainCount]);
[c release];
return 0;
}
运行结果:
2014-06-07 11:08:08.238 dui[784:303] Car is 1
2014-06-07 11:08:08.280 dui[784:303] Car is 2
2014-06-07 11:08:08.281 dui[784:303] Car is 1
Program ended with exit code: 0
在对Car类型对象进行操作的时候,retain和alloc都是对引用计数器加1的操作。当执行release之后引用计数器减1。如果对Car类型对象多次执行release那么就会出现野指针错误。因为release到引用计数器为0时,对象就已经被回收销毁,如再对其执行操作,由于那部分内存已不再是原来的对象所有,所以会出现非法引用。
3.内存管理的基本原则
对内存进行管理遵循的基本原则就是谁创建,谁销毁,不是这个对象创建的,就不需要执行销毁操作。这样符合逻辑的原则,便于内存的有效利用。
set方法的内存管理
1.对象销毁方法的实现
对象的引用计数器为0时,对象即被销毁。当对象被销毁时调用dealloc方法。
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
Person.m
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"Person is dealloc");
[super dealloc];
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[])
{
Person *p = [[Person alloc] init];
[p release];
return 0;
}
运行结果:
2014-06-07 11:53:26.154 dealloc[890:303] Person is dealloc
Program ended with exit code: 0
在覆写dealloc方法时一定要在最后调用父类中的dealloc方法,这样就不会报警告。因为类的创建和释放都是由系统来运作的,如果手动直接调用dealloc方法,可能造成系统崩溃,所以调用父类中的dealloc方法来交与系统处理。
2.property的参数类型
property可以自动生成对成员进行操作的set方法和get方法。set方法和get方法随着程序的使用有时需要实现更多的拓展,所以property的参数类型使得方法拓展方便了许多。property的参数主要集中在这四个方面:内存管理,控制方法的生成,多线程管理,控制方法的名称更改。
- 内存管理
assign:默认类型,就是直接赋值,不用返回。
retain:在set方法中释放旧的对象返回新的对象。
copy:和retain一样返回新的对象。
- 控制方法的生成
readwrite:默认参数即生成set方法也生成get方法。
readonly:只生成get方法。
- 多线程管理
nonatomic:不用多线程,提高了性能。
atomic:默认多线程同步,性能低。
- 控制方法的名称更改
setter:更改set方法的名称。
getter:更改get方法的名称。
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int idcode;
@property (nonatomic,assign) int age;
@property (nonatomic,retain) NSString *name;
@property (nonatomic,readonly) int num;
@property (atomic,setter = cset:,getter = cscore) int score;
- (void)setNum:(int)num;
@end
Person.m
#import "Person.h"
@implementation Person
- (void)setNum:(int)num
{
_num = num;
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[])
{
Person *p = [[Person alloc] init];
p.idcode = 10;
NSLog(@"idcode = %d",p.idcode);
p.age = 20;
NSLog(@"age = %d",p.age);
p.name = @"lsmseed";
NSLog(@"name = %@",p.name);
[p setNum:10];
NSLog(@"num = %d",p.num);
[p cset:30];
NSLog(@"score = %d",p.cscore);
return 0;
}
运行结果:
2014-06-07 16:06:13.999 dealloc[1139:303] idcode = 10
2014-06-07 16:06:14.039 dealloc[1139:303] age = 20
2014-06-07 16:06:14.041 dealloc[1139:303] name = lsmseed
2014-06-07 16:06:14.042 dealloc[1139:303] num = 10
2014-06-07 16:06:14.043 dealloc[1139:303] score = 30
Program ended with exit code: 0
property修饰的成员可以是一般的变量,也可以是对象成员。property的参数在修饰对象成员时一般加上retain参数,这样自动生成的方法可以释放以前的对象,赋值成新的对象。
Good.h
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"
@interface Good : NSObject
@property (nonatomic,retain) Book *b;
@property (nonatomic,retain) Person *p;
@end
Good.m
#import "Good.h"
@implementation Good
- (void)dealloc
{
[_p release];
[_b release];
NSLog(@"Person book is dealloc");
[super dealloc];
}
@end
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@end
Person.m
#import "Person.h"
@implementation Person
@end
Book.h
#import <Foundation/Foundation.h>
@interface Book : NSObject
@end
Book.m
#import "Book.h"
@implementation Book
@end
main.m
#import <Foundation/Foundation.h>
#import "Good.h"
int main(int argc, const char * argv[])
{
Good *g = [[Good alloc] init];
g.p = [[Person alloc] init];
g.b = [[Book alloc] init];
[g release];
return 0;
}
运行结果:
2014-06-07 21:52:42.141 set[1313:303] Person book is dealloc
Program ended with exit code: 0
成员对象加上retain修饰之后可以自动生成释放旧对象赋值新对象的方法,但是不会自动生成成员对象自动销毁的方法,所以在Good类的dealloc方法中要对成员对象做release操作,让对象在调用完后自动回收。
@class的循环引用
1. @class的使用
@class就是声明了一个类,这样在不导入对应类的头文件的情况下,就可以使用该类型做声明,但是在实现的时候还是要导入对应类的头文件。
Person.h
#import <Foundation/Foundation.h>
@class Book;
@interface Person : NSObject
@property (nonatomic,retain) Book *b;
@end
Person.m
#import "Person.h"
#import "Book.h"
@implementation Person
- (void)dealloc
{
[_b release];
NSLog(@"Book is dealloc");
[super dealloc];
}
@end
Book.h
#import <Foundation/Foundation.h>
@interface Book : NSObject
@end
Book.m
#import "Book.h"
@implementation Book
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"
int main(int argc, const char * argv[])
{
Person *p = [[Person alloc] init];
p.b = [[Book alloc] init];
[p release];
return 0;
}
运行结果:
2014-06-07 22:15:35.117 pp[1428:303] Book is dealloc
Program ended with exit code: 0
@class作为类的声明引用,可以提高编译效率。在需要实现的时候导入头文件,这样程序的效率也可以得到优化。
2. 循环引用僵死的解决方法
循环引用即两个类互相使用@class引用对方,双方的成员都是对方类型的变量。这样会引发一个问题就是互相自动生成的默认方法都retain一次对方类型的对象,这会使得最终两个成员对象都无法释放销毁。
Person.h
#import <Foundation/Foundation.h>
@class Book;
@interface Person : NSObject
@property (nonatomic,retain) Book *b;
@end
Person.m
#import "Person.h"
#import "Book.h"
@implementation Person
- (void)dealloc
{
[_b release];
NSLog(@"Book is dealloc");
[super dealloc];
}
@end
Book.h
#import <Foundation/Foundation.h>
@class Person;
@interface Book : NSObject
@property (nonatomic,assign) Person *p;
@end
Book.m
#import "Book.h"
#import "Person.h"
@implementation Book
- (void)dealloc
{
NSLog(@"Person is dealloc");
[super dealloc];
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"
int main(int argc, const char * argv[])
{
Person *p = [[Person alloc] init];
Book *b = [[Book alloc] init];
p.b = b;
b.p = p;
[p release];
[b release];
return 0;
}
运行结果:
2014-06-07 22:49:26.081 pp[1582:303] Book is dealloc
2014-06-07 22:49:26.084 pp[1582:303] Person is dealloc
Program ended with exit code: 0
如果出现循环引用锁死不能互相释放对象的情况,在property的参数上一个用retain一个用assign。这样互相引用的对象就能被顺利释放。在dealloc方法中,如果是加retain参数的成员对象就写上release操作,如果是assign参数修饰的就不用写release了。
自动释放池
1.autorelease的作用
当一对象调用autorelease方法时,这个对象会被放入一个自动释放池中。这个池子在被销毁的时候将会对池内所有的对象都做一次release操作。autorelease方法不会马上改变引用计数器的值,可以理解成延迟改变,autorelease方法调用之后返回对象本身。
Car.h
#import <Foundation/Foundation.h>
@interface Car : NSObject
@end
Car.m
#import "Car.h"
@implementation Car
- (void)dealloc
{
NSLog(@"Car is dealloc");
[super dealloc];
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Car.h"
int main(int argc, const char * argv[])
{
@autoreleasepool
{
Car *c = [[[Car alloc] init] autorelease];
NSLog(@"Car count = %ld",[c retainCount]);
}
return 0;
}
运行结果:
2014-06-07 23:15:27.023 ff[1690:303] Car count = 1
2014-06-07 23:15:27.123 ff[1690:303] Car is dealloc
Program ended with exit code: 0
autorelease的使用使得创建出来的对象可以不用关心内存何时被释放,也不用考虑引用间的复杂关系。对于内存占用较多的对象不易使用autorelease,如果内存占用较小,autorelease对内存影响较小。
2.自动释放池的创建
自动释放池即对象调用autorelease方法时都会进入的池子。自动释放池可以创建多个,也可以嵌套创建。
自动释放池创建的一般形式:
@autoreleasepool
{
// ....
}
嵌套形式:
@autoreleasepool
{
@autoreleasepool
{
}
}
3.autorelease的应用
有了autorelease在创建对象的时候就不用再考虑内存管理,有多少内存用多少。一般除了alloc,new,copy等方法其他的方法都内部声明了autorelease,这样我们再使用方法的时候就不用写release操作了。但是,有的时候还是需要自己实现创建对象后的自动释放。
Seed.h
#import <Foundation/Foundation.h>
@interface Seed : NSObject
@property int count;
+ (id)initSeed;
+ (id)initWithSeed:(int)count;
@end
Seed.m
#import "Seed.h"
@implementation Seed
+ (id)initSeed
{
return [[[self alloc] init] autorelease];
}
+ (id)initWithSeed:(int)count
{
Seed *s = [self initSeed];
s.count = 10;
return s;
}
- (void)dealloc
{
NSLog(@"Seed dealloc");
[super dealloc];
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Seed.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
Seed *s = [Seed initSeed];
NSLog(@"count = %d",s.count);
Seed *s1 = [Seed initWithSeed:10];
NSLog(@"count = %d",s1.count);
}
return 0;
}
运行结果:
2014-06-08 11:44:35.854 lsm[484:303] count = 0
2014-06-08 11:44:35.858 lsm[484:303] count = 10
2014-06-08 11:44:35.859 lsm[484:303] Seed dealloc
2014-06-08 11:44:35.860 lsm[484:303] Seed dealloc
Program ended with exit code: 0
ARC新特性
1.arc的基本原理
arc即全称为automatic reference counting。这是xcode提供的新特性,它能很好地帮助程序员管理内存,使得对象地内存不用再使用手动方式来管理。arc的原理就是当一个强引用指向对象还存在时,这个对象就一直在内存中不被释放。
2.强指针和若指针
- 强指针
一般默认的指针都是强指针,强指针指向的对象不能被回收。强指针用__strong或strong关键字来表示。
- 弱指针
弱指针用__weak或weak来表示。弱指针指向的对象被回收,指针自动置为nil。弱指针指向的对象不会出现野指针错误。
Str.h
#import <Foundation/Foundation.h>
@class Word;
@interface Str : NSObject
@property (nonatomic,strong) Word *w;
@end
Str.m
#import "Str.h"
@implementation Str
- (void)dealloc
{
NSLog(@"Str is dealloc");
}
@end
Word.h
#import <Foundation/Foundation.h>
@interface Word : NSObject
@end
Word.m
#import "Word.h"
@implementation Word
- (void)dealloc
{
NSLog(@"Word is dealloc");
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Str.h"
#import "Word.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
Str *s = [Str new];
s.w = [Word new];
}
return 0;
}
运行结果:
2014-06-08 12:12:35.197 strwea[574:303] Str is dealloc
2014-06-08 12:12:35.202 strwea[574:303] Word is dealloc
Program ended with exit code: 0
由于IOS7默认强制支持arc模式,所以使用arc之后,成员对象原来使用的retain参数直接改为strong就可以正常使用了。当使用了arc之后,在两个类中也有可能出现互相引用对方类型的对象,导致双方僵持不休的情况。所以,当循环引用时,一端用强引用,一端用弱引用。
Car.h
#import <Foundation/Foundation.h>
@class Wheel;
@interface Car : NSObject
@property (nonatomic,strong) Wheel *w;
@end
Car.m
#import "Car.h"
@implementation Car
- (void)dealloc
{
NSLog(@"Car is dealloc");
}
@end
Wheel.h
#import <Foundation/Foundation.h>
@class Car;
@interface Wheel : NSObject
@property (nonatomic,weak) Car *c;
@end
Wheel.m
#import "Wheel.h"
@implementation Wheel
- (void)dealloc
{
NSLog(@"Wheel is dealloc");
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Car.h"
#import "Wheel.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
Car *c = [Car new];
c.w = [Wheel new];
}
return 0;
}
运行结果:
2014-06-08 12:36:59.807 weak[690:303] Car is dealloc
2014-06-08 12:36:59.810 weak[690:303] Wheel is dealloc
Program ended with exit code: 0
使用weak修饰的成员对象,在定义完之后就会自动释放,不会等到程序结束时再释放。
3.arc的使用注意
- arc模式下不用再调用release,retain,retainCount来管理内存,arc已经自动实现了这些操作。
- 在实现中可以重写dealloc方法,但是不用再调用父类中的dealloc方法。
- 简单地来说,以前用retain的现在都用strong来修饰。
- 两端互相引用时,一端用strong,一端用weak。