1. 进行内存管理的原因
为什么要进行内存管理?由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存较多时,系统就会发出内存警告,这时需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等。如果程序占用内存过大,系统可能会强制关闭程序,造成程序崩溃、闪退现象,影响用户体验。
因此,我们在编写代码的过程中需要进行合理的分配内存、清除内存,回收那些不需要再使用的对象。从而保证程序的稳定性。
2. iOS程序中的内存分配分区
2.1 堆区
亦称动态内存分配,动态内存的生存期由程序员决定,一般由程序员分配释放,分配:用malloc、new、alloc申请任意大小的内存,释放:在不再使用的时候由程序员释放,如果程序员不释放,程序结束时由操作系统回收。在程序运行的过程中,程序员没有及时将不再使用的内存释放掉的话,可能会出现内存泄漏,内存溢出的情况。
内存泄漏:是指在程序运行过程中,动态分配的内存没有得到及时释放,导致系统内存逐渐耗尽。
内存溢出:是指当应用程序申请的内存超过了系统分配给该应用程序的内存上限,从而导致应用程序崩溃或运行缓慢的情况。
2.2 栈区
栈区(stack) 由编译器自动分配并释放,存放函数的参数值,局部变量(不包括OC对象)等。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点时有限制,数据不灵活。
继承了NSObject的对象的存储在操作系统的堆
里边,非OC对象(int、char、float、double、struct、enum、等)一般放在操作系统的栈
里面。
注意:指向对象的指针变量存放在栈区,其指向存放在堆区的对象。
代码示例:
@interface People:NSObjact
{
int age;//成员变量,表示年龄
NSString *name;//成员变量,表示名字
}
@end
@implementation
@end
-(void)testStackAndHeap{
int a = 10;//非OC对象存储在栈区
int b = 20;//非OC对象存储在栈区
People *p = [People new];//指针变量p存储在栈区,其指向的对象存放在堆区
}
2.3 全局区(静态区) (static)
全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,这两个区域都在全局区,程序结束后由系统释放。
注意:
1.全局区又可分为未初始化全局区:.bss段和初始化全局区:data段。
举例:int a;未初始化的。int a = 10;已初始化的。
2.我理解的全局变量应该是声明在.m文件中@implementation外部的变量,可以在其他.m文件中通过extern访问
3.静态变量的概念在我的另外一篇文章中有相应的讲解介绍http://t.csdnimg.cn/gk4wEhttp://t.csdnimg.cn/gk4wE
2.4 文字常量区
存放常量字符串,程序结束后由系统释放。在程序中使用的常量,都会到文字常量区获取。
2.5 程序代码区
存放函数的二进制代码,运行程序就是执行代码,代码要执行就要加载进内存。
3. MRC 手动引用内存管理机制
全称 Manual Reference Counting, 是需要开发者手动标记对象以决定其在内存中的命运的运行机制。管理通过使用 retain
, release
, 以及 autorelease
的消息发送来实现。
引用计数:
retain
: 持有(拥有)对象,对象引用数加 1release
: 释放对象,对象引用数减 1autorelease
: 通知系统,在 @autoreleasepool 代码块结束时,对对象调用 release
由于我对MRC模式用的不多,平时工作中很少接触到,因此只能把看到的比较好的文章推荐给大家
iOS 开发:彻底理解 iOS 内存管理(MRC、ARC) - 简书 (jianshu.com)https://www.jianshu.com/p/48665652e4e4//这篇文章对MRC的原理讲的比较细,看它
iOS MRC、ARC 与 @autoreleasepool 学习笔记 - 简书 (jianshu.com)https://www.jianshu.com/p/b452237458a1
4. ARC 自动引用内存管理机制
使用ARC后,系统会检测出何时需要保持对象,何时需要自动释放对象,何时需要释放对象,编译器会管理好对象的内存,会在何时的地方插入retain, release和autorelease,通过生成正确的代码去自动释放或者保持对象。我们完全不用担心编译器会出错。
4.1属性修饰符
在详细介绍ARC之前,我们先讲解几个常见的与内存管理相关的属性修饰符
1.assign
简单赋值,不会更改对象的引用计数,一般用于原始数据类型(primitive type)的赋值。可以用于修饰对象,但只是存储对象的引用+,并不真正的拥有对象,效果相当于 weak
,可是有一个坑是当对象被释放后,assign
属性的指针变量不会变成 nil
,而是成为野指针(dangling pointer),下次在访问该对象就会造成野指针异常,(如果这块内存地址被其他对象申请并占用,就可能会报告找不到方法的异常,)!
野指针: "野指针"不是NULL指针,是指向"垃圾"内存(不可用内存)的指针,因为野指针所指向的内存区域已经被回收了。
2.retain
为了防止出现因assign对象销毁导致野指针,引入了retain,使用了引用计数,retain属性表示两个对象地址相同(将一个对象赋值给retain属性,会建立一个新的指针,并且将对象的地址拷贝到retain属性中(指针拷贝),对象的引用计数加一,两个指针变量所指向的是同一个对象)
指针拷贝指的是创建一个新的指针变量,并将原始对象的内存地址赋值给这个新指针变量的过程。
3.copy
copy是创建一个新对象,两个对象的内容一样(对象存储的数据一样,但是对象的地址是不一样的)。新的对象引用计数为1,旧的对象引用计数不变。两个对象之间互相不影响,copy能减少对象对上下文的依赖。
我们上代码了解一下以上三个属性修饰符,在MRC模式下测试
@interface LearnAssignRetainCopy
@property (nonatomic,assign) int assignIntProperty;
@property (nonatomic,assign) NSArray assignArrProperty;
@property (nonatomic,assign) People assignPeopleProperty;
@property (nonatomic,retain) NSString *retainProperty;
@property (nonatomic,copy) NSString *coProperty;
@end
@implementation
-(void)testAssign{
int intValue = 25;
self.assignIntProperty = intValue;
NSLog(@"int:%@",intValue)
NSLog(@"assignInt:%@",self.assignIntProperty);
NSArray *tempArr = [NSArray arrayWithObject:@"shao"];
NSLog(@"tempArr的引用计数:%ld",[tempArr retainCount]);//引用计数为1
self.assignArrProperty = tempArr;//将对象赋值给assign修饰的property,引用计数不变,只是进行指针拷贝
NSLog(@"tempArr的引用计数:%ld",[tempArr retainCount]);
NSLog(@"tempArr:%@", self.assignArrProperty);
self.assignPeopleProperty = [People new];
NSLog(@"self.assignPeopleProperty的引用计数:%ld",[self.assignPeopleProperty retainCount]);
[self.assignArrProperty release];
//[self.assignPeopleProperty release];//释放之后再次访问会报访问异常,对于NSArray对象进行释放操作会造成访问异常
}
//将不可变的对象赋值给retain属性,进行的是浅复制,对象引用计数加一;将不可变的对象赋值给copy属性,进行的是浅复制,对象的引用计数加一,这两者都不会创建新的对象。
-(void)testCopyAndRetain{
NSString *str = @"我是不可变的";
self.retainProperty = str;
self.coProperty = str;
NSLog(@" str: %p",str);
NSLog(@" copy: %p",self.coProperty);
NSLog(@"retain: %p",self.retainProperty);
}
//将不可变的对象赋值给retain属性,进行的是浅复制,进行指针拷贝,对象引用计数加一;将不可变对象赋值给copy属性,进行的是深复制,创建新的对象,旧的对象引用计数不变,新的对象引用计数为1
-(void)testCopyAndRetain1{
NSMutableString *str = [NSMutableString stringWithString:@"我是可变的"];
self.retainProperty = str;
self.coProperty = str;
NSLog(@" str: %p",str);
NSLog(@" copy: %p",self.coProperty);
NSLog(@"retain: %p",self.retainProperty);
}
在iOS开发中我们一般都这么定义:@property (nonatomic,copy) NSString *name,而不这么定义:@property (nonatomic,retain) NSString *name,两者的差别就在一个使用copy,一个使用retain。 这是为什么呢? 在说明白retain和copy的区别,首先需要明白深复制和浅复制的概念。
1 深复制:内容拷贝,源对象和副本对象指的是两个不同的对象,创建新的对象,源对象引用计数器不变,副本对象引用计数器为1
2 浅复制:指针拷贝,源对象和副本对象指的都是同一个对象,不创建新的对象,对象引用计数器+1,相当于retain
只有不可变对象创建不可变副本(copy)才是浅复制,其它的都是深复制
retain是浅复制,因为它是指针拷贝,源对象和副本对象指的都是同一个对象。copy有两种可能,将不可变的对象赋值给copy修饰的属性的时候,这个时候是浅复制,将可变对象赋值给copy修饰的对象是深复制
5. Strong和Week
这两个修饰符一般在ARC模式下使用,都是用来修饰OC对象的,不能用来修饰基本类型。
strong修饰属性的时候,表示该属性为强引用,当使用一个强引用指向某一个对象的时候,该对象的引用计数加一。
weak修饰属性的时候,表示该属性为弱引用,当使用一个弱引用指向某一个对象的时候,该对象的引用计数不变。
下面这一篇博客是我近期看到的写的比较好的,分享给大家,大家可以通过阅读这一篇文章对strong和weak由一个清晰的认识,在阅读这一篇文章的过程中,我将代码使用Xcode运行,加入自己个人的一些代码,也是终于对strong和weak有了一个较为清晰的认识。