OC加强(一)之内存管理

1.为什么要进行内存管理?

由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存 较多时,系统就会发出内存警告,一个app可用的内存是被限制的,如果一个app使用的内存超 过20M,则系统会向该app发送Memory Warning消息。收到此消息后,需要回收一些不需要再 继续使用的内存空间,比如回收一些不再使用的对象和变量等,否则程序会崩溃。

 

这里首先要解释一个常识概念:内存,存储空间,各自的功能?

内存:我们一般说的手机内存其实是指运行内存,,简称运存,即 RAM-全称是RamdomAccessMemory:随机存取存储器 

RAM 的大小直接决定手机后台能开多少程序,能运行多少软件,RAM越大,就表示可以运行的软件就越多

存储空间:存储空间就是下载的软件或各种文件的容器,即ROM-全称是Read Only Memory:只读存储器

ROM的大小决定了手机能装多少软件存多少文件,歌曲,电影,小说等..

 

所以我们知道,手机内存是很小的,一个APP运行如果占用过多内存,苹果规定如果单个APP运行内存超过20M,就会报内存警告,所以必须对内存进行管理,否则会引起程序崩溃

软件被强制闪退,关闭

2.OC 内存管理的范围:

OC管理所有继承自NSObject类的对象,不去管理其他基本数据类型的对象,(只针对堆区)

为什么?

本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于 栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指 向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄 露。 

通俗讲就是:栈区存的是一些基本类型的局部变量,指针变量也是局部变量,这些变量在程序结束会自动被系统回收,

但堆区是不会被系统管理的,如果我们也不去管理,就像永远不清理的垃圾,越来越多,占着内存空间,就相当于你有100块钱,被偷了50块,也就是内存泄漏的概念了

 

这里又想到一个小的概念:

为什么定义堆区时说堆区是程序运行过程中动态分配的内存空间?

可以想一下,堆区是存储对象的,而只有创建对象时调用类方法alloc时才会在堆区申请内存空间,所以显而易见堆区就是只有当实例化对象时才会被分配内存空间

显而易见:定义油然而生

 

3.内存管理的原理和分类:

3.1引用计数器

引用计数器是一个整数,是用来计算对象被引用的次数的

对象创建时向计算机申请了一块内存空间,并在这一块内存空间中分出了一个8个字节的空间用来存放引用计数器

3.2对引用计数器的操作

给对象发送消息,进行相应的计数器操作。

retain消息:使计数器+1,该方法返回对象本身

release消息:使计数器-1(并不代表释放对象)

retainCount消息:获得对象当前的引用计数器值%lu %tu 

 

作用就是给对象发消息(每天干的活)

 

又想到一个小概念:

我们玩苹果手机时通常会按home键,让程序"看似退出",但实际上程序只是放在后台运行了,并没有退出,怎么看到后台程序呢?双击home键即可看到

所以程序退出(双击home键,按住应用程序往上滑),但有个问题微信,qq,我让程序退出后还是能够接受消息,这是不是说明程序在后台是运行着呢?并

没有退出,但我明明已经双击home把程序滑退了啊,这个怎么解释呢?

其实,程序确实是退出了,这些消息其实就是推送消息,而推送消息是由苹果的服务器推送给你的,并不是APP推送的,(这项技术成为手机推送开发技术)

应该是涉及服务器和数据库的,这也是学习iOS的弊端,好多都不能深入了解,要想清楚,还是要往全栈上发展和努力.

 

Objective-C提供了三种内存管理方式:
MannulReference Counting(MRC,手动管理,在开发 iOS4.1之前的版本的项目时我们要

自己负责使用引用计数来管理内存,比如要手动 retain、release、autorelease 等,而在其后 的版本可以使用 ARC,让系统自己管理内存。)

automatic reference counting(ARC,自动引用计数,iOS4.1 之后推出的)

garbage collection(垃圾回收)。iOS不支持垃圾回收; ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存;

开发中如何使用:需要理解MRC,但实际使用时尽量用ARC 

 
4.MRC(手动内存管理)
 1)关闭ARC的方法(All 里维斯)
思考:内存管理的目的?要释放堆区的内存,防止内存泄漏.因为它不像栈区存储的局部变量那样,一段代码结束(跳出括号)就会自动释放
那么问题来了,假如我释放了堆区的内存,程序员怎么知道呢?
这就涉及到内存释放的原理了:
这和继承息息相关,我们知道我们写的子类都继承自父类,也就拥有了父类的属性和方法,当我们释放子类的成员变量时,并没有释放继承自父类的
成员变量,也就是这部分变量是不受管理的,所以必然会造成内存泄漏问题,我们该如何避免呢?
 
[super delloc]是为了释放子类继承父类的一些变量(其实子类继承父类,子类是比父类要庞大的,子类是完全拥有父类的所有属性的,当子类被释放也就意味着父类的所有属性都要被释放,所以子类被释放时,父类也就会跟着被释放了,所以其本质是必须要调用父类被释放的方法,因为alloc方法就是释放对象的标识,一旦出现[super alloc],父类对象就不会存在了,子类继承过来的属性也会随之被释放)
还有一个问题:如下例:为什么要先让[_car release]?或者说为什么要写这个?用于什么情况之下?
这种情况存在于:Person类中存在一个car属性,而car又是Car类的对象
如下图:

当p被释放时,如果不让car释放,就会造成内存泄漏,所以必须先让[_car release];

有人会有疑问:_car 和car是同一个吗?

答案:是同一个,为什么呢,因为下图所示,set方法会给_car赋值为car,这是必然的,_car相当于全局变量,不赋值是没有任何意义的,

赋值了才能代表一个对象car,才能执行release操作,属性其实是不会执行也不能执行release操作的,除非这种情况下:给属性赋值为对象(刺激吧!!!)

 

注意

1)永远不要直接通过对象调用dealloc方法(实际上调用并不会出错) 一旦对象被回收了, 它占用的内存就不再可用, 坚持使用会导致程序崩溃(野指针错误)为 了防止调用出错,可以将“野指针”指向nil(0)。 这句话也就直接说明了,调用delloc方法就是回收了对象的内存空间,释放了内存.

至于顺序:为什么先进行release后[super delloc], 查阅资料给出我这一句答复:

一定要[super dealloc],而且要放到最后,意义是:你所创建的每个类都是从父类, 根类继承来的,有很多实例变量也会继承过来,这部分变量有时候会在你的程序内使用,它 们不会自动释放内存,你需要调用父类的 dealloc方法来释放,然而在此之前你需要先把自 己所写类中的变量内存先释放掉,否则就会造成你本类中的内存积压,造成泄漏 

ps:说实话我不太懂,为什么会造成内存积压? 
我个人理解:关于顺序可能只是苹果规定的一种规范,脑袋疼,,,,未完待续.....................

-(void)dealloc
{

          [_car release];
          [super dealloc];

}

 
先写上: OC规范一般是下划线开头来定义成员变量:_age
 
5.内存管理的原则:
1)谁创建,谁release
2)谁retain,谁release
内存管理常见问题:
1)野指针:指向被销毁对象的指针
2)僵尸对象:已经被销毁的对象
3)空指针:没有指向存储空间的指针(里面存的是nil)
4)内存泄漏:上面已经提过
了解:

1)空指针:没有指向任何东西的指针,给空指针发送消息不会报错

 

2)nil和Nil及NULL、NSNull的区别:

 

nil:是一个对象值;如果我们要把一个对象设置为空的时候就用nil;

A null pointer to an Objective-C object. ( #define nil ((id)0) )

 

Nil:是一个类对象的值,如果我们要把一个Class类型的对象设置为空的时候就用Nil ;

A null pointer to an Objective-C class.

 

NULL 是一个通用指针;

A null pointer to anything else. ( #define NULL ((void *)0) )

 

NSNull 是一个对象,它用在不能使用nil的场合;A class defines a singleton object used to represent null values in collection objects (which don't allow nil values).

                                                                                                                    

[NSNull null]: The singleton instance of NSNull.

[NSNull null]是一个对象,他用在不能使用nil的场合。

 
 
6.OC中的属性关键字:

 @property的修饰关键字
1> 控制set方法的内存管理

* assign: 直接赋值, 不做任何内存管理(默认, 用于非OC对象类型)
* retain: release旧值,retain新值(用于OC对象),要配合nonatomic使用 
* copy: release旧值, copy新值(一般用于NSString *)

2> 控制是否需要生成set方法
* readwrite: 同时生成set方法和get方法(默认) * readonly: 只会生成get方法 

1)assign

用于 ‘基本数据类型’、‘枚举’、‘结构体’ 等非OC对象类型

eg:int、bool等

后续见下一波...............

 7.set方法的内存管理:

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

int float double long struct enum 
-(void)setAge:(int)age
{
    _age=age;
 }

2)OC对象类型 

-(void)setCar:(Car*)car{
//判断_car 存放的是否是 形参对象,如果不是,则执行 [_car realease];
 if (_car!=car) {
  
    [_car release];//先释放上一个对象,(注意第一次是向nil发送release消息)
     _car = [car retain];
    } 
}

8.@class的使用以及和#import的区别:

作用

可以简单地引用一个类
简单使用
@class Dog; //类的引入
仅仅是告诉编译器: Dog是一个类; 并不会包含Dog这个类的所有内容 

具体使用

在.h文件中使用@class引用一个类 在.m文件中使用#import包含这个类的.h文件

如下面代码:

 

如下面代码: A.h文件

#import "B.h"
@interface A : NSObject {

  B *b;

}

@end

 

 

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

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

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

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

所以:我们实际开发中尽量在.h头文件中使用@class 

9.循环retain
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
posted @ 2016-10-17 23:53  忆缘晨风  阅读(215)  评论(0编辑  收藏  举报