ObjC第三节:内存管理

内存管理

1、简介

     1.1 OC2.0增加了垃圾回收机制,但应清楚内存的管理;alloc+init 和release相当于new和delete

2、自动释放池

     2.1 自动释放池是OC的一种内存自动回收机制,可以将一些临时变量通过自动释放池统一回收释放

     2.2 当一个对象接收到autorelease消息时,对象就会被放到自动释放池中,当池被释放时,其中的对象会接收到一次release消息(离开作用域自动释放,也可以向其发送release消息),即对象放到池中后不是立即释放而是稍后释放

     2.3 当自动释放池释放时,其中的对象可能会被释放,因为池释放时会给其中的每一个对象发送一个release消息,使对象引用计数器-1,如果减为0了,则系统向对象发送dealloc消息销毁对象。(即当某操作导致该对象的引用计数器+1时,没有将其放到池中,池释放时,该对象引用计数器-1,但不为0,就不会被释放)

     2.4 如果显式使用了alloc,就需要使用release或者autorelease,如果没有显式使用alloc则不需要

     2.5 系统自带的类,如NSString,都是已经autorelease的,不需要再进行autorelease操作,但是可以将其加入自己创建的池(NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];  [pool addObject:..];  [pool drain];   栈,先进后出,即释放时先给最后入池的发送release)

     2.6 需要注意:首先,发送过多autorelease消息,和release一样,当清空自动释放池时,可能会引发内存故障;;;其次,出于对系统性能的考虑,能使用release的地方尽量不使用autorelease,因为自动释放池所做的工作要比直接release多很多;;;最后,自动释放池的延迟释放机制可能会导致无用的内存消耗,内存使用效率较低

     2.7 建议仅在不确定对象什么时候应该release时使用

3、引用计数器

     3.1 OC增加了引用计数器机制来记录对象被引用的次数,对象引用几次就需要释放几次

     3.2 通常,创建一个新对象,对象引用计数器置1,若其他地方需要引用该对象就向其发送retain消息,使引用计数器+1,如果不再需要这个对象,可以向其发送release消息,使引用计数器-1

     3.3 Foundation框架中也有增减对象引用计数器的操作,如把对象加入数组或移出数组都可以对引用计数器产生影响

     3.4 增加引用计数器的值:alloc、retain、copy[WithZone:]或者mutableCopy[WithZone]

     3.5 一旦对象引用计数器的值减为0,则编译器给对象发送release消息彻底销毁对象(调用dealloc方法);此时如果继续发送release消息,就会导致内存错误使程序崩溃

     3.6 向对象发送retainCount消息可以获得对象引用计数器的值(但并不常用,仅用于帮助更好地理解引用计数器的变化)

4、内存分配、初始化

     4.1 C++中,对象的空间分配和初始化是混合在一起的,通过构造函数来完成,而OC中,分配空间和初始化是不同的方法,分配空间通过alloc处理,其中也会对所有的实例变量初始化,但是除了从NSObject继承的指针isa之外,所有的实例变量均被置为0或nil;对于实例变量而言,它们的初始值取决于初始化方法的参数,相关初始化代码会被放在一种方法中,通常以init开头

     4.2 alloc消息发送给类对象,分配空间,init消息发送给新对象,进行初始化(初始化通过方法实现,建议初始化方法以init开头(约定但不强制))

     4.3 初始化方法的规范:

           名字以init开头

           返回一个对象,便于之后的使用

           方法体内,执行父类的初始化方法,要检查父类init方法的返回值

           方法体内,要正确处理初始化中的错误,不管是本类的还是继承的

     4.4 处理初始化中的错误:

           方法的参数。在执行[super init]之前,如果方法的参数无效,那么初始化必须停止

            执行父类的init方法。在执行父类init方法时,有可能初始化不成功,此时应放弃初始化操作

            类中特有实例变量的初始化。执行完父类的init,完成对继承自父类成员的初始化后,接着为特有的成员进行初始化,一旦资源分配失败,初始化方法需要终止

     4.5 self指当前的对象,super指父类(仅向上一层的父类)

5、内存回收dealloc

     5.1 dealloc类似于析构函数,当引用计数器为0时自动执行以销毁对象

     5.2 组合类的情况,类A中对象有指针指向类B的对象,需要在类A中重写dealloc,实现在销毁类A的对象时,使该类B对象的引用计数器-1

     5.3 重写dealloc方法时,不仅要释放掉自己实例变量所占的空间,也要释放掉继承的变量所占的空间,[super dealloc](任何类都是如此)

6、扩展

[pool drain];  //清空pool,向所有对象发送一个release

//[pool release];

//release,在引用计数环境下,由于NSAutoreleasePool是一个不可以被retain的类型,所以release会直接dealloc pool对象。当pooldealloc的时候,pool向所有在pool中的对象发出一个release的消息,如果一个对象在这个poolautorelease了多次,pool对这个对象的每一次autorelease都会release。在GC(garbage-collected)环境下release是一个no-op操作(代表没有操作,是一个占据进行很少的空间但是指出没有操作的计算机指令)。

//drain,在引用计数环境下,它的行为和release是一样的。在GC的环境下,这个方法调用objc_collect_if_needed出发GC

//因此,重点是:在GC环境下,release是一个no-op,所以除非你不希望在GC环境下出发GC,你都应该使用drain而不是使用release来释放pool

//[pool autorelease];  //不可以自动释放一个自动释放池


1.进程

 1.1>进程是指在系统中正在运行的一个应用程序(同时打开QQXcode,系统会分别启动2个进程)

 1.2>每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内

2.线程

 1.1>一个进程想要执行任务,必须得有线程(每个进程至少要有一条线程,即主线程)

 1.2>线程是进程的基本执行单元,进程的所有任务都在线程中执行

3.多线程

 3.1>一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务

进程车间,线程车间工人

 3.2>多线程技术可以提高程序的效率

4.多线程的原理

 4.1>同一时间,CPU只能处理一条线程,只有一条线程在工作(执行)

 4.2>多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换)

 4.3>如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

5.多线程的优缺点

 5.1>多线程优点:

   (1)能适当提高程序的执行效率

   (2)能适当提高资源利用率(CPU,内存利用率)

 5.2>多线程缺点:

   (1)开启线程需哟啊占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能

   (2)线程越多,CPU在调度线程上的开销就越大

   (3)程序设计更加复杂:比如线程之间的通信,多线程的数据共享

6.主线程

 6.1>一个iOS程序运行后,默认会开启1条线程,称为主线程”UI线程

 6.2>主线程的主要作用

   (1)显示\刷新UI界面

   (2)处理UI事件(比如点击事件,滚动事件,拖拽事件)

 6.3>主线程使用注意

   (1)别将比较好使的操作放到主线程中

   (2)好使操作会卡主主线程,严重影响UI的流畅度,给用户一种的体验

7.多线程的实现方案


objective-c中,当一个类使用到另一个类时,并且在类的头文件中需要创建被引用的指针时,如下面代码:

A.h文件

#import "B.h"

@interface A : NSObject {

    B *b;

}

@end

为了简单起见:A类是引用类,B类是被引用类,这里先不考虑A类的实现文件。

通常引用一个类有两种办法:一种是通过#import方式引入;另一种是通过@class引入;

这两种的方式的区别在于:

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

2、使用@class方式由于只需要被引用类(B类)的名称就可以了,而在实现类由于要用到被引用类中的实体变量和方法,所以需要使用#importl来包含被引用类的头文件;

3、通过上面2点也很容易知道在编译效率上,如果有上百个头文件都#import了同一 个文件,或者这些文件依次被#improtA->B, B->C,C->D…,一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来讲,使用@class方式就不会出现这种问题了;

4、对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类,B类的代码:

#import "A.h"

@interface B : NSObject {

    A *a;

}

@end

当程序运行时,编译会报错,

当使用@class在两个类相互声明,就不会出现编译报错。

由上可知,@class是放在interface中的,只是在引用一个类,将这个被引用类作为一个类型,在实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类。

A、autorelease
#import <Foundation/Foundation.h>

@interface XYPoint : NSObject

@property (nonatomic) int x, y;

- (id) initWithX:(int)_x andY:(int)_y;

@end;

@implementation XYPoint

@synthesize x, y;

- (id) initWithX:(int)_x andY:(int)_y
{
    if (self = [super init])
    {
        x = _x;
        y = _y;
    }
    return self;
}

@end

int main()
{
    XYPoint *p1 = [[[XYPoint alloc] initWithX:1 andY:2] autorelease];
    XYPoint *p2 = [[XYPoint alloc] initWithX:3 andY:4];
    NSLog(@"%d, %d", p1.x, p1.y);
    NSLog(@"%d, %d", p2.x, p2.y);
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  //新建自动释放池
    [pool addObject:p2];  //把p2放到pool
    //[p2 autorelease];
    //[p2 release];  //不放到自动释放池而直接release,则p2引用计数器为0,内存被回收释放
    sleep(10);  //等待Xs,再继续执行(避免因时间过短,p2空间虽然被释放但依然可以读写)
    p1.x = 10;
    p1.y = 10;
    p2.x = 20;
    p2.y = 20;
    NSLog(@"%d, %d", p1.x, p1.y);
    NSLog(@"%d, %d", p2.x, p2.y);
    [pool drain];  //清空pool,向所有对象发送一个release
    //[pool release];
    //release,在引用计数环境下,由于NSAutoreleasePool是一个不可以被retain的类型,所以release会直接dealloc pool对象。当pool被dealloc的时候,pool向所有在pool中的对象发出一个release的消息,如果一个对象在这个pool中autorelease了多次,pool对这个对象的每一次autorelease都会release。在GC(garbage-collected)环境下release是一个no-op操作(代表没有操作,是一个占据进行很少的空间但是指出没有操作的计算机指令)。
    //drain,在引用计数环境下,它的行为和release是一样的。在GC的环境下,这个方法调用objc_collect_if_needed出发GC。
    //因此,重点是:在GC环境下,release是一个no-op,所以除非你不希望在GC环境下出发GC,你都应该使用drain而不是使用release来释放pool。
    //[pool autorelease];  //不可以自动释放一个自动释放池
    sleep(10);
    NSLog(@"p1引用计数器:%lu", [p1 retainCount]);
    NSLog(@"p2引用计数器:%lu", [p2 retainCount]);
    
    return 0;
}
B、练习
Tair.h
#import <Foundation/Foundation.h>

@interface Tair : NSObject

@property int screws;
@property double radius;

- (id) initWithScrews:(int) _screws andRadius:(double) _radius;
- (void) print;
- (void) setScrews:(int)_screws andRadius:(double)_radius;
//- (void) dealloc;  //不需要声明

@end
Tair.m
#import "Tair.h"

@implementation Tair

@synthesize screws, radius;

- (id) initWithScrews:(int) _screws andRadius:(double) _radius
{
    if (self = [super init])
    {
        screws = _screws;
        radius = _radius;
    }
    return self;
}

- (void) print
{
    NSLog(@"Screws: %d, Radius: %lf", screws, radius);
}

- (void) setScrews:(int)_screws andRadius:(double)_radius;
{
    screws = _screws;
    radius = _radius;
}

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

@end
Car.h
#import <Foundation/Foundation.h>
#import "Tair.h"

@interface Car : NSObject
{
    Tair * tair;
}

@property int seats;
@property double height;
@property float width;
//@property (nonatomic, retain) Tair * tair;  //自己写的set方法会覆盖@property生成的

- (id) initWithSeats:(int)_seats andHeight:(double)_height andWidth:(float)_width andTair:(Tair *)_tair;
- (void) print;
- (void) setTair:(Tair *)_tair;

@end
Car.m
#import "Car.h"

@implementation Car

@synthesize seats, height, width;
//@synthesize tair;

- (id) initWithSeats:(int)_seats andHeight:(double)_height andWidth:(float)_width andTair:(Tair *)_tair
{
    if (self = [super init])
    {
        seats = _seats;
        height = _height;
        width = _width;
        if (tair != _tair)
        {
            [tair release];
            tair = [_tair retain];
        }
        //self.tair = _tair;  //与上面if等价
    }
    return self;
}

- (void) print
{
    [tair print];
    NSLog(@"Seats: %d, Height: %lf, Width: %f", seats, height, width);
}

- (void) setTair:(Tair *)_tair
{
    if (tair != _tair)
    {
        [tair release];
        tair = [_tair retain];
    }
    //tair = _tair;
}

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

@end
main.m
#import <Foundation/Foundation.h>
#import "Car.h"
int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        Tair *t1 = [[Tair alloc] initWithScrews:1 andRadius:2.2];
        [t1 print];
        NSLog(@"t1引用计数器:%lu", [t1 retainCount]);  //无符号长整型
        Tair *t2 = [[Tair alloc] init];
        [t2 setScrews:3 andRadius:4.4];
        [t2 print];
        NSLog(@"t2引用计数器:%lu", [t2 retainCount]);
        
        Car *c1 = [[Car alloc] initWithSeats:1 andHeight:2 andWidth:3 andTair:t1];
        [c1 print];
        NSLog(@"t1引用计数器:%lu", [t1 retainCount]);
        NSLog(@"c1引用计数器:%lu", [c1 retainCount]);
        Car *c2 = [[Car alloc] init];
        c2.seats = 4;
        c2.height = 5;
        c2.width = 6;
        c2.tair = t2;
        [c2 print];
        NSLog(@"t1引用计数器:%lu", [t1 retainCount]);
        NSLog(@"t2引用计数器:%lu", [t2 retainCount]);
        NSLog(@"c2引用计数器:%lu", [c2 retainCount]);
        
        [t1 release];
        [t2 release];
        [c1 release];
        [c2 release];
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值