天天看点

黑马程序员——9、OC语言(内存管理和ARC)一、内存管理二、ARC

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

一、内存管理

移动设备的内存极其有限,每个app所能占用的内存也是有限制的。

内存管理的范围:任何继承了NSObject的对象,对基本数据类型无效。

  内存管理方法:

  • 你想使用某个对象,就应该让对象的引用计数器+1
  • 当你不想使用某个对象时,就应该让对象引用计数器-1
  • 谁retain,谁release
  • 谁alloc,谁release

  内存管理代码规范:

内存管理代码规范:
 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];
 }
 
 4.如果对象没有使用alloc就不需要release,如NSString类型对象
           

  1. 引用计数器

每个OC对象都有自己的引用计数器,表示“对象被引用的次数”。

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

(1)引用计数器的作用:

1》当使用alloc、new或copy创建一个新对象时,新对象的引用计数器就为1。

2》当一个对象的引用计数器的值为0时,对象所占的内存就会被系统回收。如果对象的引用计数器不为0,对象在整个程序的运行过程中,都不会被回收(除非整个程序退出)。

(2)引用计数器的基本操作(针对当前对象)

1》retain 计数器+1 返回对象本身

2》release 计数器-1 没有返回值

3》retainCount 返回当前计数器值

4》dealloc

* 当一个对象要被回收的时候,就会自动调用

* 一定要调用[super dealloc],而且放在最后面

//当Person对象被回收时,一定会【自动】调用这个方法
- (void)dealloc
{
    NSLog(@"Person对象被回收!!!");
    
    //super的dealloc一定要调用,而且放在最后面
    [super dealloc];
}
           

(3)对象的销毁

1》当对象的引用计数器为0时,对象被销毁,其所占的内存被系统回收。

2》当一个对象销毁时,系统会自动向对象发送一条dealloc的消息。(调用- dealloc方法)

3》一般会重写dealloc方法,在这里释放相关的资源。

4》一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面。

5》不要直接调用dealloc方法,系统在对象销毁时会调用的。

6》一旦对象被回收了,它占用的内存就不再可用,坚持使用就会导致野指针错误。

(4)僵尸对象、野指针、空指针

1》僵尸对象:所占内存已经被回收的对象,僵尸对象不能再使用

给已经释放的对象发送了一条消息:

message sent to deallocated instance 0x1001099d0

默认情况下,Xcode是不会管僵尸对象的,如何开启僵尸对象监控:

Xcode界面:set the active scheme -> edit scheme.. -> Diagnostics -> Enable Zombie Objects

2》野指针:指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错

EXC_BAD_ACCESS:访问了一块坏的内存(已被回收,不可用的内存)

3》空指针:没有指向任何东西的指针(存储的东西是nil,null,0),给空指针发送消息不会报错

  2. setter方法的内存管理

(1)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];

    }

}

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

- (void)dealloc

{

[_car release];

[super dealloc];

}

(2)dealloc方法实现

1》一定要调用[super dealloc],且放最后
@implementation Person
- (void)setCar:(Car *)car
{
    // 对当前正在使用的车(旧车)进行一次release操作
    // [nil release] 无操作
    if (car != _car) {
        [_car release];
        
        // 对新车进行一次retain操作
        _car = [car retain];
    }
}
- (Car *)car
{
    return _car;
}
- (void)setAge:(int)age
{
    // 基本数据类型不需要管理内存
    _age = age;
}
- (int)age
{
    return _age;
}

- (void)dealloc
{
    // Person对象释放,Car对象进行一次release操作
    [_car release];
    
    NSLog(@"年龄为%d的Person对象被dealloc", self.age);
    [super dealloc];
}
@end
           

  3. 设置property参数进行内存管理

@property 参数:

1》set方法内存管理相关的参数

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

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

* copy:release旧值,copy新值(一般用于NSString *)

2》是否要生成set方法

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

* readwrite:同时生成setter和getter的声明和实现

3》多线程管理

* nonatomic:性能高(推荐)

* atomic:性能低(默认)

4》setter和getter方法的名称

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

* getter:决定了get方法的名称(一般用在BOOL类型变量中,getter = isRich)

@interface Person : NSObject

@property int age;

@property (nonatomic, readonly) double height;

@property (setter = setAbc:, getter = aaa) double weight;

// 返回BOOL类型的方法中,方法名一般以is开头
@property (getter = isRich) BOOL rich;

// retain参数:生成的set方法中,release旧值,retain新值
@property (retain) Book *book;

@property (retain) NSString *name;

@end
           
【注意】:被retain过的属性,必须在dealloc方法中release对应的属性。
@implementation Person
//- (void)setBook:(Book *)book
//{
//    if (_book != book) {
//        [_book release];
//        _book = [book retain];
//    }
//}

- (void)dealloc
{
    [_book release];
    [_name release];
    [super dealloc];
}
@end
           

  4. 循环引用

循环引用场景:A类引用了B类,同时B类也引用了A类。导致A对象和B对象永远无法释放。

面对这种两端循环引用,两端循环retain引用:

解决方法:一端用retain,一端用assign。

【开发中一个类引用另一个类的规范】:

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

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

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

格式:@class 类名;

【@class和#import的区别】

1>#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中B *b只是类的声明,具体这个类里面有什么信息,编译器不需要知道。等实现文件.m中真正用到的时候,才会真正去查看B类里面的信息。

2>如果很多头文件都#import了同一个文件,那么一旦修改了最开始的头文件,后面所有#import引用到这个文件的所有的类都需要重新编译一遍。@class就不会有这种问题。

3>使用了@class方式,在.m实现文件中,如果需要用到被引用的实体变量或方法,我们还需要使用#import方式引入被引用类。#import方式则不用。

  5. autorelease

@autoreleasepool
    { // { 开始 代表创建了释放池
        
        // autorelease方法会返回对象本身
        // [p autorelease];调用完autorelease方法后,不影响对象计数器
        // autorelease会将对象放到一个自动释放池中
        // 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
        Person *p = [[[Person alloc] init] autorelease]; // p-1
        
        p.age = 10;
        
        @autoreleasepool {
            Person *p2 = [[[Person alloc] init] autorelease]; // p2-1
            p2.age = 20;
        } // p2-0
        // p-0
    } // } 结束 代表销毁释放池
           

(1)autorelease的基本用法

1》autorelease会将对象【放到】一个自动释放池中

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

3》返回对象本身

4》调用完autorelease方法后,不影响对象计数器(销毁的时候计数器才改变(release))

(2)autorelease的好处

1》不用再关心对象释放的时间

2》不用再关心什么时候调用release

(3)autorelease的使用注意

1》占用内存较大的对象不要随便使用autorelease(一直占着,直到pool销毁才释放,太耗内存)

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

(4)常见错误

1》alloc之后调用了autorelease,又调用release,池释放时出现野指针错误

2》连续调用autorelease,池释放时也会出现野指针错误

@autoreleasepool {
        // p-1
        // 若调用2次autorelease方法,释放池的时候会进行2次release操作,出现野指针错误
        Person *p = [[[Person alloc] init] autorelease];
        // p-0
        [p release];
    } // 野指针错误
           

(5)自动释放池

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

2》当一个对象调用autorelease方法时,会将这个对象放到栈顶的池子中

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

1》IOS 5.0 before
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
Person *p1 = [[[Person alloc] init] autorelease];
     
[pool release]; // [pool drain]; 排水..
           
2》IOS 5.0 later
@autoreleasepool {
        // ... code ...
    }
           

(7)系统自带的方法

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

如:NSString、NSNumber

NSString *str = @"wert";
NSString *str1 = [NSString stringWithFormat:@"age is %d", 15];
           

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

* 创建对象的时候,不要直接用类名。一般用self

+ (id)person

{

// self 任何子类调用该方法,都可以 self == 子类

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

}

// Person *p = [[[Person alloc] init] autorelease];
Person *p = [Person person];
        
p.age = 200;
           

二、ARC

ARC是编译器特性:

IOS 5之后,完全消除了手动管理内存的繁琐,编译器会自动在适当的地方插入适当的retain、release、autorelease语句。

  1. ARC的基本使用

(1)指针分2种

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

2》弱指针:__weak

1》不允许调用release、retain、retainCount

2》允许重写dealloc,但不允许调用[super dealloc]

3》@property参数

* strong:成员变量是强指针,相当于原来的retain(适用于OC对象)

* weak:成员变量是弱指针,相当于原来的assign(还是适用于OC对象)

* assign:适用于非OC对象类型

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

(3)ARC的特点

#import <Foundation/Foundation.h>
@class Dog;

@interface Person : NSObject

@property (nonatomic, weak) Dog *dog;
@property (nonatomic, strong) NSString *name;

@end
           
/* 对象一被创建出来就被释放了(错误写法)
 __weak Person *pp = [[Person alloc] init];
 */
__strong Person *p = [[Person alloc] init];

__weak Person *p2 = p;

p = nil; // 没有强指针指向对象,对象之后被释放
// 弱指针指向的对象都不存在了,弱指针被清空,不会发生野指针
p2 = nil; // 弱指针不能决定是否释放对象<span style="font-family:SimHei;font-size:18px;">
</span>
           

 2. ARC的转换功能

要想将非ARC的代码转换为ARC的代码,有2种方式:

1》使用Xcode的自动转换工具

* Build Settings下选上Warnings中的Other Warning Flags 为 -Wall,这样编译器就会检查所有可能的警告,有助于我们避免潜在的问题;

* Build Options下面的Run Static Analyzer选项也最好启用,这样每次Xcode编译项目时,都会运行静态代码分析工具来检查我们的代码;

* 设置"Objective-C Automatic Reference Counting"选项为YES,不过Xcode自动转换工具会自动设置这个选项,这里只是说明一下如何手动设置;

* Edit -> Refactor -> Convert to Objective-C ARC.. -> Check -> NEXT -> Save.

2》手动设置某些文件支持ARC

项目参数的Build Phases -> Compile Sources -> 修改Compiler Flags:

用别人的非ARC框架时:

如果.m 文件不是ARC文件了,那么就告诉编译器,这个项目不是ARC的 (-fno-objc-arc);

如果项目默认情况下就不是ARC,想让某个文件是ARC (-fobjc-arc)。

  3. ARC的循环功能

循环引用解决方案:

ARC:

一端用strong,一端用weak

非ARC:

一端用retain,一端用assign

#import <Foundation/Foundation.h>
@class Dog;

@interface Person : NSObject

@property (nonatomic, strong) Dog *dog;

@end
           
#import <Foundation/Foundation.h>
@class Person;

@interface Dog : NSObject

@property (nonatomic, weak) Person *person;

@end
           

继续阅读