黑马程序员——OC内存管理

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

基本原理

  • 什么是内存管理
    1、移动设备的内存极其有限,每个app所能占用的内存是有限制的。
    2、当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等。
    3、管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效。

  • 对象的基本结构
    1、每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象。
    2、每个OC对象内部专门有4个字节的存储空间来存储引用计数器。

  • 引用计数器的作用及操作
    1、当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1。
    2、当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出。
    操作引用计数器的方法基本使用:
    1、retain :计数器+1,会返回对象本身
    2、release :计数器-1,没有返回值
    3、retainCount :获取当前的计数器

  • 对象销毁
    1、当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收。
    2、当一个对象被销毁时,系统会自动向对象发送一条dealloc消息。
    3、一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言。如下所示:

- (void)dealloc
{

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

当对象被回收后,会出现几个概念:
1、僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用。
2、野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错。所报的错误为(EXC_BAD_ACCESS),这个是常见的错误,只要我们看到这个错误信息就知道是野指针问题。
*EXC_BAD_ACCESS : 访问了一块坏的内存(已经被回收、已经不可用的内存。
3、空指针 :没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错。

内存管理规则

在一个程序中,我们会创建多个对象,对象之间也互有联系,但是我们只要按照计数器+1必-1,使计数器数值恒定即可。
1、你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)。
2、你不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release)。
3、谁retain,谁release。
4、谁alloc,谁release。
如下程序创建了人和书,人拥有书时一系列的计数器操作:

main函数

int main()
{
    Book *b = [[Book alloc] init];

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

    [p1 setBook:b];

    [p1 release];
    p1 = nil;

    [b release];
    b = nil;
    return 0;
}

Book.h

#import 

@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 
#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
{
    _book = [book retain];
}

- (Book *)book
{
    return _book;
}

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

set方法内存管理

当采用上述代码的set方法设置时,如果我们从书1换成书2,就会出现内存泄露的情况,因为我们的书1并没有release,没有回收对象。这时我们就需要对set方法重写,需要判断是否为新对象、并对就对象进行release、对新对象进行retain:

- (void)setCar:(Car *)car
 {
    // 1.先判断是不是新传进来对象
    if ( car != _car )
    {
        // 2.对旧对象做一次release
        [_car release];

        // 3.对新对象做一次retain
        _car = [car retain];
    }
 }

这是对于OC对象,如果是基本数据类型,setter方法不需要进行修改:

- (void)setAge:(int)age
 { 
    _age = age;
 }

同时在代码中,只要是调用了alloc,也就是分配存储空间,在代码的下方必须要有release(autorelease),但是如果对象不是通过alloc产生的,比如说NSString *str = @"Tang";,此时就不需要release。
此时在- (void)dealloc方法中,对self(当前)所拥有的其他对象做一次release,每一个对象对应一个release,如下:

- (void)dealloc
{
    [_age release];
    [super dealloc];
}

@property参数

set方法的内存管理虽然很好的控制了内存的回收,但是却也造成了代码重复、繁琐等缺点,这时候OC利用@peoperty加参数来控制set方法的内存管理。
书写格式:@property (参数) 类名 *属性名;
参数分为四类:
1、set方法内存管理相关的参数
* 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类型)

对于OC对象类型,采用这种方式:

@property ( nonatomic, retain) 类名 *属性名;

对于非OC对象类型,比如说int\float\enum\struct等,采用这种方式:

@property ( nonatomic, assign) 类型名称 属性名;

采用这种方式可以很好的简化代码,将更多的精力投入与功能上。

循环引用

当代码中两个类互相引用时:

#import "B.h"
@interface A : NSObjcet
{
    B *b;
}
@end
#import "A.h"
@interface B : NSObject
{
    A *a;
}
@end

这种代码编译会报错。当使用@class在两个类相互声明,就不会出现编译报错。使用 @class 类名; 就可以引用一个类,说明一下它是一个类。

和#import的区别:
1、#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息。
2、如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方式就不会出现这种问题了。
3、在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类。

循环retain

    p.card = c;
    c.person = p;

即使我们的retain和release都没有问题,但是在程序运行中任然会有内存泄露,这时我们可以将其中一个retain改为assign就可以解决这个问题:@property (nonatomic, retain) Card *card; @property (nonatomic, assign) Person *person;

autorelease

  • autorelease的基本用法

    1、会将对象放到一个自动释放池中
    2、 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
    3、 会返回对象本身
    4、 调用完autorelease方法后,对象的计数器不变

  • autorelease的好处

    1、不用再关心对象释放的时间
    2、不用再关心什么时候调用release

  • autorelease的使用注意

    1、占用内存较大的对象不要随便使用autorelease
    2、占用内存较小的对象使用autorelease,没有太大影响

  • 错误写法

    1、alloc之后调用了autorelease,又调用release

@autoreleasepool
 {
    // 1
    Person *p = [[[Person alloc] init] autorelease];

    // 0
    [p release];
 }

2、连续调用多次autorelease

@autoreleasepool
 {
    Person *p = [[[[Person alloc] init] autorelease] autorelease];
 }
  • 自动释放池

    1、在iOS程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
    2、当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池

  • 自动释放池的创建方式

    1、iOS 5.0前

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

2、iOS 5.0 开始

@autoreleasepool
 {

 }
  • 注意点
    1、系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
    2、开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象,创建对象时不要直接用类名,一般用self
+ (void)person
{
    return [[[self alloc] init] autorelease];
}

在Xcode中如何取消ARC,在5.1以前的版本中在创建项目的时候不要勾选ARC即可,如图所示:
黑马程序员——OC内存管理_第1张图片
之后的版本中,打开你的工程,点击目录的工程文件,最顶端蓝色的,然后选择project下你的工程,还是蓝色那项,然后build Settings,然后往下拉,在Apple LLVM 5.0 - Language - Objective C 里有一个选项,Objective-C Automatic Reference Counting 选择NO,就可以了。如图所示:
黑马程序员——OC内存管理_第2张图片

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

你可能感兴趣的:(黑马程序员——OC内存管理)