一.概述:
1.内存的作用:存储数据(例子:声明一个变量,会将变量的数据存储到内存之中)。
2.当数据不再被使用的时候,占有的内存空间如何被释放:
内存中的五大区域:
1.栈:局部变量,当局部变量的作用域被执行完毕之后,这个局部变量就被系统回收。
2.BSS段:未初始化的全局变量、静态变量。一旦初始化就回收,并转存到数据段之中。
3.数据段:已经初始化的全局变量、静态变量。直到程序结束的时候才会被回收。
4.代码段:代码,程序结束的时候系统会自动回收存储在代码段中的数据.
5.堆:实例化的对象。底层使用C函数申请的空间。需要我们手动进行管理。
除了 '堆' ,其他由系统自动回收的,不需要我们手动管理。
3.堆:对象存储在堆中,当“代码块(局部作用域)”结束时候,这个代码块(局部作用域)中涉及的所有局部变量都会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但是!!!对象依然存在内存中,造成内存泄露!!!
MRC情况下:
void test(){
Person *p = [Person alloc] init];
}
int main(){
//内存泄漏
test();
return 0;
}
无论是 iOS 还是 android 中,系统对每个程序运行时内存的占用都有一个限制,默认都是几十 M 左右大小,当 程序占用的内存的大小超过限制时,程序可能就会被强制退出。
二.如何进行管理呢?
1.了解一个概念:“引用计数器”
1). 每1个对象都有1个属性,叫做retainCount引用计数器,类型是unsigned long占据8个字节。(64位操作系统)
2). 当多1个人使用这个对象的时候,应该先让这个对象的引用计数器的值+1代表这个对象多1个人使用。
3). 当这个对象少1个人使用的时候,应该先让这个对象的引用计数器的值-1代表这个对象少1个人使用。4). 当这个对象的引用计数器变为0的时候,代表这个对象无人使用,这个时候系统就会自动回收这个对象。
2.如何操作引用计数器:
1). 为对象发送1条retain消息,对象的引用计数器就会加1,当多1个人使用对象的时候才发。
2). 为对象发送1条release消息,对象的引用计数器就会减1,当少1个人使用对象的时候才发。
3). 为对象发送1条retainCount消息,就可以取到对象的引用计数器的值。
4). 对象的引用计数器变为0的时候,对象就会被系统立即回收,在对象被回收的时候,会自动调用对象的dealloc方法。
引用计数器的作用:
用来记录当前这个对象有多少个人在使用它,默认情况下,创建1个对象出来,这个对象的引用计数器的默认值是1。
如上图,这个对象正在被 A、B、C 三者使用(拥有),那么它的引用计数就是 3。
三.MAC:
僵尸对象:所占用的内存已经被回收的对象,僵尸对象不能再使用 .
野指针:指向僵尸对象的指针,给野指针发送消息会报错
空指针:没有指向任何东西的指针(存储的东西是 nil、null、0),给空指针发送消息不会报错。
内存泄露:指的是1个对象没有被及时的回收,在该回收的时候而没有被回收,一直驻留在内存中,直到程序结束的时候才回收。
-
Xocode打开僵尸对象检测:Product -> Scheme -> EditScheme -> Diagnostict -> Zombie Objects(耗性能)
2.野指针错误形式在Xcode中通常表现为:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)错误。
补充:
nil:用于“对象”的字面空值。例如:NSString *someSting = nil; id someObject = nil;
Nil: 用于Class类型的对象空值。 例如:class someclass = Nil;
NULL: 用于C指针空值。 例如: int *p = NULL;
NSNull:
一般用于表示集合中值为空的对象。
- 只有一个单例方法:+[NSNull null]
例子:
// 因为 nil 被用来用为集合结束的标志,所以 nil 不能存储在 Foundation 集合里。 NSArray *array = [NSArray arrayWithObjects:@"one", @"two", nil]; // 错误的使用 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:nil forKey:@"someKey"]; // 正确的使用 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setObject:[NSNull null] forKey:@"someKey"];
MRC: Manual Reference Counting 手动引用计数-手动内存管理-当多1个人使用对象的时候,要求程序员手动的发送retain消息,少1个人使用的时候程序员手动的发送relase消息。
在Xcode中关闭ARC(项目默认):项目属性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting设置为No即可。
MRC
1). 新创建1个对象,这个对象的引用计数器的值默认是1。
2). 当对象的引用计数器变为0的时候,对象就会被系统立即回收并自动调用dealloc方法。
3). 为对象发送retain消息,对象的引用计数器就会+1。
4). 为对象发送release消息,并不是回收对象,而是让对象的引用计数器-1
当对象的引用计数器的值变为0的时候,对象才会被系统立即回收。
MRC中重写dealloc方法的规范:
必须要调用父类的dealloc方法,并且要放在最后一句代码。
内存管理的重点及原则
(1)谁创建,谁 release:
如果你通过 alloc、new 或[mutable]copy 来创建一个对象,那么必须调用 release 或 autorelease(2)谁 retain,谁 release:
只要你调用了 retain,无论这个对象如何生成的,你都要调用 release。 综上:有始有终,有加就有减。曾经让对象计数器+1,就必须在最后让对象计数器-1。(3). 在ARC机制下,retain release dealloc这些方法方法无法调用.
#import <Foundation/Foundation.h>
@interface Person : NSObject
#pragma mark - 属性
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
@end
#import "Person.h"
@implementation Person
#pragma mark - 覆盖方法
#pragma mark 重写dealloc方法,在这个方法中通常进行对象释放操作
-(void)dealloc{
NSLog(@"Invoke Person's dealloc method.");
[super dealloc];//注意最后一定要调用父类的dealloc方法(
两个目的:一是父类可能有其他引用对象需要释放;
二是:当前对象真正的释放操作是在super的dealloc中完成的)
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
void Test1(){
Person *p=[[Person alloc]init]; //调用alloc,引用计数器+1
p.name=@"xb";
p.age=20;
NSLog(@"retainCount=%lu",[p retainCount]);
//结果:retainCount=1
[p release];
//结果:Invoke Person's dealloc method.
//上面调用过release方法,p指向的对象就会被销毁,但是此时变量p中还存放着Person对象的地址,
//如果不设置p=nil,则p就是一个野指针,它指向的内存已经不属于这个程序,因此是很危险的
p=nil;
//如果不设置p=nil,此时如果再调用对象release会报错,但是如果此时p已经是空指针了,
//则在ObjC中给空指针发送消息是不会报错的
[p release];
}
void Test2(){
Person *p=[[Person alloc]init];
p.name=@"xb";
p.age=20;
NSLog(@"retainCount=%lu",[p retainCount]);
//结果:retainCount=1
[p retain];//引用计数器+1
NSLog(@"retainCount=%lu",[p retainCount]);
//结果:retainCount=2
[p release];//调用1次release引用计数器-1
NSLog(@"retainCount=%lu",[p retainCount]);
//结果:retainCount=1
[p release];
//结果:Invoke Person's dealloc method.
p=nil;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Test1();
}
return 0;
}
2.当一个对象的成员变量(属性)是一个对象的时候。
set方法的写法:
//人对象拥有车对象
-(void)setCar:(Car *)car{
if(_car !=car){
//对旧对象做一次release
[_car release];//诺没有旧对象,则没有影响。初始化的时候nil
_car = [car retain];
}
}
//在人对象重写dealloc
-(void)dealloc{
[_car release];
[super dealloc];
}