您的位置:首页 > 职场人生

黑马程序员---OC基础知识⑦

2015-04-11 16:00 274 查看
※内存管理很重要

1.为什么要管理内存

首先看这样一段代码:

<span style="font-size:18px;">int main(int argc, const charchar * argv[])
{
int a = 10;
BOOL b = YES;
char c = 'w';
return 0;
}  </span>


像a、b、c这样的基本数据类型的局部变量存放在栈上,函数执行结束时这些存储单元自动被释放。

在看看这样的代码:

<span style="font-size:18px;">int main(int argc, const charchar * argv[])
{
Person *p = [[Person alloc] init];
[p setAge:10];
NSLog(@"%d", [p age]);
return 0;
}  </span>
像Person这样的对象类型,分配在堆上,在上面的代码中,[[Person alloc] init]在堆上创建了一个

Person类型的对象 Person类型的指针p指向了这个对象,而指针p类型保存在栈上,当程序结束时p所

占用的内存被清空,而存储在堆上得对象没有被释放,依然放在内存中。OC项目中的代码中处处都是

这样的代码,如果不进行内存管理,程序运行过程会不断地加大内存开销,最终可能导致程序崩溃。

2.引用计数器

① 每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在

使用这个OC对象

② 每个OC对象内部专门有4个字节的存储空间来存储引用计数器

③ 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1

④ 当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数

器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出

我们可以使用引用计数器来进行内存管理,比如:

当程序结束时,p不存在了,而p指向的对象的对象的引用计数器由1变成了0,意味着没有引用它的指

针了,这时候可以将堆中对象占用的内存回收,进行合理的内存管理。现在的问题就是如何进行管理对象的引用计数器

OC语言中定义了下面几种对引用计数器的操作:

① 给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)

② 给对象发送一条release消息,可以使引用计数器值-1

③ 可以给对象发送retainCount消息获得当前的引用计数器值

由此可以得到启发,每当创建对象或者复制对象时可以使用retain将其"引用计数器"的值+1,而不再使

用引用它的指针时,将对象的"引用计数器"的值-1,问题就来了:如何在对象的"引用计数器"的值为0

的时候将对象销毁呢:

对象的销毁

① 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收

② 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息

③ 一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言

④ 一旦重写了dealloc方法,就必须调用[superdealloc],并且放在最后面调用

⑤ 不要直接调用dealloc方法

⑥ 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)

下面对Person类的deallloc方法进行重写:

@implementation Person

// 当一个Person对象被回收的时候,就会自动调用这个方法

- (void)dealloc

{

NSLog(@"Person对象被回收");

// super的dealloc一定要调用,而且放在最后面

[super dealloc];

}

对retain、release、retainCount做一些简单的测试:

#import <Foundation/Foundation.h>

#import "Person.h"

int main(int argc, const charchar * argv[]) {

Person *p = [[Person alloc] init]; // 执行后p指向的对象retainCount为1

NSUInteger c = [p retainCount]; // 获取p指向的对象的retainCount

NSLog(@"计数器:%ld", c);

[p retain]; // 执行完这句之后retainCount变为2

[p release]; // retainCount为1

[p release]; // retainCount为0 系统自动调用Person的dealloc方法

return 0;

}

程序运行后输出的结果是:

计数器:1

Person对象被回收

使用引用计数器要特别注意:

注意点一:给已经释放的对象发送了一条setAge消息-------->闪退

注意点二:给空指针发送消息不会报错 OC中不存在空指针错误

<span style="font-size:18px;">#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const charchar * argv[]) {
//
Person *p = [[Person alloc] init];
[p retain];
//
[p release];
[p release];
p.age = 10; // 给已经释放的对象发送了一条setAge消息-------->闪退
return 0;
}  </span>


这时候说指针p指向了僵尸对象(对象的内存已经被释放了),此时p被成为野指针。

这时候有这样一种解决方案将p赋值为ni,因为对空指针(值为nil的指针,基本数据类型而言值为0)

调用方法时,程序什么也不做。代码如下:

<span style="font-size:18px;">#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const charchar * argv[]) {
//
Person *p = [[Person alloc] init];
[p retain];
//
[p release];
[p release];

p = nil;// 对象计数器rc减为0时调用 以此来消除野指针

p.age = 10;// 给空指针发送消息不会报错 OC中不错在空指针错误
[p release];// 给空指针发送消息不会报错 OC中不错在空指针错误
return 0;
}  </span>


3.多对象内存管理

在实际开发中,可能遇到这样的情况,一个类A的成员变量类型也是一个类B,对成员变量B类型对象的

引用就不只是B类型,可能还有A类型。这样容易引发这样两种问题,①B类型的对象被销毁了A成员仍

然在引用它,②A类型的对象销毁顺带着将B类型的成员变量销毁,但仍有B类型的指针指向该对象。只

要按照以下原则使用引用计数,就可以对内存进行合理的管理

原则1. 谁创建谁release

① 如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease

② 换句话说,不是你创建的,就不用你去[auto]release

原则2. 谁retain,谁release

① 只要你调用了retain,无论这个对象是如何生成的,你都要调用release

例如Person类中包含了一个Book类,规范的设计代码应该是这样:

<span style="font-size:18px;">#import "Person.h"

@implementation Person
- (void)setBook:(Book *)book
{
_book = [book retain];
}
- (Book *)book
{
return _book;
}

- (void)dealloc
{
[_book release];
NSLog(@"Person对象被回收");
[super dealloc];
}
@end  </span>


<span style="font-size:18px;">#import <Foundation/Foundation.h>
#import "Book.h"
#import "Person.h"
int main(int argc, const charchar * argv[]) {

Book *b = [[Book alloc] init];

Person *p1 = [[Person alloc] init];

[p1 setBook:b];

[p1 release];
p1 = nil;

[b release];
b = nil;
return 0;
}
</span>


上面的程序对多个对象内存管理很好地进行了总结:

1.你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)// 如 Person 要使用Book

2.你不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release)

3.谁retain,谁release // Person retain Book ,Person就要release Book

4.谁alloc,谁release // Book alloc Book对象,Book就要release Book对象

4.set方法完善---针对内存管理

在刚刚的多对象内存管理中,仍让存在一些不合理的地方:

假设有这样的场景,人拥有一辆车,按照刚才的方法先创建一辆车carA,设置人的车为carA,但是当

人要换车时人并没有对carA进行release(因为人 没有调用dealloc方法,但实际上人已经不再引用carA

了,应当将carA的rc减一)。解决的方案是在Person的set方法中将旧的车release一次:

- (void)setCar:(Car *)car

{

// 对当前正在使用的车(旧车)做一次release

[_car release];

// 对新车做一次retain操作

_car = [car retain];

}

这样似乎能解决问题,但是如果新传入的汽车仍然是carA(现在的车),且在执行[p setCar:carA];之前

carA被release了一次(也就是说carA现在的rc是1),此时执行setCar方法,就会引起野指针问题。因此

再对set方法进行完善:

<span style="font-size:18px;">- (void)setCar:(Car *)car
{
if (car != _car)
{
// 对当前正在使用的车(旧车)做一次release
[_car release];
// 对新车做一次retain操作
_car = [car retain];
}
}  </span>


总结

内存管理代码规范:

1.只要调用了alloc,必须有release(autorelease)

2.set方法的代码规范

1>.基本数据类型:直接赋值

- (void)setAge:(int)age

{ // 基本数据类型 不需要管理内存

_age = age;

}

2>.OC对象类型

- (void)setCar:(Car *)car

{

// 1.先判断是不是新传进来的对象

if(car != _car)

{

// 2.对旧的对象做一次release

[_car release];

// 3.对新传进来的对象做一次retain

_car = [car retain];

}

}

3.dealloc方法的代码规范

1>一定要调用[super dealloc]且放在最后

2>.对当前对象所拥有的其他对象做一次release

- (void)dealloc

{

[_car release];

[super dealloc];

}

利用@property参数对内存进行管理

默认情况下@property生成set方法只是简单的赋值操作:

在Person类的声明中添加这行代码

@property Book *book;

意味着编译器在实现中帮助生成的set方法是这样的

- (void)setBook:(Book *)book

{

_book = book;

}

这样显然是不合理的(刚刚讨论的),若要为@property添加retain关键字:

@property (retain) Book *book;

编译器便会添加这样的set方法:

- (void)setBook:(Book *)book

{

if (book != _book)

{

[_book release];

_book = [book retain];

}

}

可见retain参数的作用: 生成的set方法里面,release旧值,release新值。

因此开发中对于成员变量是类类型时@property要添加retain参数,需要注意的是使用NSString作为

成员变量类型时也要添加,因为NSString也是了类类型。

5.property参数详解

1).内存管理相关的参数(三择一)

retain: release旧值、retain新值(适用于OC对象类型)

assign:直接赋值(默认,适用于非OC对象类型)

copy:release旧值, copy新值

2).是否要生成set方法

readwrite:同时生成setter和getter的声明和实现(默认)

readonly:只会生成getter的声明和实现

3).多线程管理

nonatomic:性能高(一般就用这个)

atomic:性能低(默认)

4).setter和getter方法的名称

setter:决定了set方法的名称,一定要由个冒号:

getter:决定了get方法的名称(一般用在BOOL类型的属性值)

注意:

当成员变量类型为BOOL类型时,get方法方法名一般以is开头

@property (getter=isRich) BOOL rich;

6.类的循环引用

两个类循环引用时(A引用B,B也引用A)

在两个类中在.h分别用@class声明另一个类 在m文件中使用#import

@class仅仅是告诉编译器这是一个类

@class和#import的区别

#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;

@class方式只是告诉编译器在A.h文件中 B *b只是类的声明,具体这个类里有什么信息,这里不需要

知道,等实现文件中真正要用到时,才会真正去查看B类中信息

如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头

文件稍有改动,

后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用

@class方式就不会出现这种问题了

在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入

被引用类

老师总结

1.@class的作用:仅仅告诉编译器,某个名称是一个类

2.开发中引用一个类的规范

1>.在.h文件中用@class声明类

2>.在m文件中用#import来包含类的所有东西

3.两端循环引用解决方案

1>.一端用retain

2>.一端用assign

例如Person拥有Card类型的属性,Card类型拥有Person类型的属性:

在Person.h中

<span style="font-size:18px;">#import <Foundation/Foundation.h>
// @class仅仅是告诉编译器这是一个类
@class Card;
@interface Person : NSObject
@property (nonatomic, retain) Card *card;
@end  </span>


在Person.m中

<span style="font-size:18px;">#import "Person.h"
#import "Card.h"

@implementation Person
- (void)dealloc
{

NSLog(@"Person被销毁");
[_card release]; // 使用retain参数的对象 需要这一句
[super dealloc];
}
@end  </span>


Card.h中

<span style="font-size:18px;">#import <Foundation/Foundation.h>
@class Person;
@interface Card : NSObject
@property (nonatomic, assign) Person *person;
@end  </span>


在Card.m中

<span style="font-size:18px;">#import "Card.h"
#import "Person.h"

@implementation Card
- (void)dealloc
{
NSLog(@"Car被销毁");
//    [_person release]; // 因为使用的时assign参数 所以不用这一句
[super dealloc];
}
@end  </span>


7.autorelease的使用

1).autorelease的基本用法

1>.会将对象放到自动释放池

2>.当自动释放池销毁时,会对池子里面的所有对象做一次release操作

3>.方法会返回对象本身

4>.调用完autorelease方法后,对象的计数器不变

2).autorelease的好处

1>.不用担心对象释放的时间

2>.不用关心什么时候调用release

3).autorelease的使用注意

1>.占用内存较大的对象不要随便使用autorelease

2>.占用内存较小的对象使用autorelease,没有太大影响

4).错误写法

1>.alloc之后调用了autorelease,又调用release

@autoreleasepool

{

Person *p = [[[Person alloc] init] autorelease];

[p release];

}

2>.连续调用多次autorelease

@autoreleasepool

{

Person *p = [[[[Person alloc] init] autorelease] autorelease];

}

5).自动释放池

1>.在IOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在(先进后出)

2>.当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池

6).自动释放池的创建方式

1>.IOS5.0之前

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

Person *p = [[[Perosn alloc] init] autorelease];

[pool release];// [pool drain];

2>.IOS5.0开始

@autoreleasepool

{

Person *p = [[[Person alloc] init] autorelease];

}

autorelease应用

1.系统自带的方法里面没有包含alloc、new、copy说明返回的对象都是autorelease的

[NSString stringWithFormat:...];

2.开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象

1>.创建对象时候不要直接用类名,一般用self

+ (instancetype)person

{

return [[[self alloc] init] autorelease];

}

2>.添加带参数的方法时,先使用1>中创建的方法初始化再赋值

+ (instancetype)personWithAge:(int)age

{

Person *p = [self person];

p.age = age;

return p;

}

8.ARC机制

arc是一种编译器特性:编译时编译器帮助完成代码 ,不能和垃圾回收混为一谈

而JAVA垃圾回收是运行时特特性。

强弱指针:指针分2种

1>强指针: 默认情况下,所有的指针都是强指针 __strong

2>弱指针: __weak

ARC所做的工作:

使用arc机制,每当使用alloc时,编译器都会添加release;

每一个类的dealloc方法中都会增加对类成员变量的release;

ARC的判断准则:只要没有强指针指向对象,就会释放对象

// 错误写法(没有意义的写法)

__weak Person *p = [[Person alloc] init]; // 创建 随即就被销毁了 相当于把空值nil赋给了p

ARC特点(使用时需要注意的地方)

1>.不允许调用release、retain、retainCount

2>.允许重写dealloc,但是不允许调用[super dealloc];

3>.@property的参数

strong:成员变量是强指针 (适用于OC对象类型)

weak:成员变量是弱指针 (使用于OC对象类型)

assign:适用于非OC对象类型

4>.以前的retain改为用strong,其他不变

设置单个文件是否使用arc (对于导入其他第三方开发框架的情况)

Build Phases->Compile Sources->选择文件->修改文件编译参数Compile Flags

-fno-objc-arc//不需要arc

-f-objc-arc//需要arc

将整个非arc的工程转为arc工程 Edit->Refactor->Convert to Objective-C ARC

使用ARC时如何处理循环引用:

当两端循环引用的时候,解决方案:

1.使用ARC

修改@property参数:一端用strong,一端用weak

2.非ARC

修改@property参数: 一端用retain另一端用assign
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: